Unexpected behaviour of getattr(obj, __dict__)

S

Steven D'Aprano

I came across this unexpected behaviour of getattr for new style classes.
Example:
.... thing = [1,2,3]
....False

I would have expected that the object returned by getattr would be the
same object as the object returned by standard attribute access. This is
true for some attributes, but not for __dict__. I don't know if there are
other examples.

Why is this? Is there some documentation I can read up about this? I've
tried searching, but can't find anything useful.
 
R

Raymond Hettinger

Steven said:
I came across this unexpected behaviour of getattr for new style classes.
Example:
... thing = [1,2,3]
...False

I would have expected that the object returned by getattr would be the
same object as the object returned by standard attribute access.

The returned object is a wrapper created on-the-fly as needed. You've
requested two of them and each wrapper has a different object id but
wraps an identical source.

Contemplate this for a bit:
thing = [1,2,3]
def f(self): pass
False


The reason why is evident when you check the object representation. It
shows that your lookup returned a wrapper:
<dictproxy object at 0x00C41770>

IOW, attribute lookup can do more than just return the result of a
straight-lookup. The underlying mechanism is a deep and interesting
subject. If you want to know more, try this link:

http://users.rcn.com/python/download/Descriptor.htm

Raymond
 
S

Steven D'Aprano

Steven said:
I came across this unexpected behaviour of getattr for new style classes.
Example:

class Parrot(object):

... thing = [1,2,3]
...
getattr(Parrot, "thing") is Parrot.thing
True

getattr(Parrot, "__dict__") is Parrot.__dict__
False


hint:
getattr(object, '__dict__')
<dictproxy object at 0x2aaaaab2ff30>

That doesn't answer the question, it just re-words it. Why is the
dictproxy returned by getattr a different instance from the dictproxy that
you get when you say object.__dict__?
 
S

Steven D'Aprano

Steven said:
I came across this unexpected behaviour of getattr for new style classes.
Example:
class Parrot(object):
... thing = [1,2,3]
...
getattr(Parrot, "thing") is Parrot.thing True
getattr(Parrot, "__dict__") is Parrot.__dict__
False

I would have expected that the object returned by getattr would be the
same object as the object returned by standard attribute access.

The returned object is a wrapper created on-the-fly as needed. You've
requested two of them and each wrapper has a different object id but
wraps an identical source.

[penny drops]

That would certainly explain it.

Is there a canonical list of attributes which are wrapped in this way? I
suppose not... it is probably the sort of thing which is subject to change
as Python evolves.

I knew methods were wrapped, but I didn't know they were wrapped on the
fly, nor did I connect the two phenomena. Thank you for the concise and
simple answer.
 
C

Carl Banks

Raymond said:
Steven said:
I came across this unexpected behaviour of getattr for new style classes.
Example:
class Parrot(object):
... thing = [1,2,3]
...
getattr(Parrot, "thing") is Parrot.thing True
getattr(Parrot, "__dict__") is Parrot.__dict__
False

I would have expected that the object returned by getattr would be the
same object as the object returned by standard attribute access.

The returned object is a wrapper created on-the-fly as needed. You've
requested two of them and each wrapper has a different object id but
wraps an identical source.

Contemplate this for a bit:
thing = [1,2,3]
def f(self): pass
False


Yes, in fact getattr has nothing to do with it. To wit:
False

But wait, it gets weirder.
True

But that's not all. Redefine Parrot as:

class Parrot(object):
def f(self): pass
def g(self): pass

Then,
True


your-milage-may-vary-ly yr's,

Carl Banks
 
F

Fredrik Lundh

Steven said:
class Parrot(object):

... thing = [1,2,3]
...

getattr(Parrot, "thing") is Parrot.thing

True

getattr(Parrot, "__dict__") is Parrot.__dict__

False

hint:
getattr(object, '__dict__')
<dictproxy object at 0x2aaaaab2ff30>

That doesn't answer the question, it just re-words it. Why is the
dictproxy returned by getattr a different instance from the dictproxy
that you get when you say object.__dict__?

because it's created on the fly:
<dictproxy object at 0x009818B0>

the object itself contains a dictionary.

</F>
 
T

Terry Reedy

Carl Banks said:
But wait, it gets weirder.

Not weird at all. Just an artifact of CPython's storage recycling
algorithm.

A wrapper is created and passed to id() which returns an int object while
releasing the wrapper back to the free list. Then another wrapper is
created in the same chunk of memory and passed to id, which returns an int
of the same value for comparison. The language ref only guarantees
uniqueness of ids at any particular instant and allows reuse of ids of
deallocated objects.

I half seriously think the lib ref entry for id() should have a warning
that naive use can mislead.
But that's not all. Redefine Parrot as:

class Parrot(object):
def f(self): pass
def g(self): pass

Then,

True

Same thing. The wrapper content is not relevant as long as it uses the
same memory block.

Terry Jan Reedy
 
C

Christos Georgiou

A wrapper is created and passed to id() which returns an int object while
releasing the wrapper back to the free list. Then another wrapper is
created in the same chunk of memory and passed to id, which returns an int
of the same value for comparison. The language ref only guarantees
uniqueness of ids at any particular instant and allows reuse of ids of
deallocated objects.

I half seriously think the lib ref entry for id() should have a warning
that naive use can mislead.

Actually, you more-or-less just wrote what could (I also think should) be
included in the docs.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top