Writing Non-Data Descriptor in Python
<p>I am learning about descriptors in python. I want to write a non-data descriptor but the class having the descriptor as its classmethod doesn't call the <code>__get__</code> spacial method when i call the classmethod. This is my example (without the <code>__set__</code>):</p>

<pre><code>class D(object):

    "The Descriptor"

    def __init__(self, x = 1395):
        self.x = x

    def __get__(self, instance, owner):
        print "getting", self.x
        return self.x


class C(object):

    d = D()

    def __init__(self, d):
        self.d = d
</code></pre>

<p>And here is how i call it:</p>

<pre><code>&gt;&gt;&gt;c = C(4)
&gt;&gt;&gt;c.d
4
</code></pre>

<p>The <code>__get__</code> of the descriptor class gets no call. But when i also set a <code>__set__</code> the descriptor seems to get activated:</p>

<pre><code>class D(object):

"The Descriptor"

    def __init__(self, x = 1395):
        self.x = x

    def __get__(self, instance, owner):
        print "getting", self.x
        return self.x

    def __set__(self, instance, value):
        print "setting", self.x
        self.x = value

class C(object):

    d = D()

    def __init__(self, d):
        self.d = d
</code></pre>

<p>Now i create a <code>C</code> instance:</p>

<pre><code>&gt;&gt;&gt; c=C(4)
setting 1395
&gt;&gt;&gt; c.d
getting 4
4
</code></pre>

<p>and both of <code>__get__, __set__</code> are present. It seems that i am missing some basic concepts about descriptors and how they can be used. Can anyone explain this behavior of <code>__get__, __set__</code>?</p>

<p>You successfully created a proper non-data descriptor, but you then <em>mask</em> the <code>d</code> attribute by setting an instance attribute.</p>

<p>Because it is a <em>non</em>-data descriptor, the instance attribute wins in this case. When you add a <code>__set__</code> method, you turn your descriptor into a data descriptor, and data descriptors are always applied even if there is an instance attribute.</p>

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

<blockquote>
  <p>The default behavior for attribute access is to get, set, or delete the attribute from an objectâs dictionary. For instance, <code>a.x</code> has a lookup chain starting with <code>a.__dict__['x']</code>, then <code>type(a).__dict__['x']</code>, and continuing through the base classes of <code>type(a)</code> excluding metaclasses. If the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined.</p>
</blockquote>

<p>and</p>

<blockquote>
  <p>If an object defines both <code>__get__()</code> and <code>__set__()</code>, it is considered a data descriptor. Descriptors that only define <code>__get__()</code> are called non-data descriptors (they are typically used for methods but other uses are possible).</p>
  
  <p>Data and non-data descriptors differ in how overrides are calculated with respect to entries in an instanceâs dictionary. If an instanceâs dictionary has an entry with the same name as a data descriptor, the data descriptor takes precedence. If an instanceâs dictionary has an entry with the same name as a non-data descriptor, the dictionary entry takes precedence.</p>
</blockquote>

<p>If you <em>remove</em> the <code>d</code> instance attribute (never set it or delete it from the instance), the descriptor object gets invoked:</p>

<pre><code>&gt;&gt;&gt; class D(object):
...     def __init__(self, x = 1395):
...         self.x = x
...     def __get__(self, instance, owner):
...         print "getting", self.x
...         return self.x
...
&gt;&gt;&gt; class C(object):
...     d = D()
...
&gt;&gt;&gt; c = C()
&gt;&gt;&gt; c.d
getting 1395
1395
</code></pre>

<p>Add an instance attribute again and the descriptor is ignored because the instance attribute wins:</p>

<pre><code>&gt;&gt;&gt; c.d = 42  # setting an instance attribute
&gt;&gt;&gt; c.d
42
&gt;&gt;&gt; del c.d   # deleting it again
&gt;&gt;&gt; c.d
getting 1395
1395
</code></pre>

<p>Also see the <a href="https://docs.python.org/2/reference/datamodel.html#invoking-descriptors" rel="nofollow"><em>Invoking Descriptors</em> documentation</a> in the Python <em>Datamodel</em> reference.</p>

python python-2.7 descriptor