From 66c1a525e08fda439ddda3b6371236df1398cfd5 Mon Sep 17 00:00:00 2001 From: Tim Peters Date: Mon, 24 Sep 2001 21:17:50 +0000 Subject: Make properties discoverable from Python: - 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 | 30 +++++++++++++++++-- Misc/NEWS | 6 ++++ Objects/descrobject.c | 81 ++++++++++++++++++++++++++++++++++---------------- 3 files changed, 90 insertions(+), 27 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index fdf2a41..5bd837e 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -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): diff --git a/Misc/NEWS b/Misc/NEWS index 7e0234f..3691616 100644 --- 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 diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 7be1074..fea67f3 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -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 */ -- cgit v0.12