8

The result of the below code boggles me:

class MyClass(type):
    @property
    def a(self):
        return 1

class MyObject(object):
    __metaclass__ = MyClass

    a = 2

print MyObject.a
print object.__getattribute__(MyObject, 'a')
print type.__getattribute__(MyObject, 'a')
print MyObject.__dict__['a']
print MyObject().a

I really expect this to just print 2 repeatedly, but it prints 1 1 1 2 2. Is there a way this makes any intuitive sense?


To clarify: I understand that this behavior is well documented (here, "data descriptors"), but I want to have an understanding of why this makes sense, and why the core devs implemented descriptors this way.

bukzor
  • 37,539
  • 11
  • 77
  • 111
  • `a` in `MyObject` is not an attribute, just variable in `MyObject` 'namespace'. Try `MyObject.a = 2` to see the difference (though it won't be listed then in `__dict__` – m.wasowski Mar 23 '14 at 02:38
  • @m.wasowski: `a` **is** an attribute on the `MyObject` class. The fact that the `property` object is invoked when looking it up doesn't change that, nor that the `a` entry in `MyObject.__dict__` is ignored.. – Martijn Pieters Mar 23 '14 at 02:50
  • @MartijnPieters: Is there any record of the rational or any debate which occurred when this was implemented? It would be a very interesting read. – bukzor Mar 23 '14 at 02:56
  • @m.wasowski: see http://docs.python.org/2/reference/datamodel.html#invoking-descriptors; data descriptors *have* to intercept early otherwise assignment and deletion wouldn't work correctly. – Martijn Pieters Mar 23 '14 at 02:59

2 Answers2

3

Properties are data descriptors; in attribute lookups, they take priority over identically-named entries in the dict of an instance of the class with the property. That means that with

MyObject.a

the a property in MyClass takes priority over the a entry in MyObject's dict. Similarly,

object.__getattribute__(MyObject, 'a')
type.__getattribute__(MyObject, 'a')

object.__getattribute__ and type.__getattribute__ both respect the priority of data descriptors over instance dict entries, so the property wins.

On the other hand,

MyObject.__dict__['a']

this explicitly does a dict lookup. It only sees things in MyObject's dict, ignoring the normal attribute lookup mechanisms.

For the last line:

MyObject().a

the MyClass descriptor only applies to instances of MyClass, not instances of its instances. The attribute lookup mechanism doesn't see the property.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • 1
    So you've shown that this behavior is well documented. Thanks. But how does it make any sense? Why was it implemented this way? – bukzor Mar 23 '14 at 02:41
  • @bukzor: I don't really know. Once you know about the descriptor protocol and how data descriptors win over the instance dict, the rest of the behavior logically follows, but I'm not sure why that rule about data descriptors exists. My best guess is that it'd be surprising if when you deleted the attribute in the instance dict, the property suddenly took over. – user2357112 Mar 23 '14 at 02:56
  • Found it: [here](http://stackoverflow.com/questions/13007179#comment-17717816) @MartijnPieters indicates "the short of it is so that you cannot override static attributes like `__class__` in the instance `__dict__`." – bukzor Mar 23 '14 at 03:00
-1

Here is some very similar code that might help you understand what's happening

class MyClass(object):
    def __init__(self, data):
        self.__dict__.update(data) # this would fail if I had self.a = a

    @property
    def a(self):
        return 1

MyObject = MyClass({'a': 2})

print MyObject.a
print object.__getattribute__(MyObject, 'a')
print MyObject.__dict__['a']

What you're seeing is that the instance (the class) having both a descriptor (your property) on its class and an attribute of the same name. Normally, there are safeguards to protect this, but the way type works goes around them.

So you have

 print MyObject.a

The descriptor beats the __dict__ entry and the property is called. This is because of the implementation of object.__getattribute__, at least conceptually.

 print object.__getattribute__(MyObject, 'a')

This is the same thing as saying MyObject.a, except if object.__getattribute__ was overridden. __getattribute__ is where the behavior of trying descriptors first comes from.

 print type.__getattribute__(MyObject, 'a')

This is the same thing as object.__getattribute__ because type doesn't override __getattribute__.

 print MyObject.__dict__['a']

This looks it up in your __dict__, which is the only place you stored 2. This is just a dict object (maybe/almost), so it's not going to look stuff up anywhere else.

 print MyObject().a

The way that attribute works, you're not accessing your type's attributes the same way you would directly. This is probably the part that doesn't have a super-intuitive answer

Mike Graham
  • 73,987
  • 14
  • 101
  • 130