1 puan yazan GN⁺ 2024-08-23 | 1 yorum | WhatsApp'ta paylaş
  • Python, ilk iki satırdaki kaynak kodlama sihirli yorumu ve kullanıcı tanımlı codec'ler sayesinde dosya içeriğini çalıştırmadan önce değiştirebilir veya tamamen farklı bir kodla değiştirebilir
  • Kullanıcı tanımlı codec'ler, .pth yol yapılandırma dosyası içindeki import çalıştırmasıyla yorumlayıcı başlatılırken kaydedilebilir ve codecs.register ile bir arama işlevi eklenir
  • Codec uygulaması için decode(data: bytes) -> tuple[str, int] ve bir artımlı decoder gerekir; istisnalar işlenmezse asıl neden yerine yalnızca SyntaxError: encoding problem: your_codec görünebilir
  • Aynı giriş noktasıyla ++/-- artırım-azaltım operatörleri, süslü parantez tabanlı Python, cppyy üzerinden C/C++ çalıştırma ve TOML'ü JSON Schema ile doğrulama gibi şeyler uygulanabilir
  • Şaka amaçlı örneklerin ötesinde pythonql, future-typing, future-fstrings, future-annotations gibi Python genişletmeleri ve backport'larda da kullanılabilir; magic_codec ise tekrar eden işleri azaltır

Kaynak kodlamasını önişlem giriş noktası olarak kullanmak

  • PEP-0263 uyarınca bir Python dosyasının ilk iki satırından birinde kaynak kodu kodlaması belirtilebilir
    • Örnek: # coding=utf8, # -*- coding: utf8 -*-, # vim: set fileencoding=utf8 :
  • Sihirli satır, ^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+) düzenli ifadesiyle eşleşmelidir
    • Codec adı da [-_.a-zA-Z0-9]+ ile eşleşmelidir
  • Kullanıcı tanımlı bir codec, yalnızca kaynağı decode etmekle kalmaz; kaynak dizesini değiştirip ardından Python yorumlayıcısına iletebilir

Codec'i .pth dosyasıyla kaydetmek

  • Python yorumlayıcısı -S seçeneği olmadan başlatılırsa site paketi başlatma sırasında yüklenir
  • site-packages içindeki .pth yol yapılandırma dosyaları, boş satırlar ve # ile başlayan satırlar dışındaki içerikleri modül arama yoluna ekler
  • Python belgelerine göre import ile başlayıp ardından boşluk ya da tab gelen satırlar çalıştırılır
    • Örneğin packagename.pth içine import packagename.register_codec koyarsanız, bu modül başlatma sırasında import edilir
  • Import edilen modül, bir codec arama işlevini kaydetmek için codecs.register çağırabilir
    • Import yalnızca bir kez çalıştığı için arama işlevi de yalnızca bir kez kaydedilir

Kullanıcı tanımlı codec uygulaması

  • Kullanıcı tanımlı bir codec için iki şey gerekir
    • decode(data: bytes) -> tuple[str, int]
    • artımlı decoder sınıfı
  • decode işlevi, gerçek UTF-8 decode işlemini codecs.utf_8_decode ile yapar ve ortaya çıkan dizeyi önişleme işlevine aktarabilir
  • Codec içindeki istisnalar yakalanmazsa, normal traceback yerine yalnızca SyntaxError: encoding problem: your_codec yazabilir
    • Önişleme işlevinde oluşan istisnaları traceback.print_exc() ile doğrudan yazdırıp sonra tekrar raise etmek daha iyidir
  • Artımlı decoder, tüm dosyayı ara bellekte toplayıp yalnızca son decode çağrısında bir kez önişleme yapabilir
    • Örnek uygulama, codecs.BufferedIncrementalDecoder sınıfını miras alır ve decode(self, data, final=False) içinde yalnızca final olduğunda işlem yapar
  • Önişleme sonucu mutlaka özgün dosya içeriğini kullanmak zorunda değildir; tamamen keyfi bir Python kodu da döndürebilir
    • Ancak ilk satır sihirli satır olarak beklendiği için silinir ve sonuç geçerli bir Python olmalıdır

