Decipador comigo aquele multiplierfactory ofuscado
-
18-09-2019 - |
Pergunta
Esta semana em comp.lang.python, uma peça de código "interessante" foi publicado Por Steven D'Aprano como uma piada, responde a uma pergunta de lição de casa. Aqui 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)()
Nós sabemos isso twice
é um equivalente à resposta:
def twice(x):
return 2*x
Dos nomes Multiplier
e MultiplierFactory
Temos uma idéia do que o código está fazendo, mas não temos certeza dos internos exatos. Vamos simplificar primeiro.
Lógica
if not factor is not None is True:
factor = self.factor
not factor is not None is True
é equivalente a not factor is not None
, Que tambem é factor is None
. Resultado:
if factor is None:
factor = self.factor
Até agora, isso foi fácil :)
Acesso ao atributo
Outro ponto interessante é o curioso factor
acessador.
def factor(self):
return getattr(self, '_%s__factor' % self.__class__.__name__)
Durante a inicialização de MultiplierFactory
, self.__factor
está definido. Mas mais tarde, o código acessa self.factor
.
Parece então:
getattr(self, '_%s__factor' % self.__class__.__name__)
Está fazendo exatamente "self.__factor
".
Podemos sempre acessar atributos dessa maneira?
def mygetattr(self, attr):
return getattr(self, '_%s%s' % (self.__class__.__name__, attr))
Mudança dinamicamente assinaturas de funções
De qualquer forma, neste momento, aqui está o 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)()
O código está quase limpo agora. A única linha intrigante, talvez, seria:
Multiplier.__init__.im_func.func_defaults = (factor,)
O que há aí? Eu olhei para o DataModel Doc, e encontrei isso func_defaults
foi "Uma tupla contendo valores de argumento padrão para os argumentos que têm padrões, ou nenhum se nenhum argumento tiver um valor padrão". Estamos apenas mudando o valor padrão para factor
argumento em __init__
aqui? O código resultante seria então:
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)()
O que significa que definir dinamicamente o valor padrão era apenas um ruído inútil, pois Multiplier
nunca é chamado sem um parâmetro padrão, certo?
E provavelmente poderíamos simplificar para:
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)
Correto?
E para aqueles que se apressam para "esta não é uma pergunta real" ... Leia novamente, minhas perguntas estão em negrito+itálico
Solução
Q1. Podemos sempre acessar atributos dessa maneira?
R: Não. São apenas os atributos que começam com sublinhados duplos. Eles são ofuscados dessa maneira, para evitar acesso/substituição acidental de fora da classe.
Q2: Estamos apenas alterando o valor padrão do argumento fatorial em __init__
aqui?
A: Sim.
Q2: Certo?
Certo.