1 पॉइंट द्वारा GN⁺ 2024-08-23 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Python पहली दो लाइनों की source encoding magic comment और custom codec का उपयोग करके, फ़ाइल की सामग्री को execution से पहले बदल सकता है या उसे पूरी तरह अलग code से replace कर सकता है
  • custom codec को .pth path configuration file में import execute करके interpreter initialization के दौरान register किया जा सकता है, और codecs.register से search function जोड़ा जाता है
  • codec implementation के लिए decode(data: bytes) -> tuple[str, int] और incremental decoder की जरूरत होती है; exception handling न हो तो असली कारण की जगह सिर्फ SyntaxError: encoding problem: your_codec दिख सकता है
  • ++/-- increment/decrement operator, curly braces आधारित Python, cppyy के जरिए C/C++ execution, और TOML को JSON Schema से validate करने का तरीका—ये सब उसी entry point से implement किए जा सकते हैं
  • मज़ाकिया examples से आगे बढ़कर, इसका उपयोग pythonql, future-typing, future-fstrings, future-annotations जैसे Python extensions और backports में भी हो सकता है; magic_codec repetitive काम कम करता है

source encoding को preprocessing entry point के रूप में इस्तेमाल करना

  • PEP-0263 के अनुसार Python file की पहली दो lines में से किसी एक में source code encoding specify की जा सकती है
    • उदाहरण: # coding=utf8, # -*- coding: utf8 -*-, # vim: set fileencoding=utf8 :
  • magic line को regex ^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+) से match होना चाहिए
    • codec name को [-_.a-zA-Z0-9]+ से match होना चाहिए
  • custom codec सिर्फ source को decode करने तक सीमित नहीं रहता; वह source string को बदलकर Python interpreter को पास कर सकता है

.pth file से codec register करना

  • अगर Python interpreter -S option के बिना start होता है, तो initialization के दौरान site package load होता है
  • site-packages के अंदर .pth path configuration file खाली lines और # से शुरू होने वाली lines को छोड़कर बाकी content को module search path में जोड़ती है
  • Python documentation के अनुसार, जिन lines में import के बाद space या tab आता है वे execute होती हैं
    • उदाहरण: packagename.pth में import packagename.register_codec डालने पर initialization के दौरान वह module import हो जाता है
  • imported module codecs.register call करके codec search function register कर सकता है
    • import सिर्फ एक बार execute होता है, इसलिए search function भी सिर्फ एक बार register होता है

custom codec implementation

  • custom codec के लिए दो चीजें चाहिए
    • decode(data: bytes) -> tuple[str, int]
    • incremental decoder class
  • decode function वास्तविक UTF-8 decoding codecs.utf_8_decode से कर सकता है और result string को preprocessing function को दे सकता है
  • अगर codec के अंदर की exceptions catch नहीं होतीं, तो सामान्य traceback की जगह सिर्फ SyntaxError: encoding problem: your_codec output हो सकता है
    • preprocessing function में आई exception को traceback.print_exc() से सीधे print करके फिर से raise करना बेहतर है
  • incremental decoder पूरी file को buffer में जमा रख सकता है और आखिरी decode call में सिर्फ एक बार preprocess कर सकता है
    • example implementation codecs.BufferedIncrementalDecoder को inherit करता है और decode(self, data, final=False) में सिर्फ final होने पर process करता है
  • preprocessing result को original file content का इस्तेमाल करना जरूरी नहीं है; पूरी तरह arbitrary Python code भी return किया जा सकता है
    • हालांकि पहली line को magic line माना जाता है इसलिए वह delete होती है, और result valid Python होना चाहिए

Python syntax extension examples

  • ++ और -- increment/decrement operators

    • Python में unary increment/decrement operator नहीं है
    • x++, x-- syntactically valid नहीं हैं
    • ++x, --x syntactically valid तो हैं, लेकिन वे क्रमशः x.__pos__().__pos__(), x.__neg__().__neg__() calls बन जाते हैं
    • preprocessor token stream बदलकर उन्हें increment/decrement operator जैसा behave करा सकता है
      • x++(x, x := x + 1)[0]
      • x--(x, x := x - 1)[0]
      • ++x(x, x := x + 1)[1]
      • --x(x, x := x - 1)[1]
    • यह transformation Python के assignment expression, यानी walrus operator का उपयोग करता है
    • सिर्फ simple token replacement से x++ - -y जैसे expression में fail हो सकता है; x++ - (-y) की तरह parentheses से ambiguity घटाई जा सकती है
    • incdec.py regex से replace करता है, लेकिन string literal के अंदर replacement से बचने की कोशिश के बावजूद fragile हो सकता है
    • token stream को सीधे modify करने वाला implementation magic.incdec में है
  • curly braces आधारित Python

    • from __future__ import braces SyntaxError: not a chance raise करता है
    • preprocessor token stream modify करके curly braces scope को indentation-based Python में बदल सकता है
    • implementation flow इस प्रकार है
      • tokenize.generate_tokens से tokens generate करता है
      • input string को io.StringIO के readline के जरिए line-by-line provide करता है
      • existing INDENT, DEDENT tokens remove करता है
      • { मिलने पर indentation level बढ़ाता है और : output करता है
      • } मिलने पर indentation level घटाता है
      • NL के बाद current indentation level के अनुसार INDENT token जोड़ता है
    • Python के dictionary literal से conflict घटाने के लिए, सिर्फ { के बाद newline होने पर indentation level adjust किया जा सकता है, और सिर्फ } से पहले newline होने पर closing scope की तरह treat किया जा सकता है
    • multi-line dictionary में backslash का उपयोग करने पर curly braces के अंदर newline token नहीं बनता, इसलिए उसका उपयोग किया जा सकता है