Python sözdizimini genişletme örnekleri

  • ++ ve -- artırım-azaltım operatörleri

    • Python'da tekli artırım-azaltım operatörü yoktur
    • x++, x-- sözdizimsel olarak geçerli değildir
    • ++x, --x ise sözdizimsel olarak geçerlidir ama sırasıyla x.__pos__().__pos__() ve x.__neg__().__neg__() çağrısına dönüşür
    • Önişlemci, token akışını değiştirerek bunların artırım-azaltım operatörü gibi davranmasını sağlayabilir
      • x++(x, x := x + 1)[0]
      • x--(x, x := x - 1)[0]
      • ++x(x, x := x + 1)[1]
      • --x(x, x := x - 1)[1]
    • Bu dönüşüm, Python'un atama ifadesi olan walrus operator'u kullanır
    • Yalnızca basit token değiştirme, x++ - -y gibi ifadelerde başarısız olabilir; x++ - (-y) gibi parantezler belirsizliği azaltabilir
    • incdec.py düzenli ifadeyle değiştirme yapar, ancak dizge sabitleri içindeki değişiklikleri önlemeye çalışsa da kırılgan olabilir
    • Token akışını doğrudan değiştiren uygulama magic.incdec içinde yer alır
  • Süslü parantez tabanlı Python

    • from __future__ import braces ifadesi SyntaxError: not a chance üretir
    • Önişlemci, token akışını değiştirerek süslü parantez kapsamını girintiye dayalı Python'a çevirebilir
    • Uygulama akışı şöyledir
      • Token'lar tokenize.generate_tokens ile üretilir
      • Girdi dizesi, io.StringIO içindeki readline ile satır satır verilir
      • Mevcut INDENT, DEDENT token'ları kaldırılır
      • { görüldüğünde girinti seviyesi artırılır ve : yazdırılır
      • } görüldüğünde girinti seviyesi azaltılır
      • NL sonrasında mevcut girinti seviyesine uygun INDENT token'ı eklenir
    • Python sözlük sabitleriyle çakışmayı azaltmak için, yalnızca { sonrasında newline varsa girinti seviyesini ayarlayıp yalnızca } öncesinde newline varsa bunu kapanan kapsam olarak yorumlamak mümkündür
    • Çok satırlı sözlükler, ters eğik çizgi kullanılırsa süslü parantez içinde newline token'ı oluşmayacağı için kullanılabilir

Başka dilleri Python üzerinden çalıştırmak

  • C ve C++

    • Kabuk betikleri, CMake betikleri, PHP ve Ruby gibi # ile yorum yazan dillerde, shebang ile birlikte kodlama sihirli satırını eklemek kolaydır
    • C ve C++'ta yorumlar /* ... */ veya // ... şeklindedir, ancak önişlem direktifleri # ile başladığı için kodlama düzenli ifadesine uyabilir
    • Örnek sihirli satır, hem C/C++ kaynağında geçerlidir hem de Python'un kodlama desenine uyar
      • #define CODEC "coding:magic.cpp"
    • cppyy kullanıldığında Python içinde C/C++ kodu yorumlanıp bağlamalar üretilebilir
      • cppyy bunu dahili olarak cling ile yapar
    • Önişleme sonucu kabaca şu Python koduna dönüşür
      • import cppyy
      • cppyy.cppdef("<input source file content>")
      • from cppyy.gbl import main
      • __name__ == "__main__" olduğunda C/C++ içindeki main() çağrılır
    • Örnek uygulama magic.cpp içinde bulunur

TOML doğrulama aracı olarak kullanmak

  • TOML, yorumları # ile başlattığı için # coding: magic.toml gibi bir kodlama sihirli satırı eklenebilir
  • Önişleme sonucunu bir Python doğrulama betiğine dönüştürerek Python yorumlayıcısı TOML doğrulama aracı gibi kullanılabilir
  • Doğrulama örneğinde şu modüller kullanılır
    • TOML dosyasını okumak için tomllib
    • JSON Schema dosyasını okumak için json
    • Doğrulama için jsonschema
  • Çalıştırma örneği:
    • python tests/toml/data_valid.toml -s tests/toml/schema.json
    • Geçerliyse Successfully validated. yazdırılır
  • Hatalı TOML örneğinde, scores dizisi içindeki '20' dizgesinin sayı olmadığına dair doğrulama hatası yazdırılır
  • Örnek uygulama magic.toml içinde bulunur

