Pickle——基于棧的編程語言
Python的pickle模塊是個相當方便的序列化數據的方法。但它究竟是如何運行的,對于很多人來說非常神秘。實際上它很簡單。pickle的輸出結果其實是一段可以生成Python數據結構的程序代碼。一門功能有限的基于棧的語言可以拿來寫這些代碼。這里說的功能有限,但仍然可以寫類似for循環和if判斷等語句。并且學起來還蠻帶感的。
這篇文章里會用下面這個簡單的解釋器來從pickle對象中讀取數據。把下面的代碼拷到本地文件中:
#!/usr/bin/python import code import pickle import syssys.ps1 = "pik> " sys.ps2 = "...> " banner = "Pik -- The stupid pickle loader.\nPress Ctrl-D to quit."
class PikConsole(code.InteractiveConsole): def runsource(self, source, filename="<stdin>"): if not source.endswith(pickle.STOP): return True # more input is needed try: print repr(pickle.loads(source)) except: self.showsyntaxerror(filename) return False
pik = PikConsole() pik.interact(banner)</pre>
然后用Python啟動:
$ python pik.py Pik -- The stupid pickle loader. Press Ctrl-D to quit. pik>到目前還沒什么神奇的。接下來,最容易創建的對象是那些空的集合,比如說一個空列表:
pik> ]. []創建空字典和空元組也是類似的:
pik> }. {} pik> ). ()切記每段pickle數據流都是用符號.來結束的。這個操作符將棧頂對象彈棧并返回之。假如你輸入一串整數,然后用.結束數據流。最后的結果將是你最后輸入的內容:
pik> I1 ...> I2 ...> I3 ...> . 3誠如所見,一個整數用符號I開頭,換行符結尾來表示。字符串和浮點數的表示方法也是類似的:
pik> F1.0 ...> . 1.0 pik> S'abc' ...> . 'abc' pik> Vabc ...> . u'abc'有了上面的基礎,可以來點復雜的例子了——創建一個復合對象。之后你會看到,Python里面會大量用到元組,所以先來個元組的例子:
pik> (I1 ...> S'abc' ...> F2.0 ...> t. (1, 'abc', 2.0)例子里面有兩個新符號,(和t。(只是一個標識符,它是棧中的一個對象,來告知元組構造器——t——什么時候終止。元組構造器不停的彈棧,知道到達標識符。然后它用彈出來的這些對象創建一個元組并將之壓棧。你可以用多個標識符來創建嵌套的元組:
pik> (I1 ...> (I2 ...> I3 ...> tt. (1, (2, 3))可以用同樣的方法來創建列表或是字典:
pik> (I0 ...> I1 ...> I2 ...> l. [0, 1, 2] pik> (S'red' ...> I00 ...> S'blue' ...> I01 ...> d. {'blue': True, 'red': False}唯一的區別是字典中的元素都是兩個一組的鍵值對。順便需要注意True和False是用類似整數1和0的符號來表示的,不過前面補了個0。
然后還可以創建嵌套的列表和字典:
pik> ((I1 ...> I2 ...> t(I3 ...> I4 ...> ld. {(1, 2): [3, 4]}還有另外一種創建集合的方法。不試用標識符來表示對象的邊界,而是創建一個空集合然后往里添加對象:
pik> ]I0 ...> aI1 ...> aI2 ...> a. [0, 1, 2]符號a的意思是append。它將一個對象和一個列表彈棧;將對象添加至列表中;最后將列表壓棧。下面演示了用這種方法創建嵌套列表:
pik> ]I0 ...> a]I1 ...> aI2 ...> aa. [0, [1, 2]]如果嫌代碼還是不夠糾結,可以看這個:
pik> }S'red' ...> I1 ...> sS'blue' ...> I2 ...> s. {'blue': 2, 'red': 1}設置字典中的對象用的是符號s而不是a。并且它需要一個鍵值對做參數。
還可以創建遞歸的數據結構:
pik> (Vzoom ...> lp0 ...> g0 ...> a. [u'zoom', [...]]技巧是用“寄存器”(在pickle中叫memo)。符號p(“put”的縮寫)拷貝棧頂對象到memmo中。這里用0來做這個memo的名字,不過可以用隨便別的來稱呼。符號g來從memo取回對象并壓到棧頂。
現在有個小問題,如何創建集合(set)呢?pickle里沒有集合的表示法。唯一的做法就是用內建函數set()來從列表或者元組上創建集合了:
pik> c__builtin__ ...> set ...> ((S'a' ...> S'a' ...> S'b' ...> ltR. set(['a', 'b'])符號c從模塊中取出對象放在棧頂。reduce通常的含義是對某個元組遍地調用某個函數,這里符號R有類似的語意,會從棧上彈出一個元組和一個函數,然后將reduce的結果壓棧。所以上面的例子可以翻譯成下面的Python代碼:
>>> import __builtin__ >>> apply(__builtin__.set, (['a', 'a', 'b'],))或者用星號語法:
>>> __builtin__.set(*(['a', 'a', 'b'],))還可以這樣:
>>> set(['a', 'a', 'b'])或者直接用Python3的語法:
>>> {'a', 'a', 'b'}符號t和R運行我們執行任意標準庫的代碼。所以一定要注意絕對不要從不信任的來源load pickle數據。惡意攻擊者可以很輕松的將一些指令混入數據中并刪除你硬盤上的數據。不過同時你也可以拿這項功能來做些奇葩的事情,比如啟動系統里的時鐘應用:
pik> cos ...> system ...> (S'xclock' ...> tR.雖然這門受限的語言沒直接支持循環,不過這仍然不能組織地球人來做循環:
pik> c__builtin__ ...> map ...> (cmath ...> sqrt ...> c__builtin__ ...> range ...> (I1 ...> I10 ...> tRtR. [1.0, 1.4142135623730951, 1.7320508075688772, 2.0, 2.2360679774997898, 2.4494897427831779, 2.6457513110645907, 2.8284271247461903, 3.0]也可以預先定義一個函數來做if判斷:
def my_if(cond, then_val, else_val): if cond: return then_val else: return else_val簡單的用例:
>>> my_if(True, 1, 0) 1 >>> my_if(False, 1, 0) 0不過還是收到Python本身最大遞歸層數的限制:
>>> def factorial(n): ... return my_if(n == 1, ... 1, n * factorial(n - 1)) ... >>> factorial(2) RuntimeError: maximum recursion depth exceeded in cmp不過一般情況下也用不著創建一個遞歸的pickle數據流,除非你想參加奇葩代碼大賽。
關于這門簡單的基于棧的編程語言大概說的就是這么多了,剩下那點沒說的自己看看也能搞定。看看pickle模塊的源碼就行。順便看一眼pickletools模塊,可以拿來反編譯pickle數據。歡迎各位留言評論。
原文:Pickle: An interesting stack language
來自:http://aisk.me/pickle-an-interesting-stack-language/