DeCypher conmigo en que ofuscado MultiplierFactory
-
18-09-2019 - |
Pregunta
Esta semana en comp.lang.python, una pieza "interesante" del código fue publicado por Steven D'Aprano como una respuesta a una pregunta broma tarea. Aquí 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)()
Sabemos que twice
es un equivalente a la respuesta:
def twice(x):
return 2*x
A partir de los nombres Multiplier
y MultiplierFactory
nos hacemos una idea de lo que está haciendo el código, pero no estamos seguros de las partes internas exactas. Vamos a simplificar primero.
Lógica
if not factor is not None is True:
factor = self.factor
not factor is not None is True
es equivalente a not factor is not None
, que también se factor is None
. Resultado:
if factor is None:
factor = self.factor
Hasta ahora, eso fue fácil:)
Atributo de acceso
Otro punto interesante es la curiosidad de acceso factor
.
def factor(self):
return getattr(self, '_%s__factor' % self.__class__.__name__)
Durante la inicialización de MultiplierFactory
, se establece self.__factor
. Pero más tarde, el código de acceso a self.factor
.
A continuación, parece que:
getattr(self, '_%s__factor' % self.__class__.__name__)
está haciendo exactamente "self.__factor
".
¿Se puede acceder a los atributos siempre de esta manera?
def mygetattr(self, attr):
return getattr(self, '_%s%s' % (self.__class__.__name__, attr))
cambiar dinámicamente las firmas de función
De todos modos, en este momento, aquí está el código simplificado:
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)()
El código es casi limpia ahora. La línea única desconcertante, tal vez, sería la siguiente:
Multiplier.__init__.im_func.func_defaults = (factor,)
¿Qué hay ahí? Miré el modelo de datos doc , y se encontró que func_defaults
era " una tupla que contiene valores de los argumentos predeterminados para los argumentos que tienen valores por defecto, o None si no hay argumentos tienen un valor por defecto ". ¿Estamos simplemente cambiando el valor predeterminado de factor
argumento a __init__
aquí código resultante sería entonces:?
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)()
Lo que significa que configurar dinámicamente el valor predeterminado era sólo ruido inútil, ya que Multiplier
nunca es llamado sin un parámetro por defecto, derecho
Y que probablemente podría simplificar a:
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)
correcta?
Y para aquellos corriendo a "esto no es una cuestión real" ... leer más, mis preguntas son en negrita cursiva +
Solución
Q1. Siempre podemos acceder a los atributos de esta manera?
R: No. Es sólo aquellos atributos que comienzan con subrayados dobles. Ellos se ofuscado de esa manera, para evitar el acceso accidental / anulando desde fuera de la clase.
P2: ¿Estamos simplemente cambiando el valor por defecto para el argumento factor en __init__
aquí
R: Sí.
P2: la derecha
?derecho.