Michael Michael - 17 days ago 6
Python Question

Descriptor protocol implementation of property()

The Python descriptor How-To describes how one could implement the

in terms of descriptors. I do not understand the reason of the first if-block in the
method. Under what circumstances will
? What is supposed to happen then? Why do the
methods not check for that?

Code is a bit lengthy, but it's probably better to give the full code rather than just a snippet. Questionable line is marked.

class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"

def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc

def __get__(self, obj, objtype=None):
# =====>>> What's the reason of this if block? <<<=====
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)

def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)

def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")

def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)

def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)

def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)


You can see what the effect is by making another version that leaves that test out. I made a class Property that uses the code you posted, and another BadProperty that leaves out that if block. Then I made this class:

class Foo(object):
    def good(self):
        print("In good getter")
        return "good"

    def good(self, val):
        print("In good setter")

    def bad(self):
        print("In bad getter")
        return "bad"

    def bad(self, val):
        print("In bad setter")

The similarities and differences can be seen in this example:

>>> x = Foo()

# same
>>> x.good
In good getter
>>> x.bad
In bad getter
>>> x.good = 2
In good setter
>>> x.bad = 2
In bad setter

# different!
>>> Foo.good
<__main__.Property object at 0x0000000002B71470>
>>> Foo.bad
In bad getter

The effect of the if block is to return the raw property object itself if it is accessed via the class. Without this check, the getter is called even when accessing the descriptor via the class.

The __set__ and __del__ methods do not need such a check, since the descriptor protocol is not invoke at all when setting/deleting attributes on a class (only on an instance). This is not totally obvious from the documentation, but can be seen in the difference between the description of __get__ vs. those of __set__/__del__ in the docs, where __get__ can get the attribute of "the owner class or an instance" but __set__/__del__ only set/delete the attribute on an instance.