Use of getattr on a class with data descriptor
<p>While using data descriptors in building classes, I came across a strange behavior of getattr function on a class.</p>

<pre><code># 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
</code></pre>

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

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

<p>That returns <code>None</code> because your <code>String.__get__()</code> method has no explicit <code>return</code> statement.</p>

<p>From the <a href="https://docs.python.org/3/howto/descriptor.html#invoking-descriptors" rel="nofollow"><em>Descriptor Howto</em></a>:</p>

<blockquote>
  <p>For classes, the machinery is in <code>type.__getattribute__()</code> which transforms <code>B.x</code> into <code>B.__dict__['x'].__get__(None, B)</code>.</p>
</blockquote>

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

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

<pre><code>class String(object):
    def __get__(self, instance, owner):
        if instance is None:
            return self
    def __set__(self, instance, value):
        pass
</code></pre>

<p>This is what the <code>property</code> descriptor object does.</p>

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

python python-2.7 class getattr