pickle: comment décaper une fonction?

Sep 28 2020

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,

  1. Que picklerecherche quand il décape une fonction? Bien que le __qualname__of ait hahahaété changé en 'sdfsdf', la fonction hahahaainsi que sa copie abcabcsont toujours appelables dans la session (comme elles le sont dir(sys.modules['__main__'])), alors pourquoi picklene peut-il pas les décaper?

  2. Quel est l'effet réel de la modification de la __qualname__fonction d'une fonction? Je comprends qu'en changeant le __qualname__de hahahaen 'sdfsdf' ne rendra pas sdfsdfappelable, car il n'apparaîtra pas dans dir(sys.modules['__main__']). Cependant, comme vous pouvez le voir en exécutant les extraits __qualname__de hahahacode , après avoir changé le de en 'sdfsdf', l'objet hahahaainsi que sa copie abcabcont changé en quelque chose comme <function sdfsdf at 'some_address'>. Quelle est la différence entre les objets dans sys.modules['__main__']et <function sdfsdf at 'some_address'>?

Réponses

5 L3viathan Sep 28 2020 at 14:05

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