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

Guido van Rossum guido at python.org
Sat Mar 19 17:24:53 CET 2011


Thanks much for your contribution! In order to get it reviewed and
submitted, can you please create a bug for this issue (mention the
python-ideas thread), upload your patch there, and perhaps ping
python-dev?

--Guido

On Sat, Mar 19, 2011 at 8:29 AM, Darren Dale <dsdale24 at gmail.com> wrote:
> 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;
>  }
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> http://mail.python.org/mailman/listinfo/python-ideas
>



-- 
--Guido van Rossum (python.org/~guido)



More information about the Python-ideas mailing list