1

I'm quite confused about Python's behavior when I assign function to classes and instances:

class A:
    def printf(*args):
        print(args)
def printff(*args):
    print(args)

a = A()
a.printf()
print('-'*5, a.printf)
print('-'*5, A.printf)

A.printf = printff
a.printf()
print('-'*5, a.printf)
print('-'*5, A.printf)

a.printf = printff
a.printf()
print('-'*5, a.printf)
print('-'*5, A.printf)

A.printf = printff
a.printf()
print('-'*5, a.printf)
print('-'*5, A.printf)

##Output
##(<__main__.A object at 0x7b3b140e80>,)
##----- <bound method A.printf of <__main__.A object at 0x7b3b140e80>>
##----- <function A.printf at 0x7b3b148048>
##(<__main__.A object at 0x7b3b140e80>,)
##----- <bound method printff of <__main__.A object at 0x7b3b140e80>>
##----- <function printff at 0x7b3b2fce18>
##()
##----- <function printff at 0x7b3b2fce18>
##----- <function printff at 0x7b3b2fce18>
##()
##----- <function printff at 0x7b3b2fce18>
##----- <function printff at 0x7b3b2fce18>

Could anyone tell me :

  • What's the difference between <bound method A.printf> and <bound method printff>?
  • Why when I run A.printf=printff again and the output becomes different?
  • Or, how to understand this behavior?

I'm using Python 3.6 if it makes any difference.

UPDATE:

I'm sorry that I didn't make it clear. For Q1, I want to know is there any difference between <bound method A.printf> and <bound method printff> not bound and unbound. For Q2, I'm wondering is that because I have assigned specifically to the instance that it didn' t work again and how to understand it.

Azat Ibrakov
  • 7,007
  • 8
  • 31
  • 40
Allan Chain
  • 683
  • 6
  • 19
  • You might want to read these threads: [assigning a function to an object attribute](https://stackoverflow.com/questions/6478371/assigning-a-function-to-an-object-attribute) and [Python: bound and unbound method](https://stackoverflow.com/questions/1015307/python-bind-an-unbound-method/1015405#1015405) – Eli Korvigo Jul 20 '18 at 05:51

3 Answers3

0

Here is what is happening, read comments in the code snippet below

class A:
    # here you are defining a method for class A which is <function A.printf at 0x7b3b148048>
    def printf(*args):
        print(args)
# here you are defining a function which is <function printff at 0x7b3b2fce18>
def printff(*args):
    print(args)

a=A() # here when you create an instance of your object you have <bound method A.printf of <__main__.A object at 0x7b3b140e80>> and thus you have 
a.printf()
print ('-'*5,a.printf) # <bound method A.printf of <__main__.A object at 0x7b3b140e80>>
print ('-'*5,A.printf) # <function A.printf at 0x7b3b148048>

A.printf=printff # here you are setting <function A.printf at 0x7b3b148048> = <function printff at 0x7b3b2fce18>
a.printf()
print ('-'*5,a.printf) # <bound method A.printf of <__main__.A object at 0x7b3b140e80>>
print ('-'*5,A.printf) # <function printff at 0x7b3b2fce18>

a.printf=printff # here <bound method A.printf of <__main__.A object at 0x7b3b140e80>> = <function printff at 0x7b3b2fce18>
a.printf()
print ('-'*5,a.printf) # <function printff at 0x7b3b2fce18>
print ('-'*5,A.printf) # <function printff at 0x7b3b2fce18>

A.printf=printff # here <function printff at 0x7b3b2fce18> = <function printff at 0x7b3b2fce18>
a.printf()
print ('-'*5,a.printf) # <function printff at 0x7b3b2fce18>
print ('-'*5,A.printf) # <function printff at 0x7b3b2fce18>
0
  1. The method have two type, bound and unbound, the bound method should have a class instance. The bound method with a class instance as an argument. that's the first question.
  2. For the Q2, we should understand the when the bound happend? the bound action happend only when the first time called a instance method. A.printf = printff result the class A have changed, so, the next time call a A's instance a will result a new bound action. and the next next call print, will no bound action, because the instance have bound method before.
Charles Cao
  • 116
  • 4
  • Thanks. But I'm still confused what you said about "the next next time". I think `a.printf` is not a bound method before I call `A.printf=printff` again. – Allan Chain Jul 20 '18 at 07:00
0

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>
Community
  • 1
  • 1
e.s.
  • 1,183
  • 5
  • 10
  • Thanks a lot for your explanation and your example which help me understand lookup and bound better. I wonder if you could answer my first question of the difference between `` and ``, and your answer would be perfect. – Allan Chain Jul 20 '18 at 07:24
  • i added a few sentences to the tldr; you can see which function is being bound if you look at the calls to a.print(). The first time, it uses the original `A.printf` function and adds `a` as the first argument. The second time if **references** `A.printf` which points to the function `printff` and then calls that with `a` added as the first argument. – e.s. Jul 20 '18 at 07:42
  • if it's still not clear, please try and tell me what you understand of what's happening with the two bound methods so i can figure out what exactly the question is. – e.s. Jul 20 '18 at 08:22