Gerçek kullanım ve magic_codec

  • Kullanıcı tanımlı codec ile .pth dosyalarını birlikte kullanmak, Python yorumlayıcısının davranışını büyük ölçüde değiştirebilir
  • Örneklerin çoğu eğlence amaçlı olsa da gerçek kullanım örnekleri de vardır
  • site-packages içine doğrudan dokunmak veya .pth dosyalarını ve tekrar eden kodları elle yazmak istemiyorsanız magic_codec kullanabilirsiniz
  • magic_codec genişletmeleri, magic_ öneki taşıyan Python paketleri olarak oluşturulabilir
    • Dosya codec'ini magic_foo olarak ayarlarsanız magic_foo paketi yüklenir
    • Bu pakette preprocess işlevinin olup olmadığı denetlenir
  • Beklenen preprocess imzası şöyledir
    • def preprocess(data: str) -> str:
  • Genişletme örnekleri example/ içinde yer alır

1 yorum

 
GN⁺ 2024-08-23
Hacker News yorumları
  • from __future__ import braces çalıştırıldığında SyntaxError: not a chance şeklinde çıkan bu komik hata mesajı, 2001’den beri CPython içinde sabit kodlanmış durumdaymış
    https://github.com/python/cpython/commit/ad3d3f2f3f19833f59f...
    Bunu yazan Jeremy Hylton şu anda Google’da yapay zeka arama kalitesinden sorumlu Principal Engineer olarak çalışıyor; 24 yıl içinde yasaklı bir sözdizimini şakacı biçimde anmaktan, özel bir sözdizimi gerektirmeyen evrensel bir sorgu sistemine uzanan bir kariyer çizgisi epey etkileyici

    • break rust; ifadesinin Rust derleyicisinde iç derleyici hatası üretmesini hatırlattı. Diğer dillerde buna benzer kaç tane easter egg var merak ediyorum
    • Bunun neden şaşırtıcı olduğunu anlamıyorum. 2001’de herkes Python’a bir şey ekleyemiyordu; o zamanlar niş bir projeydi ve katkı veren kişiler zeki ve kendini adamış insanlardı, dolayısıyla etkili kariyerlere sahip olma ihtimalleri yüksekti
      Şakacı ve gayriresmî hobi amaçlı hacking’in gerçek profesyonel geliştirmeden ayrı bir dünya olduğu düşüncesinin bir yanılgı olduğunu düşünüyorum
    • Masum zamanlardı. Hylton, Tim Peters’ın savunusu için güvensizlik oylaması girişimine katılabilir gibi
      https://news.ycombinator.com/item?id=41314393
    • Böyle easter egg’ler görmek her zaman harika. Eskisi kadar yaygın olmamaları üzücü
  • Import hook’larıyla şakalaşmanın işten atılmanın en yaratıcı yolu olduğunu sanıyordum ama bu safça bir düşünceymiş. Codec regex’i yüzünden μtf8 gibi şeylerle düzgün trollük yapamayacak olmak üzücü; artık import hook, önişlemci ve sys.settrace kullanıp tüm fonksiyonları hemen önce çağrılan fonksiyona monkey patch’lemek ve her 17 dakikada bir stdout ile stderr’i yer değiştirmekten başka çare yok gibi

    • İyi bir dile yakışır şekilde süslü parantez kullanımını da zorunlu kılmak gerekir
  • Python’ın önişlemci hook’unu bilerek açıkta bırakmamasının iyi nedenleri var ve aklı başında yetişkinlerin uzak durması gerektiğini düşündüğüm bir tarafı da var
    Ama öte yandan aklı başında yetişkinlerle işim olsun istemem. Gerçekten çok eğlenceli şeyler yapılabilir gibi

    • Python’ın “rızası olan yetişkinler(consenting adults)” için bir dil olduğu yönünde bir felsefesi var; bu yüzden public/private görünürlük belirteçleri yok ve muhtemelen her tür metaprogramlama büyüsünü de açık bırakıyor. “Aklı başında yetişkinler” konusunda endişe edilseydi bu tasarım kararları biraz tuhaf olurdu :)
    • İnsanların bunu kolay ve bariz bir şekilde yapmasını engellerseniz, daha kötü ve hacky yöntemlerle deneyeceklerdir
  • Bu kullanışlı ve gerçekten faydalı görünüyor. Saçma import hack’leri yaparken genelde modülü import edip ast modülüyle kodu yeniden yazar, exec eder ve araya exit() koyardım; önişlemci olursa kullanımı çok daha iyi olur
    Tüm dict’ler sıralamayı garanti etmeden önce, ast yeniden yazımıyla liste literal’lerini ordered dict çağrılarına çevirmek için kullanıyordum ve gerçekten işe yarıyordu
    Python’ın esnekliğini seviyorum. Yaptığım en lanetli şey string’leri yerinde değiştirmekti; sonunda mmap’i bile kötüye kullanıp kendi kendini değiştiren script yazmaya kadar gittim. Şimdi producer olarak bir Lisp yorumlayıcısı yazmam gerekiyormuş gibi hissediyorum

    • “String’leri yerinde değiştirmek” mi? String’ler değişmezdir. ctypes gibi bir şeyle doğrudan bellek konumuna mı yazdın?
  • Şimdiye kadar bulduğum en iyi kullanım alanı, JSX’ten ilham alan pyxl: https://github.com/dropbox/pyxl
    Şöyle kod yazılabiliyor
    # coding: pyxl
    print Hello World!

  • Bunun Python 2’den 3’e geçişi daha iyi yönetmek için kullanılıp kullanılamayacağını merak ediyorum. Örneğin # coding: six.python2 Python 2 kodunu geçerli Python 3 koduna uyarlasa ya da # coding: six.python3 Python 3 kodunu Python 2’de çalışacak şekilde dönüştürse
    b"..." veya u"..." öneklerini eklemek ya da kaldırmak da mümkün olabilirdi

    • Yardımcı olabilirdi ama yardımcı olduğu kısım kolay olan kısım. Python 2’den 3’e geçişteki zorluk çalışma zamanı davranış değişiklikleriydi. Python 2’de ASCII içeren Unicode ile normal string aynı string gibi davranır, dict anahtarı olarak kullanıldığında aynı öğeyi işaret ederdi; Python 3’te ise aynı ASCII içeriğe sahip bytes ve str, aynı dict içinde farklı öğeleri işaret eder
      Daha zor değişiklikler de var. .keys() ve .values() gibi birçok yerleşik özellik Python 2’de liste döndürürken Python 3’te iterator döndürür. six yardımcıları veya başka geçici çözümlerle kodu güvenli şekilde çevirmeye çalışınca kod çok laf kalabalığına dönüşüyor. Çünkü çoğu şey yalnızca bir kez kullanılıyor ama bazen iki kez kullanıldığı durumlar oluyor
      Import sırasında kodu yeniden yazabilen bir araç varsa, bence dönüştürülmüş kodu commit edip kademeli olarak temizlemek daha iyi. Zor kısım, str ile bytes gibi uzaktaki kodları bile etkileyen davranış değişiklikleri
  • Bu coding hook stratejisi ile oluşan bağımlılığı pip freeze veya uv yakalıyor mu?
    Yakalamıyorsa eğlenceli zamanlar yaşanacak demektir :). Birisi böyle bir şey koyduysa, başka hangi tuzakların olacağı neredeyse garanti; o ejderhalarla savaşmaktansa kütüphaneyi yeniden yazmak daha kolay gibi

  • Sözde kod Python yapıp bunu LLM’in çözmesini sağlamak epey komik olurdu. Elbette korkunç olur ama eğlenceli de olur