]> granicus.if.org Git - python/commitdiff
Make properties discoverable from Python:
authorTim Peters <tim.peters@gmail.com>
Mon, 24 Sep 2001 21:17:50 +0000 (21:17 +0000)
committerTim Peters <tim.peters@gmail.com>
Mon, 24 Sep 2001 21:17:50 +0000 (21:17 +0000)
- property() now takes 4 keyword arguments:  fget, fset, fdel, doc.
  Note that the real purpose of the 'f' prefix is to make fdel fit in
  ('del' is a keyword, so can't used as a keyword argument name).

- These map to visible readonly attributes 'fget', 'fset', 'fdel',
  and '__doc__' in the property object.

- fget/fset/fdel weren't discoverable from Python before.

- __doc__ is new, and allows to associate a docstring with a property.

Lib/test/test_descr.py
Misc/NEWS
Objects/descrobject.c

index fdf2a41dee4e1d38f3037d4c15d97755a315bed0..5bd837e6f27bbad5c2bbb2d924b50948c099d017 100644 (file)
@@ -1364,7 +1364,7 @@ def properties():
             self.__x = value
         def delx(self):
             del self.__x
-        x = property(getx, setx, delx)
+        x = property(getx, setx, delx, doc="I'm the x property.")
     a = C()
     verify(not hasattr(a, "x"))
     a.x = 42
@@ -1378,6 +1378,32 @@ def properties():
 ##    C.x.__set__(a)
 ##    verify(not hasattr(a, "x"))
 
+    raw = C.__dict__['x']
+    verify(isinstance(raw, property))
+
+    attrs = dir(raw)
+    verify("__doc__" in attrs)
+    verify("fget" in attrs)
+    verify("fset" in attrs)
+    verify("fdel" in attrs)
+
+    verify(raw.__doc__ == "I'm the x property.")
+    verify(raw.fget is C.__dict__['getx'])
+    verify(raw.fset is C.__dict__['setx'])
+    verify(raw.fdel is C.__dict__['delx'])
+
+    for attr in "__doc__", "fget", "fset", "fdel":
+        try:
+            setattr(raw, attr, 42)
+        except TypeError, msg:
+            if str(msg).find('readonly') < 0:
+                raise TestFailed("when setting readonly attr %r on a "
+                                 "property, got unexpected TypeError "
+                                 "msg %r" % (attr, str(msg)))
+        else:
+            raise TestFailed("expected TypeError from trying to set "
+                             "readonly %r attr on a property" % attr)
+
 def supers():
     if verbose: print "Testing super..."
 
@@ -1884,7 +1910,7 @@ def rich_comparisons():
     zz = ZZ(1.0000003)
     verify(zz == 1+0j)
     verify(1+0j == zz)
-            
+
     class classic:
         pass
     for base in (classic, int, object, list):
index 7e0234f8d44ba2525df3ad10dfb77758fec7c525..36916165f6650121ca1f82580d952941ec9788be 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -3,6 +3,12 @@ What's New in Python 2.2a4?
 
 Core
 
+- property() now takes 4 keyword arguments:  fget, fset, fdel and doc.
+  These map to readonly attributes 'fget', 'fset', 'fdel', and '__doc__'
+  in the constructed property object.  fget, fset and fdel weren't
+  discoverable from Python in 2.2a3.  __doc__ is new, and allows to
+  associate a docstring with a property.
+
 - file.writelines() now accepts any iterable object producing strings.
 
 - PyUnicode_FromEncodedObject() now works very much like
index 7be1074779188bf544ccd59622d8ea2e42dce7bf..fea67f3f9f60aac799b50b7a969301c081fd1994 100644 (file)
@@ -873,9 +873,11 @@ PyWrapper_New(PyObject *d, PyObject *self)
 /*
     class property(object):
 
-       def __init__(self, get=None, set=None):
-           self.__get = get
-           self.__set = set
+       def __init__(self, fget=None, fset=None, fdel=None, doc=None):
+           self.__get = fget
+           self.__set = fset
+           self.__del = fdel
+           self.__doc__ = doc
 
        def __get__(self, inst, type=None):
            if self.__get is None:
@@ -885,26 +887,42 @@ PyWrapper_New(PyObject *d, PyObject *self)
            return self.__get(inst)
 
        def __set__(self, inst, value):
-           if self.__set is None:
-               raise AttributeError, "unsettable attribute"
-           return self.__set(inst, value)
+           if value is None:
+               if self.__del is None:
+                   raise AttributeError, "can't delete attribute"
+               return self.__del(inst)
+           else:
+               if self.__set is None:
+                   raise AttributeError, "can't set attribute"
+               return self.__set(inst, value)
 */
 
 typedef struct {
        PyObject_HEAD
-       PyObject *get;
-       PyObject *set;
-       PyObject *del;
+       PyObject *prop_get;
+       PyObject *prop_set;
+       PyObject *prop_del;
+       PyObject *prop_doc;
 } propertyobject;
 
+static PyMemberDef property_members[] = {
+       {"fget", T_OBJECT, offsetof(propertyobject, prop_get), READONLY},
+       {"fset", T_OBJECT, offsetof(propertyobject, prop_set), READONLY},
+       {"fdel", T_OBJECT, offsetof(propertyobject, prop_del), READONLY},
+       {"__doc__",  T_OBJECT, offsetof(propertyobject, prop_doc), READONLY},
+       {0}
+};
+
+
 static void
 property_dealloc(PyObject *self)
 {
        propertyobject *gs = (propertyobject *)self;
 
-       Py_XDECREF(gs->get);
-       Py_XDECREF(gs->set);
-       Py_XDECREF(gs->del);
+       Py_XDECREF(gs->prop_get);
+       Py_XDECREF(gs->prop_set);
+       Py_XDECREF(gs->prop_del);
+       Py_XDECREF(gs->prop_doc);
        self->ob_type->tp_free(self);
 }
 
@@ -913,7 +931,7 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type)
 {
        propertyobject *gs = (propertyobject *)self;
 
-       if (gs->get == NULL) {
+       if (gs->prop_get == NULL) {
                PyErr_SetString(PyExc_AttributeError, "unreadable attribute");
                return NULL;
        }
@@ -921,7 +939,7 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type)
                Py_INCREF(self);
                return self;
        }
