44

What is the method-wrapper type in Python 3? If I define a class like so:

class Foo(object):
    def __init__(self, val):
        self.val = val
    def __eq__(self, other):
        return self.val == other.val

And then do:

Foo(42).__eq__

I get:

<bound method Foo.__eq__ of <__main__.Foo object at 0x10121d0>>

But if I do (in Python 3 ):

Foo(42).__ne__

I get:

<method-wrapper '__ne__' of Foo object at 0x1073e50>

What is a "method-wrapper" type?

Edit: sorry to be more accurate: class method-wrapper is the type of __ne__, as if I do:

>>> type(Foo(42).__ne__)
<class 'method-wrapper'>

Whereas the type of __eq__ is:

>>> type(Foo(42).__eq__)
<class 'method'>

Furthermore method-wrapper seems to be the type of any undefined magic method on a class (so __le__, __repr__, __str__ etc if not explicitly defined will also have this type).

What I am interested in is how the method-wrapper class is used by Python. Are all "default implementations" of methods on a class just instances of this type?

Adam Parkin
  • 17,891
  • 17
  • 66
  • 87

3 Answers3

35

It appears that the type <method-wrapper ..> is used by CPython for methods implemented in C code. Basically the type doesn't wrap another method. Instead it wraps a C-implemented function as a bound method. In this way <method-wrapper> is exactly like a <bound-method> except that it is implemented in C.

In CPython there are two special types related to this.

  • <slot wrapper> Which (at least) wraps a C-implemented function. Behaves like an <unbound method> in CPython 2 (at least sometimes) or a <function> in CPython 3
  • <method-wrapper> Which wraps a C-implemented function as a bound method. Instances of this type have a __self__ attribute__ which is used as first argument when it is called.

If you have a <slot wrapper> you bind it to an object with __get__ to get a <method-wrapper>:

# returns a <slot_wrapper> on both CPython 2 and 3
sw = object.__getattribute__  

# returns a <method-wrapper>
bound_method = sw.__get__(object()) 

# In this case raises AttributeError since no "some_attribute" exists.
bound_method("some_attribute")  

You can call __get__ on any function-like object in Python to get a <bound method> or <method-wrapper>. Note __get__ on both of these types will simply return self.

Python 3

The type object in CPython 3 have C-implementations for both __ne__ and __eq__, and any of the other comparison operators. Thus object.__ne__ returns a <slot wrapper> for this operator. Likewise object().__ne__ returns a <method-wrapper> which can be used to compare the this object.

Since you have not defined __ne__ in your class you get a bound method (as <method-wrapper>) which is the C-implemented function for instance of object (included derived instances). My bet is that this C function will check if you have defined any __eq__, call this, and then not the result.

Python 2 (not asked but answered)

Python 2 behaves significantly different here. Since we have the concept of unbound methods. which require that you call them with the proper first-argument type, both <slot wrapper> and <method-wrapper> have a __objclass__ which is the type the first-argument must be an instance of.

More over: Python 2 does not have any implementations of comparisons operators for the object type. Thus object.__ne__ is not a function to compare objects. Rather, interesting, the type type which is the metaclass of object does have a C-implemented __ne__ operator. Thus you get a bound method from object.__ne__ that will try to compare the type object with any other type (or object).

Thus object().__ne__ will actually fail with an AttributeError since object does not define any such method. Given that object() == object() actually works (giving False), I would guess that CPython 2 have special-cases in the interpreter for comparison of objects.
Once more we see that CPython 3 have cleaned up some less-fortunate implementation details of Python 2.

greybeard
  • 2,249
  • 8
  • 30
  • 66
driax
  • 2,528
  • 1
  • 23
  • 20
  • Thanks! Are objects of `` and `` both callables and descriptors? – Tim Oct 05 '17 at 14:06
  • 1
    Both are callables, but only is a descriptor (ie. has a `__get__` method). This is unlike pure-python-3 code where `types.MethodType` is a descriptor as well. Thou that is probably just because on Python 2 `types.MethodType` represents both unbound and bound methods thus needing a `__get__` to let you bind a unbound method to an object. – driax Oct 11 '17 at 16:06
  • 2
    Slot-wrapper and method-wrapper objects have `__objclass__` on Python 3 as well. Also, slot-wrapper and method-wrapper are only used for methods corresponding to C slots; there are a bunch of other C method types, most prominently method-descriptor and builtin-function-or-method, method-descriptor being used for methods like `list.append` and builtin-function-or-method being used for bound methods like `[].append` (and also for functions, like `max`). – user2357112 Sep 29 '18 at 08:06
32

This is because 'unbound methods' don't exist in Python 3.

In Python 3000, the concept of unbound methods has been removed, and the expression "A.spam" returns a plain function object. It turned out that the restriction that the first argument had to be an instance of A was rarely helpful in diagnosing problems, and frequently an obstacle to advanced usages --- some have called it "duck typing self" which seems an appropriate name. (Source)

