2

I've stumbled upon a curious behavior in python while trying to implement an object wrapper. Consider the following code (I've simplified it from the case I've encountered to show its very basics):

class A(object):
    def hello(self):
        print "hello ",

class B(object):
    def __init__(self, obj):
        self.obj = obj
    def __getattr__(self, attr):
        return getattr(self.obj, attr)
    def regular_func(self, attr):
        return getattr(self.obj, attr)
a=A()
b=B(a)

for x in xrange(10):
    print id(b.hello), ",",
print
for x in xrange(10):
    print id(a.hello), ",",
print
for x in xrange(10):
    print id(A.hello), ",",
print
for x in xrange(10):
    print id(b.regular_func("hello")), ",",
print
for x in xrange(10):
    b.hello()

The output of the above script will be:

4375335072 , 4375100144 , 4375335072 , 4375100144 , 4375335072 , 4375100144 , 4375335072 , 4375100144 , 4375335072 , 4375100144 ,
4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 ,
4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 ,
4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 , 4375100144 ,
hello  hello  hello  hello  hello  hello  hello  hello  hello  hello

Notice how the ids toggle between two values whenever the __getattr__ is invoked (the first row printed)? I've messed around with it a bit, this happens for __getattribute__ as well, but notice it never happens when calling regular_func. It always toggles between two values, and always toggles (not randomly).

This still happens even when I change the __getattr__ to such:

a=A()
class B(object):
    def __getattr__(self, attr):
        return getattr(a, attr)

Or as such:

a=A()
class B(object):
    def __getattr__(self, attr):
        return a.__getattribute__(attr)

And in the above example, repeatedly printing id(a.__getattribute__("hello")) always gives the same number.

Can anyone explain this peculiar behavior, or suggest any further ideas to investigate?

micromoses
  • 6,747
  • 2
  • 20
  • 29
  • wow, that looks like some CPython PyObject caching madness.. interesting. Have you asked in `#python` on `irc.freenode.net` ? – Marcus Müller Apr 15 '15 at 15:10
  • 1
    Isn't `b.hello` just a bound method? what do you expect its id() to be? The boundmethod object gets created each time you access `b.hello`, then freed. Even if you replace it with `id(b.hello())`, you just get the id() of None (because b.hello() returns None). – shx2 Apr 15 '15 at 15:11
  • @shx2 `b.hello` isn't bound to `b`, and notice that it toggles the id between the id of `a.hello` and something else.. – micromoses Apr 15 '15 at 15:16
  • It's bound to `a`. What's the difference? – shx2 Apr 15 '15 at 15:18
  • 2
    http://stackoverflow.com/questions/4639908/why-is-a-method-not-identical-to-itself – Ignacio Vazquez-Abrams Apr 15 '15 at 15:21
  • @shx2 the difference is that `id(a.hello)` always yields the same value, whereas sometimes `id(a.hello) == id(b.hello)`, and sometimes it isn't. – micromoses Apr 15 '15 at 15:29
  • @IgnacioVazquez-Abrams Thanks, that's a good explanation, but then I would expect all the ids I get to be random, but they aren't. The behavior is quite consistent, so I'm still confused. I know now not to rely on ids for comparison of methods, but I would much rather understand what happens underneath the hood. – micromoses Apr 15 '15 at 15:33
  • They won't be random under CPython since the result of `id()` is the memory address of the object. They will be arbitrary but deterministic. – Ignacio Vazquez-Abrams Apr 15 '15 at 15:46
  • How are you planning to use those id()'s? If you're not, why are you trying to make sense of them? – shx2 Apr 15 '15 at 16:27
  • @shx2 I don't plan to use the ids, I was trying to figure out why `a.hello is not b.hello`, which got me to investigate the ids. And I'm always trying to make sense of things, as the deeper you dive the more you know. Knowledge is power. Information is liberating. Education is the premise of progress. The pen is mightier than the sword. And some other cliches ;) – micromoses Apr 15 '15 at 16:53
  • I tried this example out myself and I can confirm that the id of b.hello does alternate. But comparisons seem to *never* alternate. What's even more mind boggling is the following: `>>> c = id(b.hello); c == id(a.hello) True >>> id(a.hello) == id(b.hello) False ` Both statements are consistent no matter how many times you run them. – EricR Apr 15 '15 at 16:55
  • 1
    @micromoses information about bound methods is liberating indeed! :) – shx2 Apr 15 '15 at 17:43

0 Answers0