Noob Saibot
Noob Saibot

Reputation: 4759

What is the hidden argument being passed to my `MethodType`?

I recently came across this recipe for making a "weakmethod" and thought it was the bees' knees; but there seems to be a mystery argument being passed to the resulting MethodType function that i can't seem to find:

from weakref import proxy
from types import MethodType

class Foo(object):
    def __getattribute__(self, name):
        if name.startswith('foo_'):
            return MethodType(super(Foo, self).__getattribute__(name), proxy(self), self.__class__)
        else:
            return super(Foo, self).__getattribute__(name)

class Bar(Foo):
    def my_func(self, a, b):
        print a, b

    def foo_my_func(self, a, b):
        print 'FF Victory Theme'

>>> bar = Bar()
>>> bar.my_func(1, 2)
1 2
>>> weakmethod = bar.foo_my_func
>>> weakmethod(2, 3) # Or `bar.foo_my_func(2, 3)`
Traceback (most recent call last):
  File "<pyshell#160>", line 1, in <module>
    weakmethod(2, 3)
TypeError: foo_my_func() takes exactly 3 arguments (4 given)

What is this 4th argument that's being passed?

Upvotes: 0

Views: 114

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1124170

You used super(Foo, self).__getattribute__(name) to access the foo_my_func method. This already returns a MethodType object. You then wrap this object again.

So your returned object passes in proxy(self) to the wrapped method, which passes in another self argument. You started with a, b, and end up with self, proxy(self), a, b.

The recipe you linked to uses a decorator instead; this decorator is executed at class definition time, and wraps the function object. It is itself a descriptor, so it handles all the wrapping directly.

You'll want to either unwrap the result of super(Foo, self).__getattribute__(name) or don't use __getattribute__ at all.

Unwrapping can be done with accessing the __func__ attribute on a method:

class Foo(object):
    def __getattribute__(self, name):
        attr = super(Foo, self).__getattribute__(name)
        if name.startswith('foo_'):
            return MethodType(attr.__func__, proxy(self), self.__class__)
        return attr

Not using __getattribute__ is done by just accessing the __dict__ mapping on the class directly:

class Foo(object):
    def __getattribute__(self, name):
        if name.startswith('foo_'):
            for cls in type(self).__mro__:
                if name in cls.__dict__:
                    return MethodType(cls.__dict__[name], proxy(self), self.__class__)
        return super(Foo, self).__getattribute__(name)

where type(self).__mro__ lets you iterate over the class and it's base classes in method resolution order to manually search for the method.

Upvotes: 1

Related Questions