In Python 2.x, we had bound methods and unbound methods. A bound method was bound to an object, meaning that when it was called, it passed the object instance as the first variable (self, normally). An unbound method was one where the function was a method, but without a instance it belonged to - it would throw an error if something other than an object instance was passed in to the method.

Now, in 3.x, this has been changed. Instead of bound/unbound methods, when you request a method of an object, it returns the function object, but wrapped in a wrapper function that passes the instance variable in. This way there is no need to make a distinction between bound and unbound methods - bound methods are wrapped, unbound are not.

To clarify the difference:

2.x:

a = A()
f = A.method # f is an unbound method - you must pass an instance of `A` in to it as the first argument.
f = a.method # f is a bound method, bound to the instance `a`.

3.x:

a = A()
f = A.method # f is a function
f = a.method # f is a wrapped function with it's first argument filled with `a`.

a.method can be thought of as:

def method-wrapper():
    A.method(a)

For more on this, check out Guido's blog - the history of Python.

Edit:

So, the reason this all applies is that, here, __ne__() has not been overridden - it's at a default state, which is an identity check, implemented in C (line 980ish). The wrapper is there to provide the method with the above functionality.

Gareth Latty
  • 86,389
  • 17
  • 178
  • 183
  • 1
    I'm not sure I follow, so what's a `method-wrapper` type then? Is it just Python 3's equivalent to an unbound function? But you call it the same way you would a regular wrapped function (like the `__ne__` example above), so I don't get it. – Adam Parkin May 03 '12 at 15:44
  • No, an unbound method doesn't exist in Python 3 - that's just a function. (Unbound methods stopped you passing in a first argument that wasn't the type of the class the method was from, but that was considered an artificial limit and removed). A wrapped-method is a bound method - it know what instance it belongs to and the method is wrapped to provide that instance. – Gareth Latty May 03 '12 at 15:49
  • 2
    Sorry, I still don't think my question has been answered: what is a `method-wrapper` type? For both the `__eq__` and `__ne__` methods the types are different, but they both receive an implicit first argument. Are you saying that a `method-wrapper` type method is one which is not aware of which instance it belongs to? But if that's the case, then how does `someinst.__ne__(otherinst)` work (since it's bound to the `someinst` instance)? – Adam Parkin May 03 '12 at 16:09
  • @AdamParkin I added a little bit at the end, to try and make it more clear why this is relevant. – Gareth Latty May 03 '12 at 16:22
  • @Marcin Yes, but not in this situation due to the reasons described in the answer. – Gareth Latty Aug 21 '14 at 14:58
  • So, what is the difference between `__eq__` and `__ne__` which makes one `method` but the other `method-wrapper`? This is the question, and most of this answer seems irrelevant to that question. – Alexey Mar 16 '17 at 21:51
-1

Below some testcode from my variable inspection routines. Defined class Tst, which has a __str__ method. To find out if an instance of a class has either __repr__ or __str__ defined, you can test for hasattr(obj.__str__, '__code__'):

Example:

class Tst(object):
    """ Test base class.
    """
    cl_var = 15

    def __init__(self, in_var):
        self.ins_var = in_var

    def __str__(self):
        # Construct to build the classname ({self.__class__.__name__}).
        # so it works for subclass too.
        result = f"{self.__class__.__name__}"
        result += f"(cl_var: {self.__class__.cl_var}, ins_var: {self.ins_var})"
        return result

    def show(self):
        """ Test method
        """
        print(self.ins_var)


t5 = Tst(299)


print('\n\n----- repr() ------')
print(f"obj.repr(): {repr(t5)}")
print(f"obj.repr.class type: {type(t5.__repr__.__class__)}")
print(f"obj.repr.class.name: {t5.__repr__.__class__.__name__}")
print(f"obj.__repr__ has code: {hasattr(t5.__repr__, '__code__')}")

print('\n\n----- str() ------')
print(f"obj.str(): {str(t5)}")
print(f"obj.str.class type: {type(t5.__str__.__class__)}")
print(f"obj.str.class.name: {t5.__str__.__class__.__name__}")
print(f"obj.__str__ has code: {hasattr(t5.__str__, '__code__')}")

Returns:

----- repr() ------
obj.repr(): <__main__.Tst object at 0x107716198>
obj.repr.class type: <class 'type'>
obj.repr.class.name: method-wrapper
obj.__repr__ has code: False


----- str() ------
obj.str(): Tst(cl_var: 15, ins_var: 299)
obj.str.class type: <class 'type'>
obj.str.class.name: method
obj.__str__ has code: True

Since __repr__ isn't defined on the class, it defaults to the __repr__ on base-class object via the method-wrapper and gives a default output. __str__ is defined (and thus a method), so the test hasattr(t5.__str__, '__code__') results True.

Marc
  • 1
  • 1