Decypher avec moi que obscurcie MultiplierFactory
-
18-09-2019 - |
Question
Cette semaine sur comp.lang.python, un "intéressant" morceau de code a été publié par Steven d'Aprano comme une réponse à une question blague aux devoirs. Ici, il est:
class MultiplierFactory(object):
def __init__(self, factor=1):
self.__factor = factor
@property
def factor(self):
return getattr(self, '_%s__factor' % self.__class__.__name__)
def __call__(self, factor=None):
if not factor is not None is True:
factor = self.factor
class Multiplier(object):
def __init__(self, factor=None):
self.__factor = factor
@property
def factor(self):
return getattr(self,
'_%s__factor' % self.__class__.__name__)
def __call__(self, n):
return self.factor*n
Multiplier.__init__.im_func.func_defaults = (factor,)
return Multiplier(factor)
twice = MultiplierFactory(2)()
Nous savons que twice
est équivalent à la réponse:
def twice(x):
return 2*x
Des noms Multiplier
et MultiplierFactory
nous avons une idée de ce qui est le code à faire, mais nous ne sommes pas sûrs des internes exacts. Simplifions premier.
Logic
if not factor is not None is True:
factor = self.factor
not factor is not None is True
est équivalente à not factor is not None
, qui est également factor is None
. Résultat:
if factor is None:
factor = self.factor
Jusqu'à présent, c'était facile:)
Attribut accès
Un autre point intéressant est le accesseur factor
curieux.
def factor(self):
return getattr(self, '_%s__factor' % self.__class__.__name__)
Lors de l'initialisation de MultiplierFactory
, self.__factor
est réglé. Mais plus tard, le code accède self.factor
.
Il semble donc que:
getattr(self, '_%s__factor' % self.__class__.__name__)
fait exactement "self.__factor
".
Peut-on accéder à toujours les attributs de cette façon?
def mygetattr(self, attr):
return getattr(self, '_%s%s' % (self.__class__.__name__, attr))
La modification dynamique des signatures de fonction
Quoi qu'il en soit, à ce point, voici le code simplifié:
class MultiplierFactory(object):
def __init__(self, factor=1):
self.factor = factor
def __call__(self, factor=None):
if factor is None:
factor = self.factor
class Multiplier(object):
def __init__(self, factor=None):
self.factor = factor
def __call__(self, n):
return self.factor*n
Multiplier.__init__.im_func.func_defaults = (factor,)
return Multiplier(factor)
twice = MultiplierFactory(2)()
Le code est maintenant presque propre. La seule ligne déroutante, peut-être, serait:
Multiplier.__init__.im_func.func_defaults = (factor,)
Qu'est-ce qui est là-dedans? Je regardais le datamodel doc , et a constaté que func_defaults
était « un tuple contenant des arguments par défaut pour les arguments qui ont par défaut, ou aucun si aucun argument ont une valeur par défaut ». Est-ce que nous venons de changer la valeur par défaut pour factor
argument __init__
ici code résultant serait alors:
class MultiplierFactory(object):
def __init__(self, factor=1):
self.factor = factor
def __call__(self, factor=None):
if factor is None:
factor = self.factor
class Multiplier(object):
def __init__(self, innerfactor=factor):
self.factor = innerfactor
def __call__(self, n):
return self.factor*n
return Multiplier(factor)
twice = MultiplierFactory(2)()
Ce qui signifie que la définition dynamique de la valeur par défaut était juste le bruit inutile, puisque Multiplier
est jamais appelé sans paramètre par défaut, droite
Et nous pourrions simplifier probablement à:
class MultiplierFactory(object):
def __init__(self, factor=1):
self.factor = factor
def __call__(self, factor=None):
if factor is None:
factor = self.factor
def my_multiplier(n):
return factor*n
return my_multiplier
twice = MultiplierFactory(2)() # similar to MultiplierFactory()(2)
Correct?
Et pour ceux qui se presser à "ce n'est pas une vraie question" ... lire à nouveau, mes questions sont en gras + italique
La solution
Q1. Peut-on accéder à toujours les attributs de cette façon?
A: Non, c'est que les attributs qui commencent avec double underscores. Ils se brouillées de cette façon, pour empêcher l'accès accidentel / remplaçant l'extérieur de la classe.
Q2: changeons-nous simplement la valeur par défaut pour argument facteur __init__
ici
A: Oui.
Q2: droite
? Droit.