[Python-ideas] Would it possible to define abstract read/write properties with decorators?

Darren Dale dsdale24 at gmail.com
Sat Mar 19 16:29:23 CET 2011


On Fri, Mar 18, 2011 at 2:36 PM, Guido van Rossum <guido at python.org> wrote:
> On Fri, Mar 18, 2011 at 10:29 AM, Darren Dale <dsdale24 at gmail.com> wrote:
>> On Sun, Mar 13, 2011 at 12:49 PM, Darren Dale <dsdale24 at gmail.com> wrote:
>>> On Sun, Mar 13, 2011 at 11:18 AM, Darren Dale <dsdale24 at gmail.com> wrote:
>>> [...]
>>>> It seems like it should be possible for Python to support the
>>>> decorator syntax for declaring abstract read/write properties. The
>>>> most elegant approach might be the following, if it could be
>>>> supported:
>>>>
>>>> class Foo(metaclass=ABCMeta):
>>>>    # Note the use of @property rather than @abstractproperty:
>>>>    @property
>>>>    @abstractmethod
>>>>    def bar(self):
>>>>        return 1
>>>>    @bar.setter
>>>>    @abstractmethod
>>>>    def bar(self, val):
>>>>        pass
[...]
>> The modifications to "property" to better support abstract base
>> classes using the decorator syntax and @abstractmethod (rather than
>> @abstractproperty) are even simpler than I originally thought:
>>
>> class Property(property):
>>
>>    def __init__(self, *args, **kwargs):
>>        super(Property, self).__init__(*args, **kwargs)
>>        for f in (self.fget, self.fset, self.fdel):
>>            if getattr(f, '__isabstractmethod__', False):
>>                self.__isabstractmethod__ = True
>>                break
>>
>>>
>>> class C(metaclass=abc.ABCMeta):
>>>    @Property
>>>    @abc.abstractmethod
>>>    def x(self):
>>>        return 1
>>>    @x.setter
>>>    @abc.abstractmethod
>>>    def x(self, val):
>>>        pass
>>>
>>> try:
>>>    c=C()
>>> except TypeError as e:
>>>    print(e)
>>>
>>> class D(C):
>>>    @C.x.getter
>>>    def x(self):
>>>        return 2
>>>
>>> try:
>>>    d=D()
>>> except TypeError as e:
>>>    print(e)
>>>
>>> class E(D):
>>>    @D.x.setter
>>>    def x(self, val):
>>>        pass
>>>
>>> print(E())
>>>
>>
>> running this example yields:
>>
>> Can't instantiate abstract class C with abstract methods x
>> Can't instantiate abstract class D with abstract methods x
>> <__main__.E object at 0x212ee10>
>>
>> Wouldn't it be possible to include this in python-3.3?
>
> Sounds good to me.

I took a stab at this, but unfortunately I have not been able to
perform a complete build of python from the mercurial checkout on
either ubuntu 11.04 or OS X 10.6.6, for reasons that appear unrelated
to the changes below (undefined setlocale symbols on OS X, Could not
find platform dependent libraries <exec_prefix> segfault on ubuntu).
I'm an experienced python programmer, but not an experienced python
hacker. Would anyone care to comment on (or test) the changes?:

diff -r e34b09c69dd3 Objects/descrobject.c
--- a/Objects/descrobject.c     Sat Mar 12 22:31:06 2011 -0500
+++ b/Objects/descrobject.c     Sat Mar 19 11:22:14 2011 -0400
@@ -1117,6 +1121,7 @@
     PyObject *prop_set;
     PyObject *prop_del;
     PyObject *prop_doc;
+    PyObject *prop_isabstract;
     int getter_doc;
 } propertyobject;

@@ -1128,6 +1133,8 @@
     {"fset", T_OBJECT, offsetof(propertyobject, prop_set), READONLY},
     {"fdel", T_OBJECT, offsetof(propertyobject, prop_del), READONLY},
     {"__doc__",  T_OBJECT, offsetof(propertyobject, prop_doc), READONLY},
+    {"__isabstractmethod__",  T_OBJECT,
+     offsetof(propertyobject, prop_isabstract), READONLY},
     {0}
 };

@@ -1180,6 +1187,7 @@
     Py_XDECREF(gs->prop_set);
     Py_XDECREF(gs->prop_del);
     Py_XDECREF(gs->prop_doc);
+    Py_XDECREF(gs->prop_isabstract);
     self->ob_type->tp_free(self);
 }

@@ -1213,7 +1221,7 @@
         PyErr_SetString(PyExc_AttributeError,
                         value == NULL ?
                         "can't delete attribute" :
-                "can't set attribute");
+                        "can't set attribute");
         return -1;
     }
     if (value == NULL)
@@ -1263,6 +1271,21 @@
     return new;
 }

+static void
+property_identify_abstract_method(PyObject *self, PyObject *method)
+{
+    /* Set self.__isabstractmethod__ if method is abstract */
+    if (method != NULL){
+        PyObject *is_abstract = PyObject_GetAttrString(method,
+                                                       "__isabstractmethod__");
+        if (PyObject_IsTrue(is_abstract) > 0){
+            Py_INCREF(Py_True);
+            PyObject_SetAttrString(self, "__isabstractmethod__", Py_True);
+        }
+        Py_DECREF(is_abstract);
+    }
+}
+
 static int
 property_init(PyObject *self, PyObject *args, PyObject *kwds)
 {
@@ -1285,11 +1308,13 @@
     Py_XINCREF(set);
     Py_XINCREF(del);
     Py_XINCREF(doc);
+    Py_INCREF(Py_False);

     prop->prop_get = get;
     prop->prop_set = set;
     prop->prop_del = del;
     prop->prop_doc = doc;
+    prop->prop_isabstract = Py_False;
     prop->getter_doc = 0;

     /* if no docstring given and the getter has one, use that one */
@@ -1320,6 +1345,11 @@
         }
     }

+    /* set __isabstractmethod__ if fget, fset, or fdel are abstract methods */
+    property_identify_abstract_method(self, get);
+    property_identify_abstract_method(self, set);
+    property_identify_abstract_method(self, del);
+
     return 0;
 }



More information about the Python-ideas mailing list