2

This bit of Python has me puzzled.

class my_class(object):
    def f():
        return 1
c = my_class()
f1 = c.f
f2 = c.f
assert not f1 is f2
assert id(f1) != id(f2)
assert f1 == f2

All three asserts pass. The first two are equivalent to each other, but I expected both to fail.

This seems very strange to me. Is Python really creating new, bound, member function objects whenever I call c.f? Why would it do that?

OK - looking here, I guess the answer to my first question is yes. But still, this business of having different ids with consistent == seems completely strange in this context. For containers, sure, I want divergence between is and == (i.e two distinct container objects that contain equivalent data). But for member functions, it seems like is and == ought to be equivalent.

Can anyone motivate this bit of Python design for me?

EDIT : Upon further reflection, this makes sense to me. In order for the memory footprint of an object to be small, the bound functions have to be created upon request, and thus will be distinct objects for each request.

Community
  • 1
  • 1
Pete Cacioppi
  • 880
  • 7
  • 11
  • 4
    There's no "ought" relation between `is` and `==`, as they intentionally do different things. – TigerhawkT3 Jul 07 '15 at 17:41
  • When you say you want them to be equivalent, what do you mean? `is` *has* to mean object identity; that can't be changed. If `==` also meant object identity you would lose the ability to see if two bound methods are "the same" (i.e., result from accessing the same method on the class). Why would you want that? – BrenBarn Jul 07 '15 at 17:44
  • There are lots of instances in Python where different objects compare as equal (e.g., `3 == 3.0`). Conceptually, it's the same for bound methods; two distinct objects, but with the same underlying class and function. – chepner Jul 07 '15 at 17:46
  • I understand the difference between `is` and `==`. My expectation was that the first two asserts would fail, because it seems strange to me that two different calls to `c.f` would yield distinct objects. I think I'm starting to answer my own question, however. In order for the memory footprint of an object to be small, the bound functions have to be created upon request. – Pete Cacioppi Jul 07 '15 at 17:47
  • Also, in Python 2, you could replace instance methods on a per-object basis; there was no guarantee that `c.f` would bind the same function each time it was used. – chepner Jul 07 '15 at 17:50
  • @PeteCacioppi, why would creating the function ahead of time use more memory? – James Jul 07 '15 at 17:56
  • @James - how could it not take more memory? Typically, the more attributes to a class, the bigger the memory footprint. But - if those attributes are "created upon request" instead of "created upon init", then then the memory footrprint of the class should be lower. This is my sense of why Python handles bound functions this way, I'd appreciate more info.... – Pete Cacioppi Jul 07 '15 at 19:23
  • @PeteCacioppi, when you define the class with methods, the code for the methods needs to be stored *somewhere*. What additional information needs to be stored if we were to just construct the function object right then? – James Jul 07 '15 at 19:27
  • But the code for the function is stored just one time, even if there 1 million live instances. Whereas if I init an integer attribute to each instance, that's 1 million more integers. (No? How could it be otherwise?) – Pete Cacioppi Jul 07 '15 at 19:32

0 Answers0