दूसरी languages को Python से execute करना

  • C और C++

    • shell scripts, CMake scripts, PHP, Ruby जैसी languages जिनमें # से comments लिखे जाते हैं, उनमें shebang के साथ encoding magic line डालना आसान है
    • C और C++ में comments /* ... */ या // ... होते हैं, लेकिन preprocessor directives # से शुरू होते हैं, इसलिए उन्हें encoding regex से match कराया जा सकता है
    • example magic line C/C++ source में भी valid है और Python के encoding pattern से भी match होती है
      • #define CODEC "coding:magic.cpp"
    • cppyy का उपयोग करके Python में C/C++ code interpret किया जा सकता है और bindings generate किए जा सकते हैं
      • cppyy internally cling का उपयोग करता है
    • preprocessing result roughly नीचे दिए Python code जैसा बनता है
      • import cppyy
      • cppyy.cppdef("<input source file content>")
      • from cppyy.gbl import main
      • __name__ == "__main__" होने पर C/C++ का main() call
    • example implementation magic.cpp में है

TOML validation tool के रूप में उपयोग करना

  • TOML में comments # से शुरू होते हैं, इसलिए # coding: magic.toml जैसी encoding magic line डाली जा सकती है
  • preprocessing result को Python validation script में बदलने पर Python interpreter को TOML validation tool की तरह इस्तेमाल किया जा सकता है
  • validation example नीचे दिए modules का उपयोग करता है
    • tomllib से TOML file पढ़ता है
    • json से JSON Schema file पढ़ता है
    • jsonschema से validate करता है
  • execution example:
    • python tests/toml/data_valid.toml -s tests/toml/schema.json
    • valid होने पर Successfully validated. output करता है
  • गलत TOML example में scores array के अंदर string '20' number नहीं है—यह validation error output होता है
  • implementation example magic.toml में है

वास्तविक उपयोग और magic_codec

  • custom codec और .pth file को साथ इस्तेमाल करने पर Python interpreter का behavior काफी बदला जा सकता है
  • ज्यादातर examples मज़े के लिए हैं, लेकिन real-world use cases भी हैं
  • अगर आप site-packages को सीधे modify नहीं करना चाहते या .pth files और repetitive code खुद नहीं लिखना चाहते, तो magic_codec इस्तेमाल कर सकते हैं
  • magic_codec extension को magic_ prefix वाले Python package के रूप में बनाया जा सकता है
    • file codec को magic_foo set करने पर magic_foo package load होता है
    • यह check करता है कि उस package में preprocess function है या नहीं
  • expected preprocess signature इस प्रकार है
    • def preprocess(data: str) -> str:
  • extension example example/ में है

