简体   繁体   中英

What exactly is a method in Python?

In the code below, the object a has two functions as attributes: one is a class attribute while one is its own instance attribute.

class A:
    def foo(*args):
        pass

a = A()

def bar(*args):
    pass

a.bar = bar

print(a.foo)
print(a.bar)

What I expected was that both bar() and foo() would be methods of the object a , but, from the output of this code, it turns out that this is not the case—only foo is a method of a .

<bound method A.foo of <__main__.A object at 0x0000016F5F579AF0>>
<function bar at 0x0000016F5F5845E0>

So what exactly is a method in Python? Is it a class attribute that holds a function definition, which seems to be the case.

And why isn't the attribute bar considered a method by Python? What exactly is the idea behind this behaviour of Python?

A method is an instance of the class method returned by, among other things, the __get__ method of a function -valued class attribute.

a.bar is an instance attribute, not a class attribute.

When looking for a.foo , Python first looks for A.foo . Having found it, it next checks if its value has a __get__ method (which, as a function value, it does.) Because A.foo is a descriptor (ie, has a __get__ method), its __get__ method is called: a.foo is the same as A.foo.__get__(a, A) . The return value is a method object, whose __call__ method calls the underlying function with the object and its own arguments. That is,

a.foo(x) == A.foo.__get__(a, A)(x)
         == A.foo(a, x)

Because a.bar is an instance attribute, the descriptor protocol is not invoked, so a.bar is the exact same object as bar .

(This is an extremely condensed version of the contents of the Descriptor HowTo Guide , in particular the section on methods . I highly recommended reading it.)


The lookup for a.bar proceeds by first looking for bar in A.__dict__ . When it isn't found, Python looks in a.__dict__ , and finds a function to call: end of story.

This is because you are setting the attribute to an instance of the class, not itself.

Following your example, let's set:

A.bar = bar

You'll see that the output is:

print(A.bar)
# <function bar at 0x0000021821D57280>

c = A()
print(c.bar)
# <bound method bar of <__main__.A object at 0x0000021821EEA400>>

Bound methods are methods that require the actual instance of the class itself. This is why we typically have self as the first parameter: the instance knows to pass itself to the function when it gets called. By setting the function as an attribute to the instance, the method is not bound, and the first parameter will not be itself.

We can see this behavior if we set our function bar as the following:

def bar(*args):
    print("Argument types:", *map(type, args))

a.bar = bar
a.bar()
# Argument types:

A.bar = bar
c = A()
c.bar()
# Argument types: <class '__main__.A'>

We see that with a bound method, it passes itself to the function. This is the difference between setting a method as an attribute versus actually setting it to the class itself.

a.bar is not a BOUND method. It is a callable that has been set to an attribute. This means, among other things, that it doesn't automatically get the self .

class A:
    def __init__(self):
        self.baz = 'baz'
        
    def foo(self):
        print(self.baz)
    
def bar(obj):
    print(obj.baz)

a = A()
a.bar = bar

a.foo()
# prints baz

a.bar()
# raises TypeError: bar() missing 1 required positional argument: 'obj'

a.bar(a)
# prints baz

You can make a method into a bound method with types.MethodType :

a.bar = types.MethodType(bar, a)
a.bar()
# prints baz

This only binds it to the instance; other instances won't have this attribute

a2 = A()
a2.bar()
# AttributeError: 'A' object has no attribute 'bar'

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM