mirror of https://github.com/python/cpython
gh-101860: Expose __name__ on property (GH-101876)
Useful for introspection and consistent with functions and other descriptors.
This commit is contained in:
parent
9af80ec83d
commit
c0b0c2f201
|
@ -1004,31 +1004,42 @@ here is a pure Python equivalent:
|
|||
if doc is None and fget is not None:
|
||||
doc = fget.__doc__
|
||||
self.__doc__ = doc
|
||||
self._name = ''
|
||||
self._name = None
|
||||
|
||||
def __set_name__(self, owner, 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:
|
||||
return self
|
||||
if self.fget is None:
|
||||
raise AttributeError(
|
||||
f'property {self._name!r} of {type(obj).__name__!r} object has no getter'
|
||||
f'property {self.__name__!r} of {type(obj).__name__!r} '
|
||||
'object has no getter'
|
||||
)
|
||||
return self.fget(obj)
|
||||
|
||||
def __set__(self, obj, value):
|
||||
if self.fset is None:
|
||||
raise AttributeError(
|
||||
f'property {self._name!r} of {type(obj).__name__!r} object has no setter'
|
||||
f'property {self.__name__!r} of {type(obj).__name__!r} '
|
||||
'object has no setter'
|
||||
)
|
||||
self.fset(obj, value)
|
||||
|
||||
def __delete__(self, obj):
|
||||
if self.fdel is None:
|
||||
raise AttributeError(
|
||||
f'property {self._name!r} of {type(obj).__name__!r} object has no deleter'
|
||||
f'property {self.__name__!r} of {type(obj).__name__!r} '
|
||||
'object has no deleter'
|
||||
)
|
||||
self.fdel(obj)
|
||||
|
||||
|
|
|
@ -834,9 +834,8 @@ def _finddoc(obj):
|
|||
cls = self.__class__
|
||||
# Should be tested before isdatadescriptor().
|
||||
elif isinstance(obj, property):
|
||||
func = obj.fget
|
||||
name = 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):
|
||||
|
|
|
@ -127,9 +127,8 @@ def _finddoc(obj):
|
|||
cls = self.__class__
|
||||
# Should be tested before isdatadescriptor().
|
||||
elif isinstance(obj, property):
|
||||
func = obj.fget
|
||||
name = 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):
|
||||
|
|
|
@ -68,9 +68,9 @@ class FesteringGob(MalodorousPervert, ParrotDroppings):
|
|||
def abuse(self, a, b, c):
|
||||
pass
|
||||
|
||||
@property
|
||||
def contradiction(self):
|
||||
def _getter(self):
|
||||
pass
|
||||
contradiction = property(_getter)
|
||||
|
||||
async def lobbest(grenade):
|
||||
pass
|
||||
|
|
|
@ -201,6 +201,59 @@ class PropertyTests(unittest.TestCase):
|
|||
self.assertIsNone(prop.fdel)
|
||||
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
|
||||
|
||||
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.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()
|
||||
|
||||
|
|
|
@ -1162,6 +1162,17 @@ class PydocImportTest(PydocBaseTest):
|
|||
self.assertEqual(loaded_pydoc.__spec__, pydoc.__spec__)
|
||||
|
||||
|
||||
class Rect:
|
||||
@property
|
||||
def area(self):
|
||||
'''Area of the rect'''
|
||||
return self.w * self.h
|
||||
|
||||
|
||||
class Square(Rect):
|
||||
area = property(lambda self: self.side**2)
|
||||
|
||||
|
||||
class TestDescriptions(unittest.TestCase):
|
||||
|
||||
def test_module(self):
|
||||
|
@ -1550,13 +1561,13 @@ cm(x) class method of test.test_pydoc.test_pydoc.X
|
|||
|
||||
@requires_docstrings
|
||||
def test_property(self):
|
||||
class Rect:
|
||||
@property
|
||||
def area(self):
|
||||
'''Area of the rect'''
|
||||
return self.w * self.h
|
||||
|
||||
self.assertEqual(self._get_summary_lines(Rect.area), """\
|
||||
area
|
||||
Area of the rect
|
||||
""")
|
||||
# inherits the docstring from Rect.area
|
||||
self.assertEqual(self._get_summary_lines(Square.area), """\
|
||||
area
|
||||
Area of the rect
|
||||
""")
|
||||
self.assertIn("""
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Expose ``__name__`` attribute on property.
|
|
@ -1519,22 +1519,34 @@ class property(object):
|
|||
self.__doc__ = doc
|
||||
except AttributeError: # read-only or dict-less class
|
||||
pass
|
||||
self.__name = None
|
||||
|
||||
def __set_name__(self, owner, 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)
|
||||
|
||||
*/
|
||||
|
@ -1628,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)
|
||||
{
|
||||
|
@ -1637,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) {
|
||||
|
@ -1652,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;
|
||||
}
|
||||
|
@ -1673,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) {
|
||||
|
@ -1698,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;
|
||||
}
|
||||
|
@ -1883,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)
|
||||
{
|
||||
|
@ -1913,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,
|
||||
|
|
Loading…
Reference in New Issue