5

If I have a

class A:
  def foo(self):
    pass

this evaluates to True:

getattr(A, 'foo') is A.foo

but this evaluates to False:

a = A()
getattr(a, 'foo') is a.foo

as does

a.foo is a.foo

Why?

I found that getattr(a, 'foo') and a.foo both are represented by

<bound method A.foo of <__main__.A object at 0x7a2c4de10d50>>)

So no hint there....

steffen
  • 8,572
  • 11
  • 52
  • 90

3 Answers3

3

At least in CPython, bound methods are implemented as an instance of a class method. Every time you ask for the value of a bound function, you get a new instance of this class.

x = a.foo
y = a.foo

assert x is not y
id(x)  # E.g. 139664144271304
id(y)  # E.g. 139664144033992
type(x)  # <class 'method'>
type(y)  # <class 'method'>

All this class does is store a reference to the instance and the unbound function, and when you call the class it calls the unbound function with the stored instance (along with your other arguments).

Unbound functions, like A.foo, are just regular old functions - no new instances of proxy classes are being constructed, so identity works as you expect.

The reason for this difference is that the semantic meaning of a.foo depends on two things, the value of a and the value of A.foo. In order to be able to get this meaning at any point in time later, both of these values need to be stored. This is what the method class does.

Conversely, the meaning of A.foo depends only on a single value: A.foo. So no additional work is required to store anything, and the value itself is used.

You might consider the idea of pre-allocating bound method instances, so that a.foo always returns the same immutable object - but given the dynamic nature of Python, it is simpler and cheaper to just construct a new one each time, even if they could be the same.

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • So there is one more layer of abstraction. A method is not actually part of a class, but an instance of class `method`, which in turn knows the function body and the "hosting" class. Correct? – steffen May 23 '18 at 23:32
  • mmhh... what you call "regular old functions" are instances of class "function". So again: why the different behaviour? – steffen May 23 '18 at 23:34
  • 2
    Yes, `A.foo` is a function just like any non-class function is (compare the types, for example). The difference in behavior is that `a.foo` represents, abstractly, "the function `A.foo` called with `a`, as they each were at the time this was invoked". In order to actually make this call later, you must store the value of both of them for later as well, and `method` is this storage. Simply referring to `A.foo` requires no additional storage, as you already have the reference to the sole value in question, so regular Python reference semantics suffice. – GManNickG May 23 '18 at 23:36
  • Thank you @GManNickG for a very good answer! Just to add/confirm - see `new.instancemethod` in https://docs.python.org/2/library/new.html – AGN Gazer May 23 '18 at 23:40
  • ok, I just played around with `setattr` and the class and instances of the class. It is necessary to generate `a.foo` everytime again, because `A.foo` might have changed (or `other_instance_of_A.foo` for that matter) and I want to keep `a.foo` as it was when creating `a`. Please add that just for completeness. – steffen May 23 '18 at 23:51
3

To add to @GManNickG answer:

getattr(a, 'foo').__func__ is a.foo.__func__ 

will return True.

AGN Gazer
  • 8,025
  • 2
  • 27
  • 45
2

Some objects stored in classes are descriptors, which don't follow normal rules for object lookups. The foo method you're dealing with in your example is one (function objects are descriptors).

A descriptor is an instance of a class that defines a __get__ (and optionally __set__ and __delete__) method(s). Those methods control what happens when you look up the desciptor on an instance of the class it's stored in.

I think an example will make this more clear:

class DescriptorClass:
    def __get__(*args):
        print("__get__ was called")
        return "whatever"

class OtherClass:
    descriptor_instance = DescriptorClass() # the descriptor instance is a class variable

other_instance = OtherClass()

# this calls the descriptor's __get__ method, which prints "__get__ was called"
result = other_instance.descriptor_instance

print(result) # will print "whatever", since that's what the __get__ method returned

A __get__ method doesn't need to return the same thing every time it's called. In fact, it usually won't. In the specific case of functions being used as descriptors (i.e. methods), a new "bound method" object will be created each time you look the function up. Thus the is operator will not see multiple bound methods as the same object, even though they may be binding the same function to the same instance.

Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • Found this in your link which explains the exact case https://docs.python.org/3/howto/descriptor.html#functions-and-methods – steffen May 24 '18 at 00:17