pickle: como ele pickle uma função?
Em um post que postei ontem, descobri acidentalmente que alterar o __qualname__de uma função tem um efeito inesperado em pickle. Ao executar mais testes, descobri que, ao fazer a decapagem de uma função, picklenão funciona da maneira que eu pensava e que alterar __qualname__o funcionamento da função tem um efeito real em como picklese comporta.
Os snippets abaixo são testes que fiz,
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)
Como você pode ver executando os snippets, ambos hahahae abcabcnão podem ser conservados por causa da exceção:
Can't pickle <function sdfsdf at 0x7fda36dc5f28>: attribute lookup sdfsdf on __main__ failed.
Estou muito confuso com esta exceção,
O que
pickleprocura quando decompõe uma função? Embora o__qualname__de tenhahahahasido alterado para 'sdfsdf', a funçãohahaha, bem como sua cópia,abcabcainda pode ser chamada na sessão (como estãodir(sys.modules['__main__'])), então porpickleque não pode selecioná- los?Qual é o efeito real de alterar o
__qualname__de uma função? Eu entendo que alterar o__qualname__dehahahapara 'sdfsdf' não tornarásdfsdfexigível, pois não aparecerá emdir(sys.modules['__main__']). No entanto, como você pode ver executando os snippets, após alterar o__qualname__dehahahapara 'sdfsdf', o objetohahaha, bem como sua cópia,abcabcfoi alterado para algo semelhante<function sdfsdf at 'some_address'>. Qual é a diferença entre os objetos emsys.modules['__main__']e<function sdfsdf at 'some_address'>?
Respostas
A decapagem de objetos de função é definida no save_globalmétodo em pickle.py :
Primeiro, o nome da função é recuperado via __qualname__:
name = getattr(obj, '__qualname__', None)
Posteriormente, após recuperar o módulo, ele é reimportado:
__import__(module_name, level=0)
module = sys.modules[module_name]
Este recém importado moduleé então usado para procurar a função como um atributo:
obj2, parent = _getattribute(module, name)
obj2agora seria uma nova cópia da função, mas como sdfsdfnão existe neste módulo, a decapagem falha aqui.
Você pode fazer isso funcionar, mas deve ser consistente:
>>> 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.'