1 टिप्पणियां

 
GN⁺ 2024-08-23
Hacker News की राय
  • from __future__ import braces चलाने पर SyntaxError: not a chance वाला यह मज़ेदार error message CPython में 2001 से hardcoded था
    https://github.com/python/cpython/commit/ad3d3f2f3f19833f59f...
    इसे लिखने वाले Jeremy Hylton अब Google में AI search quality के Principal Engineer हैं; 24 सालों में उनका करियर प्रतिबंधित syntax को मज़ाकिया ढंग से याद करने से लेकर ऐसे universal query system तक आ गया जिसे किसी dedicated syntax की ज़रूरत नहीं—यह काफी प्रभावशाली है

    • break rust; से Rust compiler में internal compiler error आने वाली बात याद आ गई। सोचता हूँ दूसरी languages में ऐसे कितने Easter eggs होंगे
    • समझ नहीं आता इसमें हैरानी की क्या बात है। 2001 में कोई भी जाकर Python में कुछ डाल नहीं सकता था; उस समय यह niche project था, और योगदान देने वाले लोग समझदार व समर्पित थे, इसलिए उनके प्रभावशाली करियर होने की संभावना ज़्यादा थी
      मुझे लगता है यह गलतफहमी है कि playful, अनौपचारिक hobby hacking और असली professional development अलग-अलग दुनिया हैं
    • वह भोला-भाला दौर था। Hylton शायद Tim Peters के बचाव में no-confidence vote की मुहिम में शामिल हो सकते हैं
      https://news.ycombinator.com/item?id=41314393
    • ऐसे Easter eggs देखना हमेशा अच्छा लगता है। अफसोस है कि वे पहले जितने आम नहीं रहे
  • मुझे लगता था import hook के साथ शरारत करना नौकरी से निकाले जाने का सबसे creative तरीका है, लेकिन वह मेरी मासूम सोच थी। अफसोस है कि codec regex की वजह से μtf8 जैसी चीज़ से ठीक से trolling नहीं कर पाऊँगा, तो अब शायद import hook, preprocessor और sys.settrace का इस्तेमाल करके हर function को उसके ठीक पहले call हुए function से monkey-patch करना और हर 17 मिनट में stdout और stderr को आपस में बदलना ही रास्ता बचेगा

    • अच्छी language की तरह curly braces के इस्तेमाल को भी मजबूर करना चाहिए
  • Python ने preprocessor hooks को जानबूझकर expose नहीं किया, इसके अच्छे कारण हैं, और मुझे लगता है कि sensible adults को इससे दूर रहना चाहिए
    लेकिन दूसरी तरफ, मैं sensible adults के साथ उलझना भी नहीं चाहता। इससे सचमुच बहुत मज़ेदार चीज़ें की जा सकती हैं

    • Python की philosophy है कि यह “consenting adults” के लिए language है, इसलिए इसमें public/private visibility specifiers नहीं हैं और शायद हर तरह का metaprogramming magic भी खुला छोड़ रखा है। अगर “sensible adults” की चिंता होती तो ये design decisions थोड़े अजीब लगते :)
    • अगर लोगों को यह आसान और साफ तरीके से करने से रोका जाएगा, तो वे इसे और भी खराब व hacky तरीकों से करने की कोशिश करेंगे
  • यह सुविधाजनक और वाकई उपयोगी लगता है। बेवकूफी भरे import hacks करते समय मैं आम तौर पर module import करके ast module से code rewrite करता था, फिर exec करता था और बीच में exit() डाल देता था; preprocessor हो तो यह बहुत ज्यादा usable होगा
    जब तक सभी dict order guarantee नहीं करते थे, तब तक मैं ast rewriting का इस्तेमाल मुख्यतः list literals को ordered dict calls में बदलने के लिए करता था, और यह सच में useful था
    मुझे Python की flexibility पसंद है। मैंने जो सबसे cursed काम किया, वह strings को in-place बदलना था, और आखिर में mmap तक abuse करके खुद को बदलने वाली script लिख डाली। अब लगता है producer के रूप में Lisp interpreter लिखना पड़ेगा

    • “strings को in-place बदलना” कह रहे हो, लेकिन strings तो immutable होती हैं। क्या ctypes जैसी किसी चीज़ से memory location पर directly लिखा था?
  • अब तक मिला सबसे अच्छा use case JSX से inspired pyxl है: https://github.com/dropbox/pyxl
    ऐसा code लिखा जा सकता है
    # coding: pyxl
    print Hello World!

  • सोचता हूँ क्या इसका इस्तेमाल Python 2 से 3 में transition को बेहतर संभालने के लिए किया जा सकता था। जैसे # coding: six.python2 Python 2 code को valid Python 3 code में ढाल दे, या # coding: six.python3 Python 3 code को Python 2 पर चलने लायक बदल दे
    b"..." या u"..." prefixes जोड़ना या हटाना भी संभव रहा होगा

    • मदद तो मिल सकती थी, लेकिन जिस हिस्से में मदद मिलती वह आसान हिस्सा था। Python 2 से 3 की मुश्किल runtime behavior changes थे। Python 2 में ASCII रखने वाला Unicode और normal string एक जैसी string की तरह behave करते थे, इसलिए dict key के रूप में इस्तेमाल करने पर वे same item की ओर इशारा करते थे, लेकिन Python 3 में same ASCII content वाले bytes और str एक ही dict में अलग-अलग items की ओर इशारा करते हैं
      और भी tricky changes हैं। .keys() और .values() जैसी कई built-in सुविधाएँ Python 2 में list return करती हैं, लेकिन Python 3 में iterator return करती हैं। six utilities या दूसरे workarounds से code को safely translate करने की कोशिश करें तो code बहुत verbose हो जाता है, क्योंकि ज्यादातर चीज़ें एक बार ही use होती हैं, लेकिन कभी-कभी दो बार भी use होती हैं
      अगर import के समय code rewrite करने वाला tool हो, तो मेरी राय में transformed code को commit करके धीरे-धीरे clean up करना बेहतर है। मुश्किल हिस्सा str बनाम bytes जैसे behavior changes हैं जो दूर-दूर के code तक असर डालते हैं
  • इस coding hook strategy से बनी dependencies को pip freeze या uv पकड़ते हैं क्या?
    अगर नहीं, तो मज़ेदार समय आने वाला है :). अगर किसी ने ऐसी चीज़ डाल रखी है, तो लगभग तय है कि और भी जाल होंगे; ऐसे dragons से लड़ने के बजाय library दोबारा लिखना शायद आसान होगा

  • pseudocode Python बनाकर उसे LLM से decode करवाया जाए तो काफी मज़ेदार होगा। जाहिर है भयानक होगा, लेकिन मज़ा तो आएगा