pickle: comment décaper une fonction?
Dans un article que j'ai publié hier, j'ai trouvé accidentellement que la modification __qualname__d'une fonction avait un effet inattendu sur pickle. En exécutant plus de tests, j'ai constaté que lors du décapage d'une fonction, picklene fonctionne pas comme je le pensais, et la modification __qualname__de la fonction a un effet réel sur le picklecomportement.
Les extraits ci-dessous sont des tests que j'ai exécutés,
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)
Comme vous pouvez le voir en exécutant les extraits, les deux hahahaet abcabcne peuvent pas être picklés à cause de l'exception:
Can't pickle <function sdfsdf at 0x7fda36dc5f28>: attribute lookup sdfsdf on __main__ failed.
Je suis vraiment confus par cette exception,
Que
picklerecherche quand il décape une fonction? Bien que le__qualname__of aithahahaété changé en 'sdfsdf', la fonctionhahahaainsi que sa copieabcabcsont toujours appelables dans la session (comme elles le sontdir(sys.modules['__main__'])), alors pourquoipicklene peut-il pas les décaper?Quel est l'effet réel de la modification de la
__qualname__fonction d'une fonction? Je comprends qu'en changeant le__qualname__dehahahaen 'sdfsdf' ne rendra passdfsdfappelable, car il n'apparaîtra pas dansdir(sys.modules['__main__']). Cependant, comme vous pouvez le voir en exécutant les extraits__qualname__dehahahacode , après avoir changé le de en 'sdfsdf', l'objethahahaainsi que sa copieabcabcont changé en quelque chose comme<function sdfsdf at 'some_address'>. Quelle est la différence entre les objets danssys.modules['__main__']et<function sdfsdf at 'some_address'>?
Réponses
Le décapage des objets de fonction est défini dans la save_globalméthode de pickle.py :
Tout d'abord, le nom de la fonction est récupéré via __qualname__:
name = getattr(obj, '__qualname__', None)
Ensuite, après avoir récupéré le module, il est réimporté:
__import__(module_name, level=0)
module = sys.modules[module_name]
Ce fraîchement importé moduleest ensuite utilisé pour rechercher la fonction en tant qu'attribut:
obj2, parent = _getattribute(module, name)
obj2serait maintenant une nouvelle copie de la fonction, mais comme sdfsdfn'existe pas dans ce module, le décapage échoue ici.
Vous pouvez faire fonctionner cela, mais vous devez être cohérent:
>>> 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.'