-       return PyObject_CallFunction(gs->get, "(O)", obj);
+       return PyObject_CallFunction(gs->prop_get, "(O)", obj);
 }
 
 static int
@@ -931,9 +949,9 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value)
        PyObject *func, *res;
 
        if (value == NULL)
-               func = gs->del;
+               func = gs->prop_del;
        else
-               func = gs->set;
+               func = gs->prop_set;
        if (func == NULL) {
                PyErr_SetString(PyExc_AttributeError,
                                value == NULL ?
@@ -954,32 +972,45 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value)
 static int
 property_init(PyObject *self, PyObject *args, PyObject *kwds)
 {
-       PyObject *get = NULL, *set = NULL, *del = NULL;
+       PyObject *get = NULL, *set = NULL, *del = NULL, *doc = NULL;
+       static char *kwlist[] = {"fget", "fset", "fdel", "doc", 0};
        propertyobject *gs = (propertyobject *)self;
 
-       if (!PyArg_ParseTuple(args, "|OOO:property", &get, &set, &del))
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOO:property",
+                                        kwlist, &get, &set, &del, &doc))
                return -1;
+
        if (get == Py_None)
                get = NULL;
        if (set == Py_None)
                set = NULL;
+       if (del == Py_None)
+               del = NULL;
+
        Py_XINCREF(get);
        Py_XINCREF(set);
        Py_XINCREF(del);
-       gs->get = get;
-       gs->set = set;
-       gs->del = del;
+       Py_XINCREF(doc);
+
+       gs->prop_get = get;
+       gs->prop_set = set;
+       gs->prop_del = del;
+       gs->prop_doc = doc;
+
        return 0;
 }
 
 static char property_doc[] =
-"property([getfunc[, setfunc[, delfunc]]]) -> property attribute\n"
-"Typical use to define a managed attribute x of C instances:\n"
+"property(fget=None, fset=None, fdel=None, doc=None) -> property attribute\n"
+"\n"
+"fget is a function to be used for getting an attribute value, and likewise\n"
+"fset is a function for setting, and fdel a function for del'ing, an\n"
+"attribute.  Typical use is to define a managed attribute x:\n"
 "class C(object):\n"
 "    def getx(self): return self.__x\n"
 "    def setx(self, value): self.__x = value\n"
 "    def delx(self): del self.__x\n"
-"    x = property(getx, setx, delx)";
+"    x = property(getx, setx, delx, \"I'm the 'x' property.\")";
 
 PyTypeObject PyProperty_Type = {
        PyObject_HEAD_INIT(&PyType_Type)
@@ -1012,7 +1043,7 @@ PyTypeObject PyProperty_Type = {
        0,                                      /* tp_iter */
        0,                                      /* tp_iternext */
        0,                                      /* tp_methods */
-       0,                                      /* tp_members */
+       property_members,                       /* tp_members */
        0,                                      /* tp_getset */
        0,                                      /* tp_base */
        0,                                      /* tp_dict */