Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Fallback to fget.__name__ if name is not set
Raise AttributeError if no fget or it doesn't have `__name__`.
  • Loading branch information
eltoder committed Feb 20, 2024
commit c20e9a78fadfa8c58dda10adb9a07e627239e371
18 changes: 13 additions & 5 deletions Doc/howto/descriptor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1004,10 +1004,18 @@ here is a pure Python equivalent:
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
self.__name__ = None
self._name = None

def __set_name__(self, owner, name):
self.__name__ = name
self._name = name

@property
def __name__(self):
return self._name if self._name is not None else self.fget.__name__

@__name__.setter
def __name__(self, value):
self._name = value

def __get__(self, obj, objtype=None):
if obj is None:
Expand Down Expand Up @@ -1037,17 +1045,17 @@ here is a pure Python equivalent:

def getter(self, fget):
prop = type(self)(fget, self.fset, self.fdel, self.__doc__)
prop.__name__ = self.__name__
prop._name = self._name
return prop

def setter(self, fset):
prop = type(self)(self.fget, fset, self.fdel, self.__doc__)
prop.__name__ = self.__name__
prop._name = self._name
return prop

def deleter(self, fdel):
prop = type(self)(self.fget, self.fset, fdel, self.__doc__)
prop.__name__ = self.__name__
prop._name = self._name
return prop

.. testcode::
Expand Down
5 changes: 2 additions & 3 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -834,9 +834,8 @@ def _finddoc(obj):
cls = self.__class__
# Should be tested before isdatadescriptor().
elif isinstance(obj, property):
func = obj.fget
name = obj.__name__ or func.__name__
cls = _findclass(func)
name = obj.__name__
cls = _findclass(obj.fget)
if cls is None or getattr(cls, name) is not obj:
return None
elif ismethoddescriptor(obj) or isdatadescriptor(obj):
Expand Down
5 changes: 2 additions & 3 deletions Lib/pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,8 @@ def _finddoc(obj):
cls = self.__class__
# Should be tested before isdatadescriptor().
elif isinstance(obj, property):
func = obj.fget
name = obj.__name__ or func.__name__
cls = _findclass(func)
name = obj.__name__
cls = _findclass(obj.fget)
if cls is None or getattr(cls, name) is not obj:
return None
elif inspect.ismethoddescriptor(obj) or inspect.isdatadescriptor(obj):
Expand Down
41 changes: 37 additions & 4 deletions Lib/test/test_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,21 +205,54 @@ def test_property_name(self):
def getter(self):
return 42

def setter(self, value):
pass

class A:
@property
def foo(self):
return 1

@foo.setter
def oof(self, value):
pass

bar = property(getter)
baz = property(None, setter)

self.assertEqual(A.foo.__name__, 'foo')
self.assertEqual(A.oof.__name__, 'oof')
self.assertEqual(A.bar.__name__, 'bar')
self.assertEqual(A.baz.__name__, 'baz')

A.baz = property(getter)
self.assertIsNone(A.baz.__name__)
A.baz.__name__ = 'mybaz'
self.assertEqual(A.baz.__name__, 'mybaz')
A.quux = property(getter)
self.assertEqual(A.quux.__name__, 'getter')
A.quux.__name__ = 'myquux'
self.assertEqual(A.quux.__name__, 'myquux')
self.assertEqual(A.bar.__name__, 'bar') # not affected
A.quux.__name__ = None
self.assertIsNone(A.quux.__name__)

with self.assertRaisesRegex(
AttributeError, "'property' object has no attribute '__name__'"
):
property(None, setter).__name__

with self.assertRaisesRegex(
AttributeError, "'property' object has no attribute '__name__'"
):
property(1).__name__

class Err:
def __getattr__(self, attr):
raise RuntimeError('fail')

p = property(Err())
with self.assertRaisesRegex(RuntimeError, 'fail'):
p.__name__

p.__name__ = 'not_fail'
self.assertEqual(p.__name__, 'not_fail')

def test_property_set_name_incorrect_args(self):
p = property()
Expand Down
74 changes: 64 additions & 10 deletions Objects/descrobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1519,26 +1519,34 @@ class property(object):
self.__doc__ = doc
except AttributeError: # read-only or dict-less class
pass
self.__name__ = None
self.__name = None

def __set_name__(self, owner, name):
self.__name__ = name
self.__name = name

@property
def __name__(self):
return self.__name if self.__name is not None else self.fget.__name__

@__name__.setter
def __name__(self, value):
self.__name = value

