getattr(foo, 'foobar') not the same as foo.foobar?

Arnaud Delobelle arnodel at googlemail.com
Thu Mar 13 23:52:45 CET 2008


On Mar 13, 10:06 pm, Dave Kuhlman <dkuhl... at rexx.com> wrote:
> The following code has me mystified:
>
> In [4]: class A(object):
>    ...:     def show(self):
>    ...:         print 'hello'
>    ...:
>    ...:
> In [5]: a = A()
> In [6]:
> In [7]: x = a.show
> In [8]: y = getattr(a, 'show')
> In [9]: x
> Out[9]: <bound method A.show of <__main__.A object at 0xb557d0>>
> In [10]: y
> Out[10]: <bound method A.show of <__main__.A object at 0xb557d0>>
> In [11]:
> In [12]: id(x)
> Out[12]: 12419552
> In [13]: id(y)
> Out[13]: 12419872
> In [14]:
> In [15]: x is y
> Out[15]: False
> In [16]:
> In [17]: x()
> hello
> In [18]: y()
> hello
>
> Basically, the above code is saying that foo.foobar is not the same as
> getattr(foo, 'foobar').
>
> But the documentation athttp://docs.python.org/lib/built-in-funcs.html#l2h-33
> says that they are equivalent.
>
> And, the following seems even worse:
>
>   >>> id(getattr(a, 'show')) == id(a.show)
>   True
>   >>> getattr(a, 'show') is a.show
>   False
>
> What gives?  This breaks my understanding of id(), the is operator, and
> getattr().
>
> Can someone help me make sense of this?

There are several misconceptions that contribute to your confusion I
think.

1.  This has nothing to do with getattr().  If you run the same code
as above, replacing getattr(a, 'show') with a.show you will get the
same results. E.g.

>>> class Foo(object):
...     def bar(self): pass
...
>>> id(foo.bar) == id(foo.bar) # (A)
True
>>> foo.bar is foo.bar # (B)
False
>>>

2.  The above is because two objects can have the same id if their
lifetimes don't overlap.  In (A) by the time the second foo.bar is
created, the first one is already dead.  So The second one takes its
place in memory, hence their ids are equal

3.  In (B) the first foo.bar is kept alive for comparing with the
second, hence they have a different id.

4.  Both points above follow from the fact that foo.bar is really a
function call that returns a (potentially) new object: in fact what
really happens is something like

    Foo.__dict__['bar'].__get__(foo, Foo).

So every time foo.bar is executed an object is (or may be) created,
with a new id.

HTH

--
Arnaud




More information about the Python-list mailing list