Decoder with me who got confused by MultiplierFactory
This week on comp.lang.python, an "interesting" code snippet was posted by Steven D'Aprano as a joke on a homework question. Here he 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 what twice
is the equivalent of the answer:
def twice(x):
return 2*x
From the names Multiplier
and MultiplierFactory
we get an idea of what the code does, but we are not sure about the exact internals. Simplify it first.
Logics
if not factor is not None is True:
factor = self.factor
not factor is not None is True
is equivalent not factor is not None
to that also factor is None
. Result:
if factor is None:
factor = self.factor
So far this has been easy :)
Attribute access
Another interesting point is the curious accessor factor
.
def factor(self):
return getattr(self, '_%s__factor' % self.__class__.__name__)
MultiplierFactory
Set during initialization self.__factor
. But later the code accesses the self.factor
.
Then it seems that:
getattr(self, '_%s__factor' % self.__class__.__name__)
Performs exactly " self.__factor
".
Can we always refer to attributes this way?
def mygetattr(self, attr):
return getattr(self, '_%s%s' % (self.__class__.__name__, attr))
Dynamically changing function signatures
Anyway, for now, here's a 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)()
The code is almost clean now. Perhaps the only cryptic line:
Multiplier.__init__.im_func.func_defaults = (factor,)
What is there? I looked at the datamodel doc and found that there func_defaults
was "A tuple containing default argument values for those arguments that have default values, or None if no arguments have default values." Are we just changing the default for the argument factor
in __init__
here? Result:
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)()
This means that dynamically setting the default was just useless noise as it is Multiplier
never called without a default parameter, right ?
And we could probably simplify it:
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)
Right?
And for those in a hurry "this is not a real question" ... read again, my questions are in bold + italic
source to share
Q1. Can we always refer to attributes this way?
A: No. Only those attributes that start with double underscores. They are obfuscated in such a way as to prevent accidental access / overriding from outside the class.
Q2: Are we just changing the default for the factor argument in __init__
here?
A: Yes.
Q2: right?
Right.
source to share