摘要:我可以明確告訴你這不是,但它可以用解釋器運行。這種黑魔法,還要從說起。提案者設想使用一種特殊的文件首注釋,用于指定代碼的編碼。暴露了一個函數,用于注冊自定義編碼。所謂的黑魔法其實并不神秘,照貓畫虎定義好相應的接口即可。
寫在前面首發于我的博客,轉載請注明出處
引子本文為科普文
本文中的例子在 Ubuntu 14.04 / Python 2.7.11 下運行成功,Python 3+ 的接口有些許不同,需要讀者自行轉換
先看一段代碼:
example.py:
# -*- coding=yi -*- 從 math 導入 sin, pi 打印 "sin(pi) =", sin(pi)
這是什么?!是 Python 嗎?可以運行嗎?——想必你會問。
我可以明確告訴你:這不是 Python,但它可以用 Python 解釋器運行。當然,如果你愿意,可以叫它 “Yython” (易語言 + Python)。
怎么做到的?也許你已經注意到第一行的奇怪注釋——沒錯,秘密全在這里。
這種黑魔法,還要從 PEP 263 說起。
古老的 PEP 263我相信 99% 的中國 Python 開發者都曾經為一個問題而頭疼——字符編碼。那是每個初學者的夢靨。
還記得那天嗎?當你試圖用代碼向它示好:
print "你好"
它卻給你當頭一棒:
SyntaxError: Non-ASCII character "xe4" in file chi.py on line 1, but no encoding declared
【一臉懵逼】
于是,你上網查找解決方案。很快,你便有了答案:
# -*- coding=utf-8 -*- print "你好"
其中第一行的注釋用于指定解析該文件的編碼。
這個特新來自 2001 年的 PEP 263 -- Defining Python Source Code Encodings,它的出現是為了解決一個反響廣泛的問題:
In Python 2.1, Unicode literals can only be written using the Latin-1 based encoding "unicode-escape". This makes the programming environment rather unfriendly to Python users who live and work in non-Latin-1 locales such as many of the Asian countries. Programmers can write their 8-bit strings using the favorite encoding, but are bound to the "unicode-escape" encoding for Unicode literals.
Python 默認用 ASCII 編碼解析文件,給 15 年前的非英文世界開發者造成了不小的困擾——看來 Guido 老爹有些個人主義,設計時只考慮到了英文世界。
提案者設想:使用一種特殊的文件首注釋,用于指定代碼的編碼。這個注釋的正則原型是這樣的:
^[ v]*#.*?coding[:=][ ]*([-_.a-zA-Z0-9]+)
也就是說 # -*- coding=utf-8 -*- 并不是唯一的寫法,只是 Emacs 推薦寫法而已。諸如 # coding=utf-8、# encoding: utf-8 都是合法的——因此你不必驚訝于他人編碼聲明與你不同。
正則的捕獲組 ([-_.a-zA-Z0-9]+) 將會被用作查找編碼的名稱,查找到的編碼信息會被用于解碼文件。也就是說,import example 背后其實相當于有如下轉換過程:
with open("example.py", "r") as f: content = f.read() encoding = extract_encoding_info(content) # 解析首注釋 exec(content.decode(encoding))
問題其實又回到我們常用的 str.encode 和 str.decode 上來了。
可 Python 怎么這么強大?!幾乎所有編碼它都認得!這是怎么做到的?是標準庫?還是內置于解釋器中?
一切,都是 codecs 模塊在起作用。
codecscodecs 算是較為冷門的一個模塊,更為常用的是 str 的 encode/decode 的方法——但它們本質都是對 codecs 的調用。
打開 /path/to/your/python/lib/encodings/ 目錄,你會發現有許多以編碼名稱命名的 .py 文件,如 utf_8.py、latin_1.py。這些都是系統預定義的編碼系統,實現了應對各種編碼的邏輯——也就是說:編碼系統其實也是普通的模塊。
除了內置的編碼,用戶也可以 自行定義編碼系統。codecs 暴露了一個 register 函數,用于注冊自定義編碼。register 簽名如下:
codecs.register(search_function)
Register a codec search function. Search functions are expected to take one argument, the encoding name in all lower case letters, and return a CodecInfo object having the following attributes:name: The name of the encoding;
encode: The stateless encoding function;
decode: The stateless decoding function;
incrementalencoder: An incremental encoder class or factory function;
incrementaldecoder: An incremental decoder class or factory function;
streamwriter: A stream writer class or factory function;
streamreader: A stream reader class or factory function.
encode 和 decode 是無狀態的編碼/解碼的函數,簡單說就是:前一個被編解碼的字符串與后一個沒有關聯。如果你想用 codecs 系統進行語法樹解析,解析邏輯最好不要寫在這里,因為代碼的連續性無法被保證;incremental* 則是有狀態的解析類,能彌補 encode、decode 的不足;stream* 是流相關的解析類,行為通常與 encode/decode 相同。
關于這六個對象的具體寫法,可以參考 /path/to/your/python/lib/encodings/rot_13.py,該文件實現了一個簡單的密碼系統。
那么,是時候揭開真相了。
所謂的 “Yython”黑魔法其實并不神秘,照貓畫虎定義好相應的接口即可。作為例子,這里只處理用到的關鍵字:
yi.py:
# encoding=utf8 import codecs yi_map = { u"從": "from", u"導入": "import", u"打印": "print" } def encode(input): for key, value in yi_map.items(): input = input.replace(value, key) return input.encode("utf8") def decode(input): input = input.decode("utf8") for key, value in yi_map.items(): input = input.replace(key, value) return input class Codec(codecs.Codec): def encode(self, input, errors="strict"): input = encode(input) return (input, len(input)) def decode(self, input, errors="strict"): input = decode(input) return (input, len(input)) class IncrementalEncoder(codecs.IncrementalEncoder): def encode(self, input, final=False): return encode(input) class IncrementalDecoder(codecs.IncrementalDecoder): def decode(self, input, final=False): return decode(input) class StreamWriter(Codec, codecs.StreamWriter): pass class StreamReader(Codec, codecs.StreamReader): pass def register_entry(encoding): return codecs.CodecInfo( name="yi", encode=Codec().encode, decode=Codec().decode, incrementalencoder=IncrementalEncoder, incrementaldecoder=IncrementalDecoder, streamwriter=StreamWriter, streamreader=StreamReader ) if encoding == "yi" else None
在命令行里注冊一下,就可以看到激動人心的結果了:
>>> import codecs, yi >>> codecs.register(yi.register_entry) >>> import example sin(pi) = 1.22464679915e-16結語
有時,對習以為常的東西深入了解一下,說不定會有驚人的發現。
Referencescodecs - Codec registry and base classes
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/38074.html
摘要:幸而,提供了造物主的接口這便是,或者稱為元類。接下來我們將通過一個栗子感受的黑魔法,不過在此之前,我們要先了解一個語法糖。此外,在一些小型的庫中,也有元類的身影。 首發于 我的博客 轉載請注明出處 接觸過 Django 的同學都應該十分熟悉它的 ORM 系統。對于 python 新手而言,這是一項幾乎可以被稱作黑科技的特性:只要你在models.py中隨便定義一個Model的子類,Dj...
摘要:關于解決亂碼問題的終極解決方案有個特別好玩的現象,當我們為了編碼頭疼的時候,幾乎搜索到所有的文章都會先發一通牢騷。另外,關于的亂碼問題,又是一個新的較長篇章。 關于解決Python亂碼問題的終極解決方案 (TL;DR) showImg(https://segmentfault.com/img/remote/1460000013229494?w=809&h=184); 有個特別好玩的現象...
摘要:主程序通過喚起子程序并傳入數據,子程序處理完后,用將自己掛起,并返回主程序,如此交替進行。通過輪詢或是等事件框架,捕獲返回的事件。從消息隊列中取出記錄,恢復協程函數。然而事實上只有直接操縱的協程函數才有可能接觸到這個對象。 首發于 我的博客 轉載請注明出處 寫在前面 本文默認讀者對 Python 生成器 有一定的了解,不了解者請移步至生成器 - 廖雪峰的官方網站。 本文基于 Pyth...
摘要:如果類型轉換你還不是很了解,可以先讀下這篇來理解一下從看隱式強制轉換機制。函數可對通過編碼的字符串進行解碼。而作者封裝的也是基于這兩者來實現輸出黑魔法字符串的。同時通過,返回了一個匿名函數形成了閉包。為了達到裝逼的效果。 寫在最前 事情的起因是這段看起來不像代碼的代碼: showImg(https://segmentfault.com/img/remote/14600000126810...
摘要:類的繼承類繼承有三種調用方式,其實是有區別的,聽我慢慢道來第一種父類方法參數直接調用第二種方法參數直接調用在誰的類下調用,就找此類對應的下一個就是要繼承的第三種方法參數找類名對應的的下一個,就是繼承的,一般寫本身的類名上下文管理器上下文管理 類的繼承 類繼承有三種調用方式,其實是 有區別 的,聽我慢慢道來 class A: def say(self, name): ...
閱讀 1074·2021-11-19 09:40
閱讀 2213·2021-11-15 18:00
閱讀 1267·2021-10-18 13:34
閱讀 2248·2021-09-02 15:40
閱讀 1533·2019-08-30 14:01
閱讀 1113·2019-08-30 11:11
閱讀 2482·2019-08-29 15:26
閱讀 722·2019-08-29 14:15