[Python-Dev] Dinamically set __call__ method

Steven D'Aprano steve+comp.lang.python at pearwood.info
Fri Nov 7 05:18:01 CET 2014


Roberto Martínez wrote:

> Yikes, I didn't realize the difference in inheritance.
> 
> The thing with this is tricky. I need the change in the instance, not in
> the class, because I have multiple instances and all of them must have
> different implementations of __call__.
> 
> The workaround of calling a different method inside __call__ is not valid
> for my case because I want to change the *signature* of the function also
> -for introspection reasons.

This is somewhat of a code smell. It's a bit whiffy, which doesn't
*necessarily* mean it is off, only that it could be. You should think hard
about your use-case and consider alternative strategies.

Like, the strategy design pattern :-)

The real problem here is that the individual functions have different
signatures. If they had the same signature, then you could bake that into
the __call__ method and simply call a per-instance method:

class X:
    # Variation on the strategy design pattern.
    def __call__(self, spam, eggs=12):
        method = getattr(self, 'my_custom_method', None)
        if method is None:
            # Default behaviour.
            ...
        else:
            return method(self, spam, eggs)

Then simply add a `my_custom_method` function to the individual instances.
The different functions would provide different algorithms
(implementations) for the same task, with the same interface. This is
exactly the problem with the Strategy design pattern is supposed to solve.

But the smelly part here is that your custom functions take different
signatures, which suggests that they aren't providing different algorithms
for the same task. Different signatures means that they are *not*
interchangeable, that they have different calling conventions and
presumably do different things:

# This only works in Python 2 with "classic (old-style) classes"
a, b, c = [X() for i in range(3)]
a.__call__ = lambda self: self.spam + 1
b.__call__ = lambda self, x, y: (self.eggs or x)*y
c.__call__ = lambda self, value: setattr(self, "spam", value)

That's a strong violation of the expectation that any two instances of the
same class should be more or less interchangeable, with at most a few
exceptional cases (e.g. you can't swap the int instance 0 for the instance
2 in the expression x/2). But it's not *always* wrong, functions themselves
are all instances of FunctionType, and every instance does something
different when called. E.g. while you could swap math.sin for math.cos,
since both take the same parameters, you cannot swap math.sin for len.

Nevertheless, I urge you strongly to think hard about what problem you are
really trying to solve. "Dynamically override __call__" is just a means to
an end. Perhaps there is something less magically you can do to solve the
problem? Is there some way to ensure that all the instances have the same
interface? This might be easy, given that they can store per-instance
state. If instance "a" needs an extra parameter that other instances don't,
perhaps it can take it from an instance attribute instead of an argument.



-- 
Steven




More information about the Python-list mailing list