Search code examples
pythonpython-3.xtypespython-3.7introspection

isinstance(list.append (BuiltinFunctionType, BuiltinMethodType)) == False


On non-builtin types, types.FunctionType and types.MethodType behave as expected:

>>> isinstance(MyClass.func, FunctionType)
True
>>> isinstance(MyClass.func, MethodType)
False
>>> isinstance(MyClass().func, FunctionType)
False
>>> isinstance(MyClass().func, MethodType)
True

However, on a built-in type, BuiltinFunctionType and BuiltinMethodType don't behave accordingly:

>>> isinstance(list.append, (FunctionType, MethodType,
...                          BuiltinFunctionType, BuiltinMethodType))
False  <- Why??
>>> isinstance([].append, (FunctionType, MethodType))
False
>>> isinstance([].append, BuiltinFunctionType)
True  <- Why??
>>> isinstance([].append, BuiltinMethodType)
True

Now this does't make sense, does it? Can someone explain?

EDIT: isinstance(list.append, MethodDescriptorType) == True. Can someone explain how MethodDescriptorType differs from BuiltinMethodType and why do we need it? The official documentation doesn't really say much.


Solution

  • BuiltinFunctionType and BuiltinMethodType are just different names referring to the same object:

    >>> BuiltinFunctionType is BuiltinMethodType
    True
    

    isinstance(list.append, BuiltinMethodType) is false because it's not a method (i.e. an instance method bound to some object). The method binding happens through object.__getattribute__, so the functions defined with a class are just plain functions:

    >>> def func(x): pass
    ... 
    >>> class MyClass: func = func
    ... 
    >>> MyClass.func is func
    True
    

    and hence isinstance(MyClass.func, FunctionType). This works because all functions are non-data descriptors:

    >>> func.__get__
    <method-wrapper '__get__' of function object at 0x7f80abe57400>
    

    For builtin types these objects (e.g. list.append) are also dedicated descriptors that handle the binding to their objects. So x = []; x.append returns a bound method which was created by the descriptor list.append and bound to the object x:

    >>> type(list.append)
    <class 'method_descriptor'>