This week on comp.lang.python, an "interesting" piece of code was posted by Steven D'Aprano as a joke answer to an homework question. Here it is:
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)()
We know that twice
is an equivalent to the answer:
def twice(x):
return 2*x
From the names Multiplier
and MultiplierFactory
we get an idea of what's the code doing, but we're not sure of the exact internals. Let's simplify it first.
Logic
if not factor is not None is True:
factor = self.factor
not factor is not None is True
is equivalent to not factor is not None
, which is also factor is None
. Result:
if factor is None:
factor = self.factor
Until now, that was easy :)
Attribute access
Another interesting point is the curious factor
accessor.
def factor(self):
return getattr(self, '_%s__factor' % self.__class__.__name__)
During initialization of MultiplierFactory
, self.__factor
is set. But later on, the code accesses self.factor
.
It then seems that:
getattr(self, '_%s__factor' % self.__class__.__name__)
Is doing exactly "self.__factor
".
Can we always access attributes in this fashion?
def mygetattr(self, attr):
return getattr(self, '_%s%s' % (self.__class__.__name__, attr))
Dynamically changing function signatures
Anyway, at this point, here is the simplified code:
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)()
Code is almost clean now. The only puzzling line, maybe, would be:
Multiplier.__init__.im_func.func_defaults = (factor,)
What's in there? I looked at the datamodel doc, and found that func_defaults
was "A tuple containing default argument values for those arguments that have defaults, or None if no arguments have a default value". Are we just changing the default value for factor
argument in __init__
here? Resulting code would then be:
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)()
Which means that dynamically setting the default value was just useless noise, since Multiplier
is never called without a default parameter, right?
And we could probably simplify it to:
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?
And for those hurrying to "this is not a real question"... read again, my questions are in bold+italic