pickle:它如何 pickle 一个函数?
pickle: how does it pickle a function?
在我昨天发的一个post中,无意中发现改变一个函数的__qualname__
会对pickle
产生意想不到的效果。通过 运行 更多的测试,我发现当 pickle 一个函数时, pickle
并没有像我想的那样工作,改变函数的 __qualname__
对 pickle
行为举止。
下面的片段是我 运行,
的测试
import pickle
from sys import modules
# a simple function to pickle
def hahaha(): return 1
print('hahaha',hahaha,'\n')
# change the __qualname__ of function hahaha
hahaha.__qualname__ = 'sdfsdf'
print('set hahaha __qualname__ to sdfsdf',hahaha,'\n')
# make a copy of hahaha
setattr(modules['__main__'],'abcabc',hahaha)
print('create abcabc which is just hahaha',abcabc,'\n')
try:
pickle.dumps(hahaha)
except Exception as e:
print('pickle hahaha')
print(e,'\n')
try:
pickle.dumps(abcabc)
except Exception as e:
print('pickle abcabc, a copy of hahaha')
print(e,'\n')
try:
pickle.dumps(sdfsdf)
except Exception as e:
print('pickle sdfsdf')
print(e)
正如您在 运行 片段中看到的那样,hahaha
和 abcabc
都不能被 pickle 因为异常:
Can't pickle <function sdfsdf at 0x7fda36dc5f28>: attribute lookup sdfsdf on __main__ failed
.
我真的被这个异常弄糊涂了,
pickle
在 pickle 函数时寻找什么?尽管 hahaha
的 __qualname__
已更改为 'sdfsdf',但函数 hahaha
及其副本 abcabc
仍可在会话中调用(因为它们在dir(sys.modules['__main__'])
), 那为什么 pickle
不能腌制呢?
改变函数的 __qualname__
的实际效果是什么?我理解将 hahaha
的 __qualname__
更改为 'sdfsdf' 不会使 sdfsdf
可调用,因为它不会出现在 dir(sys.modules['__main__'])
中。但是,正如您在 运行 片段中看到的那样,在将 hahaha
的 __qualname__
更改为 'sdfsdf' 之后,对象 hahaha
及其副本 abcabc
已更改为类似 <function sdfsdf at 'some_address'>
的内容。 sys.modules['__main__']
和<function sdfsdf at 'some_address'>
中的对象有什么区别?
函数对象的酸洗在 save_global
method in pickle.py:
中定义
首先,通过__qualname__
检索函数名称:
name = getattr(obj, '__qualname__', None)
之后,检索模块后,重新导入:
__import__(module_name, level=0)
module = sys.modules[module_name]
这个新导入的 module
然后用于查找作为属性的函数:
obj2, parent = _getattribute(module, name)
obj2
现在将是该函数的一个新副本,但由于 sdfsdf
不存在于此模块中,因此酸洗在此失败。
你可以做到这一点,但你必须保持一致:
>>> import sys
>>> import pickle
>>> def hahaha(): return 1
>>> hahaha.__qualname__ = "sdfsdf"
>>> setattr(sys.modules["__main__"], "sdfsdf", hahaha)
>>> pickle.dumps(hahaha)
b'\x80\x04\x95\x17\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x06sdfsdf\x94\x93\x94.'
在我昨天发的一个post中,无意中发现改变一个函数的__qualname__
会对pickle
产生意想不到的效果。通过 运行 更多的测试,我发现当 pickle 一个函数时, pickle
并没有像我想的那样工作,改变函数的 __qualname__
对 pickle
行为举止。
下面的片段是我 运行,
的测试import pickle
from sys import modules
# a simple function to pickle
def hahaha(): return 1
print('hahaha',hahaha,'\n')
# change the __qualname__ of function hahaha
hahaha.__qualname__ = 'sdfsdf'
print('set hahaha __qualname__ to sdfsdf',hahaha,'\n')
# make a copy of hahaha
setattr(modules['__main__'],'abcabc',hahaha)
print('create abcabc which is just hahaha',abcabc,'\n')
try:
pickle.dumps(hahaha)
except Exception as e:
print('pickle hahaha')
print(e,'\n')
try:
pickle.dumps(abcabc)
except Exception as e:
print('pickle abcabc, a copy of hahaha')
print(e,'\n')
try:
pickle.dumps(sdfsdf)
except Exception as e:
print('pickle sdfsdf')
print(e)
正如您在 运行 片段中看到的那样,hahaha
和 abcabc
都不能被 pickle 因为异常:
Can't pickle <function sdfsdf at 0x7fda36dc5f28>: attribute lookup sdfsdf on __main__ failed
.
我真的被这个异常弄糊涂了,
pickle
在 pickle 函数时寻找什么?尽管hahaha
的__qualname__
已更改为 'sdfsdf',但函数hahaha
及其副本abcabc
仍可在会话中调用(因为它们在dir(sys.modules['__main__'])
), 那为什么pickle
不能腌制呢?改变函数的
__qualname__
的实际效果是什么?我理解将hahaha
的__qualname__
更改为 'sdfsdf' 不会使sdfsdf
可调用,因为它不会出现在dir(sys.modules['__main__'])
中。但是,正如您在 运行 片段中看到的那样,在将hahaha
的__qualname__
更改为 'sdfsdf' 之后,对象hahaha
及其副本abcabc
已更改为类似<function sdfsdf at 'some_address'>
的内容。sys.modules['__main__']
和<function sdfsdf at 'some_address'>
中的对象有什么区别?
函数对象的酸洗在 save_global
method in pickle.py:
首先,通过__qualname__
检索函数名称:
name = getattr(obj, '__qualname__', None)
之后,检索模块后,重新导入:
__import__(module_name, level=0)
module = sys.modules[module_name]
这个新导入的 module
然后用于查找作为属性的函数:
obj2, parent = _getattribute(module, name)
obj2
现在将是该函数的一个新副本,但由于 sdfsdf
不存在于此模块中,因此酸洗在此失败。
你可以做到这一点,但你必须保持一致:
>>> import sys
>>> import pickle
>>> def hahaha(): return 1
>>> hahaha.__qualname__ = "sdfsdf"
>>> setattr(sys.modules["__main__"], "sdfsdf", hahaha)
>>> pickle.dumps(hahaha)
b'\x80\x04\x95\x17\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x06sdfsdf\x94\x93\x94.'