How is types.MethodType used?

Viewed 7337

What arguments do types.MethodType expect and what does it return? https://docs.python.org/3.6/library/types.html doesn't say more about it:

types.MethodType

The type of methods of user-defined class instances.

For an example, from https://docs.python.org/3.6/howto/descriptor.html

To support method calls, functions include the __get__() method for binding methods during attribute access. This means that all functions are non-data descriptors which return bound or unbound methods depending whether they are invoked from an object or a class. In pure python, it works like this:

class Function(object):
    . . .
    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        if obj is None:
            return self
        return types.MethodType(self, obj)
  • Must the first argument self of types.MethodType be a callable object? In other words, must the class Function be a callable type, i.e. must Function have a method __call__?

  • If self is a callable object, does it take at least one argument?

  • Does types.MethodType(self, obj) mean giving obj as the first argument to the callable object self, i.e. currying self with obj?

  • How does types.MethodType(self, obj) create and return an instance of types.MethodType?

Thanks.

4 Answers

Usually you don't need to create instance of types.MethodType yourself. Instead, you'll get one automatically when you access a method on an instance of a class.

For example, if we make a class, create an instance of it, then access a method on the instance (without calling it), we'll get an instance of types.MethodType back:

import types

class Foo:
    def bar(self):
        pass

foo = Foo()

method = foo.bar

print(type(method) == types.MethodType) # prints True

The code you excerpt in your question is trying to show how this normally happens. It's not something you usually have to do yourself, though you can if you really want to. For instance, to create another instance of types.MethodType equivalent to method above, we could do:

method_manual = types.MethodType(Foo.bar, foo)

The first argument to MethodType is a callable object (normally a function, but it can be something else, like an instance of the Function class in the example you were reading). The second argument what we're binding the function to. When you call the method object (with e.g. method()), the bound object will be passed into the function as the first argument.

Usually the object the method gets bound to is an instance, though it can be something else. For instance, a classmethod decorated function will bind to the class it is called on, rather than an instance. Here's an example of that (both getting a method bound to a class automatically, and doing it manually ourselves):

class Foo2:
    @classmethod
    def baz(cls):
        pass

foo2 = Foo2()

method2 = Foo2.baz
method2_via_an_instance = foo2.baz
method2_manual = types.MethodType(method2.__func__, Foo2)

All three of the method2-prefixed variables work exactly the same way (when you call them, they'll all call baz with Foo2 as the cls argument). The only wonky thing about the manual approach this time is that it's hard to get at the original baz function without getting a bound method instead, so I fished it out of one of the other bound method objects.

A final note: The name types.MethodType is an alias for the internal type used for bound methods, which doesn't otherwise have an accessible name. Unlike many classes, the repr of an instance is not an expression to recreate it (it will be something like "<bound method Foo.bar of <__main__.Foo object at 0x0000...>>"). Nor is the repr of the type a valid name to access the type by (the repr is "method").

Short Answer:

Must the first argument self of types.MethodType be a callable object? In other words, must the class Function be a callable type, i.e. must Function have a method __call__?

Yes

If self is a callable object, does it take at least one argument?

Depends

Does types.MethodType(self, obj) mean giving obj as the first argument to the callable object self, i.e. currying self with obj?

Yes

How does types.MethodType(self, obj) create and return an instance of types.MethodType?

It doesn't work like that.


Long Answer:

the code

class Function(object):
    . . .
    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        if obj is None:
            return self
        return types.MethodType(self, obj)

As Daniel explained is mainly to demonstrate for

To support method calls, functions include the __get__() method for binding methods during attribute access. This means that all functions are non-data descriptors which return bound or unbound methods depending whether they are invoked from an object or a class. In pure python, it works like this:

The types.MethodType() works when the Function has an object.
if obj is None would be False
Then it's a method of some object aka. bound method.

It explains how Python grammar work. As a function, it could be called in the following two ways.

some_func_() or some_class.some_func()

The former part https://docs.python.org/3.6/howto/descriptor.html#invoking-descriptors explained.

For objects, the machinery is in object.__getattribute__() which transforms b.x into type(b).__dict__['x'].__get__(b, type(b)). The implementation works through a precedence chain that gives data descriptors priority over instance variables, instance variables priority over non-data descriptors, and assigns lowest priority to __getattr__() if provided.

Here it's some demonstrate code

>>> import types
>>> types.MethodType
<type 'instancemethod'>
>>> def a(self):
...     print(1)
... 
>>> class B:
...     pass
... 
>>> types.MethodType(a,B)
<bound method ?.a of <class __main__.B at 0x7f4d3d5aa598>>
>>> B.t = types.MethodType(a,B)
>>> B.t()
1
>>> def s():
...     print(3)
... 
>>> B.r = types.MethodType(s,B)
>>> B.r
<bound method ?.s of <class __main__.B at 0x7f4d3d5aa598>>
>>> B.r()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: s() takes no arguments (1 given)

See also dynamically adding callable to class as instance "method"

Documentation doesn't say much, but you can always check its source code. The signature of MethodType constructor is:

def __init__(self, func: Callable[..., Any], obj: object) -> None: ...

It accepts a callable and object, and returns None.

MethodType can be used to add an instance method to an object, instead of a function; here's an example:

from types import MethodType


class MyClass:
    language = 'Python'


# a function is bound to obj1
obj1 = MyClass()
obj1.say_hello = lambda: 'Hello World!'
print(type(obj1.say_hello))  # type is class 'function'
obj1.say_hello()

# a method is bound to obj2
obj2 = MyClass()
# this is used to bind a "method" to a specific object obj2, rather than a function
obj2.say_hello = MethodType(lambda self: f'Hello {self.language}!', obj2)
print(type(obj2.say_hello))  # type is class 'method'
obj2.say_hello()

It's not something you would ever call. Like most of the classes in the types module, it's more for comparing with existing objects (for example in isinstance).

Related