Le problème est que vous avez appliqué à une classe un décorateur conçu pour les fonctions. Le résultat n'est pas une classe, mais une fonction qui englobe un appel à la classe. Cela pose un certain nombre de problèmes (par exemple, comme l'a fait remarquer Aran-Fey dans les commentaires, vous ne pouvez pas utiliser le décorateur pour les fonctions). isinstance(feat, mystery)
parce que mystery
).
Mais le problème particulier qui vous préoccupe est que vous ne pouvez pas récupérer les instances de classes inaccessibles.
En fait, c'est en gros ce que vous dit le message d'erreur :
PicklingError: Can't pickle <class '__main__.mystery'>: it's not the
same object as __main__.mystery
Votre feat
pense que son type est __main__.mystery
mais ce n'est pas du tout un type, c'est la fonction renvoyée par le décorateur qui englobe ce type.
Le moyen le plus simple de résoudre ce problème serait de trouver un décorateur de classe qui fasse ce que vous voulez. Il pourrait s'appeler quelque chose comme flyweight
au lieu de memoize
mais je suis sûr que de nombreux exemples existent.
Mais vous pouvez construire une classe légère en mémorisant simplement le constructeur, au lieu de mémoriser la classe :
class mystery:
@funcy.memoize
def __new__(cls, num):
return super().__new__(cls)
def __init__(self, num):
self.num = num
bien que vous souhaitiez probablement déplacer l'initialisation dans le constructeur dans ce cas. Sinon, appeler mystery(1)
et ensuite mystery(1)
retournera le même objet que précédemment, mais le réinitialisera également avec self.num = 1
ce qui est au mieux un gaspillage, et au pire une erreur. Donc :
class mystery:
@funcy.memoize
def __new__(cls, num):
self = super().__new__(cls)
self.num = num
return self
Et maintenant :
>>> feat = mystery(1)
>>> feat
<__main__.mystery at 0x10eeb1278>
>>> mystery(2)
<__main__.mystery at 0x10eeb2c18>
>>> mystery(1)
<__main__.mystery at 0x10eeb1278>
Et, parce que le type de feat
est maintenant une classe qui est accessible sous le nom de module-global mystery
, pickle
n'auront aucun problème avec ça :
>>> pickle.dumps(feat)
b'\x80\x03c__main__\nmystery\nq\x00)\x81q\x01}q\x02X\x03\x00\x00\x00numq\x03K\x01sb.'
Vous faire Il faut encore réfléchir à la façon dont cette classe devrait jouer avec le décapage. En particulier, voulez-vous que le dépicklage passe par le cache ? Par défaut, ce n'est pas le cas :
>>> pickle.loads(pickle.dumps(feat)) is feat
False
Ce qui se passe, c'est que l'on utilise l'option par défaut __reduce_ex__
pour le décapage, qui fait par défaut l'équivalent de (seulement légèrement simplifié) :
result = object.__new__(__main__.mystery)
result.__dict__.update({'num': 1})
Si vous voulez qu'il passe par le cache, la solution la plus simple est la suivante :
class mystery:
@funcy.memoize
def __new__(cls, num):
self = super().__new__(cls)
self.num = num
return self
def __reduce__(self):
return (type(self), (self.num,))
Si vous prévoyez de faire cela souvent, vous pouvez penser à écrire votre propre décorateur de classe :
def memoclass(cls):
@funcy.memoize
def __new__(cls, *args, **kwargs):
return super(cls, cls).__new__(cls)
cls.__new__ = __new__
return cls
Mais ça :
- est plutôt moche,
- ne fonctionne qu'avec les classes qui n'ont pas besoin de passer des arguments de constructeur à une classe de base,
- ne fonctionne qu'avec les classes qui n'ont pas d'attribut
__init__
(ou, au moins, qui ont une idempotente et rapide __init__
qu'il est inoffensif d'appeler à plusieurs reprises),
- ne fournit pas un moyen facile d'accrocher le décapage, et
- ne documente ni ne teste aucune de ces restrictions.
Donc, je pense que c'est mieux d'être explicite et de mémoriser le __new__
ou écrire (ou trouver) quelque chose de beaucoup plus sophistiqué qui fait l'introspection nécessaire pour rendre la mémorisation d'une classe de cette manière totalement générale. (Ou bien, alternativement, écrire une méthode qui ne fonctionne qu'avec un ensemble restreint de classes - par exemple, une méthode @memodataclass
c'est juste comme @dataclass
mais avec un constructeur mémorisé serait beaucoup plus facile qu'un constructeur entièrement général @memoclass
.)