Pickle——基于栈的编程语⾔
原⽂:
译⽂:
Python的pickle模块是个相当⽅便的序列化数据的⽅法。但它究竟是如何运⾏的,对于很多⼈来说⾮常神秘。实际上它很简单。pickle的输出结果其实是⼀段可以⽣成Python数据结构的程序代码。⼀门功能有限的基于栈的语⾔可以拿来写这些代码。这⾥说的功能有限,但仍然可以写类似for循环和if判断等语句。并且学起来还蛮带感的。
这篇⽂章⾥会⽤下⾯这个简单的解释器来从pickle对象中读取数据。把下⾯的代码拷到本地⽂件中:
import code
import pickle
import sys
sys.ps1 = "pik> "
sys.ps2 = "...> "
banner = "Pik -- The stupid pickle loader.\nPress Ctrl-D to quit."
class PikConsole(code.InteractiveConsole):
def runsource(self, source, filename=""):
if dswith(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)
然后⽤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]
sqrt是什么的缩写也可以预先定义⼀个函数来做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)
不过还是收到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数据。欢迎各位留⾔评论。
标签: ,

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。