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

+2


source to share


1 answer


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.

+1


source







All Articles