2

I believe that func(obj.attr) and tmp = obj.attr; func(tmp) should be completely indistinguishable. However, the following code

class Cls(object):
    #@staticmethod
    def f(self):
        pass

def who(obj):
    print(id(obj) % 1000, end=' ')

obj = Cls()

n = 10

for i in range(n):
    who(obj.f)

print('\n')

for i in range(n):
    tmp = obj.f
    who(tmp)

produces

736 736 736 736 736 736 736 736 736 736 

736 216 736 216 736 216 736 216 736 216 

We can see that the second for loop produces a interesting pattern, while the first one does not. It seems that assigning a bound method would alter the bound method itself, but why passing it to a function wouldn't?

Huazuo Gao
  • 1,603
  • 14
  • 20

2 Answers2

2

Python creates a new bound method object every time you access f (see Does Python really create all bound method for every new instance?).

In the first loop:

  • At the first iteration:
    • obj.f creates the bound method object.
    • who prints the id
    • There are no more references to the bound method object, hence it is deallocated immediately by the interpreter
  • At the following iteration:
    • obj.f creates a new bound method object. The intrepreter reuses the memory freed at the iteration before.
    • who prints the id which happen to match the previous id
    • Again the object is deallocated

In the second loop:

  • At the first iteration
    • obj.f creates a new bound method object
    • It is assigned to tmp (and thus it has a reference now)
    • who prints the id
    • No deallocation is performed. The tmp variable keeps the object alive
  • At the following iteration
    • obj.f creates a new bound method object. It cannot reuse the old address because that object is still alive.
    • It is assigned to tmp. Now the old value of tmp has no more references and thus is deallocated.
    • who prints the id, which is a new one.
    • Again no deallocation occurs
  • At the following iteration
    • obj.f creates a new bound method object. Now the old-old address is again available and the interpreter decides to reuse it.
    • It is assigned to tmp. Now the old value of tmp has no more references and thus is deallocated.
    • who prints the id, which is a old-old one
    • Again no deallocation occurs

and so a loop is created that alternates between the two addresses.

By using more variables you can create longer cycles:

>>> tmp1 = obj.f
>>> tmp2 = tmp3 = tmp4 = tmp5 = None
>>> for i in range(20):
...     tmp5 = tmp4
...     tmp4 = tmp3
...     tmp3 = tmp2
...     tmp2 = tmp1
...     tmp1 = obj.f
...     who(tmp1)
... 
864 896 560 280 288 864 896 560 280 288 864 896 560 280 288 864 896 560 280 288

Or, as mentioned by Reblochon Masque in his answer, using del you can avoid the behaviour:

>>> for i in range(20):
...     tmp = obj.f
...     who(tmp)
...     del tmp   # forget the name
... 
688 688 688 688 688 688 688 688 688 688 688 688 688 688 688 688 688 688 688 688

Note that this is due to implementation details of CPython. In Jython or other python implementations without reference counting both loops will probably behave pretty much in the same way: showing ten different ids.

Community
  • 1
  • 1
Bakuriu
  • 98,325
  • 22
  • 197
  • 231
1

Not an answer, but a complement to Bakuriu' answer:

If you delete temp before continuing the iteration, you get the same result as in case 1:

(...)
for i in range(n):
    tmp = obj.f
    who(tmp)
    del(tmp)

produces:

496 496 496 496 496 496 496 496 496 496 

496 496 496 496 496 496 496 496 496 496

Of course, the actual number will reflect what state the system used was in at runtime.

Reblochon Masque
  • 35,405
  • 10
  • 55
  • 80