-2

While using data descriptors in building classes, I came across a strange behavior of getattr function on a class.

# this is a data descriptor
class String(object):
    def __get__(self, instance, owner):
        pass
    def __set__(self, instance, value):
        pass

# This defines a class A with 'dot' notation support for attribute 'a'
class A(object):
    a = String()

obj = A()
assert getattr(A, 'a') is A.__dict__['a']
# This raises AssertionError

LHS return an empty string, while the RHS returns an instance of String. I thought getattr on an object was to get the value for the key inside the __dict__. How does getattr function work on a class object?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Jaewan Kim
  • 45
  • 5
  • Are you sure that it returns an empty string and not `None`? – Bakuriu Oct 05 '16 at 15:07
  • *LHS return an empty string* Sure? As I am trying, it returns `None`. And why shouldn't it? You tell it so in the descriptor! – glglgl Oct 05 '16 at 15:07
  • 1
    BTW: `A.a` is **not** the same as `A.__dict__['a']`. Only the first triggers "the whole attribute mechanism". – Bakuriu Oct 05 '16 at 15:09
  • So `getattr(obj, 'a')` and getattr(A, 'a')` are somewhat similar? It turns out the descriptor had a default value of '' set in my case. So the only way to recover the descriptor object is to inspect the class' `__dict__` directly? – Jaewan Kim Oct 05 '16 at 15:43

2 Answers2

3

getattr(A, 'a') triggers the descriptor protocol, even on classes, so String.__get__(None, A) is called.

That returns None because your String.__get__() method has no explicit return statement.

From the Descriptor Howto:

For classes, the machinery is in type.__getattribute__() which transforms B.x into B.__dict__['x'].__get__(None, B).

getattr(A, 'a') is just a dynamic from of A.a here, so A.__dict__['x'].__get__(None, A) is executed, which is why you don't get the same thing as A.__dict__['x'].

If you expected it to return the descriptor object itself, you'll have to do so explicitly; instance will be set to None in that case:

class String(object):
    def __get__(self, instance, owner):
        if instance is None:
            return self
    def __set__(self, instance, value):
        pass

This is what the property descriptor object does.

Note that the owner argument to descriptor.__get__ is optional; if not set you are supposed to use type(instance) instead.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
0

getattr(A, 'a') is the same as A.a. This calls the respective descriptor, if present. So it provides the value presented by the descriptor, which is None.

glglgl
  • 89,107
  • 13
  • 149
  • 217