pickle: como ele pickle uma função?

Sep 28 2020

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,

  1. O que pickleprocura quando decompõe uma função? Embora o __qualname__de tenha hahahasido alterado para 'sdfsdf', a função hahaha, bem como sua cópia, abcabcainda pode ser chamada na sessão (como estão dir(sys.modules['__main__'])), então por pickleque não pode selecioná- los?

  2. Qual é o efeito real de alterar o __qualname__de uma função? Eu entendo que alterar o __qualname__de hahahapara 'sdfsdf' não tornará sdfsdfexigível, pois não aparecerá em dir(sys.modules['__main__']). No entanto, como você pode ver executando os snippets, após alterar o __qualname__de hahahapara 'sdfsdf', o objeto hahaha, bem como sua cópia, abcabcfoi alterado para algo semelhante <function sdfsdf at 'some_address'>. Qual é a diferença entre os objetos em sys.modules['__main__']e <function sdfsdf at 'some_address'>?

Respostas

5 L3viathan Sep 28 2020 at 14:05

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.'