Reputation: 1859
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?
Upvotes: 1
Views: 78
Reputation: 530960
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 (i.e, 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.
Upvotes: 3
Reputation: 533
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'
Upvotes: 0
Reputation: 4799
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.
Upvotes: 0