tldr (based on update);
Q1 <bound method A.printf of <__main__.A object at 0x000000000286C470>>
and <bound method printff of <__main__.A object at 0x000000000286C470>>
mean: the object at the address 0x000000000286C470 is bound as the first argument of the function called XXX. So, the two bound methods are different. They are assigning the instance as the first argument of two different functions (the original A.printf
and printff
. The above omits some details of the original function's address, etc... you can find the original function it's bound to by calling instance.method.__func__
Q2: when you assign to the instance, it never does a lookup in the class and never binds to the function stored in the class. You over-rode the class reference, but only for that specific instance.
long version:
You create an instance. Any instance attributes get bound in the __init__
. You can see them in the __dict__
of the instance. If you call an attribute not in a.__dict__
, it's looked up in A.__dict__
. If its a function (and not a class method or static method), a
is bound as the first argument of the function, so a.printf
returns the function at A.printf
with a
bound to first argument.
When you first create a
and print(a.printf)
. a.__dict__
has no printf
, so it does a lookup in A.__dict__
, gets the function, and binds a
as the first argument. it returns <bound method A.printf of <__main__.A object at 0x000000000286C470>>
This means "the function called 'A.printf'
, but bound to the object at this address (a
)."
Then, when you assign A.printf = printff
, and call a.printf
: a.__dict__
still doesn't have an entry for printf
, so it does a lookup in A.__dict__
and binds a
to this function. so : <bound method printff of <__main__.A object at 0x000000000286C470>>
means "the function called 'printff '
, but bound to the object at this address (a
)."
Then you assign a
a new attribute that shadows the attribute in the class. So when you call a.printf
, it finds an attribute in a.__dict__
. This is a function and it returns that function without doing any binding whatsoever. It never looks up the class attribute (which is still there) and never binds to it. If we have a new instance b
, it will still do a lookup in the class.
Finally, you assign printff
to A.printf
which doesn't change anything. Here's another example and its output.
import random
class A:
def __init__(self):
self.instance_attr = random.randint(1, 10)
def printf(*args):
print('in method', args)
def printff(*args):
print('in function', args)
a = A()
b = A()
print('\nbasic')
a.printf()
print('a: ',a.__dict__)
print('b: ', b.__dict__)
print('a: ', a.printf)
print('b: ', b.printf)
print('class dict lookup: ', A.printf)
print('\nchange class lookup')
TEMP = A.printf
A.printf = printff
a.printf()
print('a: ',a.__dict__)
print('b: ', b.__dict__)
print('a: ', a.printf)
print('b: ', b.printf)
print('class dict lookup: ', A.printf)
print('\nchange instance lookup')
a.printf = printff
a.printf()
print('a: ',a.__dict__)
print('b: ', b.__dict__)
print('a: ', a.printf)
print('b: ', b.printf)
print('class dict lookup: ', A.printf)
print('\nrevert class lookup')
A.printf = TEMP
a.printf()
print('a: ',a.__dict__)
print('b: ', b.__dict__)
print('a: ', a.printf)
print('b: ', b.printf)
print('class dict lookup: ', A.printf)
and the output:
basic
in method (<__main__.A object at 0x00000000029DF320>,)
a: {'instance_attr': 9}
b: {'instance_attr': 5}
a: <bound method A.printf of <__main__.A object at 0x00000000029DF320>>
b: <bound method A.printf of <__main__.A object at 0x00000000029DF390>>
class dict lookup: <function A.printf at 0x00000000029F0730>
change class lookup
in function (<__main__.A object at 0x00000000029DF320>,)
a: {'instance_attr': 9}
b: {'instance_attr': 5}
a: <bound method printff of <__main__.A object at 0x00000000029DF320>>
b: <bound method printff of <__main__.A object at 0x00000000029DF390>>
class dict lookup: <function printff at 0x00000000029E60D0>
change instance lookup
in function ()
a: {'instance_attr': 9, 'printf': <function printff at 0x00000000029E60D0>}
b: {'instance_attr': 5}
a: <function printff at 0x00000000029E60D0>
b: <bound method printff of <__main__.A object at 0x00000000029DF390>>
class dict lookup: <function printff at 0x00000000029E60D0>
revert class lookup
in function ()
a: {'instance_attr': 9, 'printf': <function printff at 0x00000000029E60D0>}
b: {'instance_attr': 5}
a: <function printff at 0x00000000029E60D0>
b: <bound method A.printf of <__main__.A object at 0x00000000029DF390>>
class dict lookup: <function A.printf at 0x00000000029F0730>