def __get__(self, inst, type=None):
if inst is None:
return self
if self.__get is None:
raise AttributeError, "property has no getter"
raise AttributeError("property has no getter")
return self.__get(inst)

def __set__(self, inst, value):
if self.__set is None:
raise AttributeError, "property has no setter"
raise AttributeError("property has no setter")
return self.__set(inst, value)

def __delete__(self, inst):
if self.__del is None:
raise AttributeError, "property has no deleter"
raise AttributeError("property has no deleter")
return self.__del(inst)

*/
Expand All @@ -1551,7 +1559,6 @@ static PyMemberDef property_members[] = {
{"fset", _Py_T_OBJECT, offsetof(propertyobject, prop_set), Py_READONLY},
{"fdel", _Py_T_OBJECT, offsetof(propertyobject, prop_del), Py_READONLY},
{"__doc__", _Py_T_OBJECT, offsetof(propertyobject, prop_doc), 0},
{"__name__", _Py_T_OBJECT, offsetof(propertyobject, prop_name), 0},
{0}
};

Expand Down Expand Up @@ -1633,6 +1640,20 @@ property_dealloc(PyObject *self)
Py_TYPE(self)->tp_free(self);
}

static int
property_name(propertyobject *prop, PyObject **name)
{
if (prop->prop_name != NULL) {
*name = Py_NewRef(prop->prop_name);
return 1;
}
if (prop->prop_get == NULL) {
*name = NULL;
return 0;
}
return PyObject_GetOptionalAttr(prop->prop_get, &_Py_ID(__name__), name);
}

static PyObject *
property_descr_get(PyObject *self, PyObject *obj, PyObject *type)
{
Expand All @@ -1642,11 +1663,15 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type)

propertyobject *gs = (propertyobject *)self;
if (gs->prop_get == NULL) {
PyObject *propname;
if (property_name(gs, &propname) < 0) {
return NULL;
}
PyObject *qualname = PyType_GetQualName(Py_TYPE(obj));
if (gs->prop_name != NULL && qualname != NULL) {
if (propname != NULL && qualname != NULL) {
PyErr_Format(PyExc_AttributeError,
"property %R of %R object has no getter",
gs->prop_name,
propname,
qualname);
}
else if (qualname != NULL) {
Expand All @@ -1657,6 +1682,7 @@ property_descr_get(PyObject *self, PyObject *obj, PyObject *type)
PyErr_SetString(PyExc_AttributeError,
"property has no getter");
}
Py_XDECREF(propname);
Py_XDECREF(qualname);
return NULL;
}
Expand All @@ -1678,16 +1704,20 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value)
}

if (func == NULL) {
PyObject *propname;
if (property_name(gs, &propname) < 0) {
return -1;
}
PyObject *qualname = NULL;
if (obj != NULL) {
qualname = PyType_GetQualName(Py_TYPE(obj));
}
if (gs->prop_name != NULL && qualname != NULL) {
if (propname != NULL && qualname != NULL) {
PyErr_Format(PyExc_AttributeError,
value == NULL ?
"property %R of %R object has no deleter" :
"property %R of %R object has no setter",
gs->prop_name,
propname,
qualname);
}
else if (qualname != NULL) {
Expand All @@ -1703,6 +1733,7 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value)
"property has no deleter" :
"property has no setter");
}
Py_XDECREF(propname);
Py_XDECREF(qualname);
return -1;
}
Expand Down Expand Up @@ -1888,6 +1919,28 @@ property_init_impl(propertyobject *self, PyObject *fget, PyObject *fset,
return 0;
}

static PyObject *
property_get__name__(propertyobject *prop, void *Py_UNUSED(ignored))
{
PyObject *name;
if (property_name(prop, &name) < 0) {
return NULL;
}
if (name == NULL) {
PyErr_SetString(PyExc_AttributeError,
"'property' object has no attribute '__name__'");
}
return name;
}

static int
property_set__name__(propertyobject *prop, PyObject *value,
void *Py_UNUSED(ignored))
{
Py_XSETREF(prop->prop_name, Py_XNewRef(value));
return 0;
}

static PyObject *
property_get___isabstractmethod__(propertyobject *prop, void *closure)
{
Expand Down Expand Up @@ -1918,6 +1971,7 @@ property_get___isabstractmethod__(propertyobject *prop, void *closure)
}

static PyGetSetDef property_getsetlist[] = {
{"__name__", (getter)property_get__name__, (setter)property_set__name__},
{"__isabstractmethod__",
(getter)property_get___isabstractmethod__, NULL,
NULL,
Expand Down