Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 418befd

Browse files
authored
gh-98963: Restore the ability to have a dict-less property. (#105262)
Ignore doc string assignment failures in `property` as has been the behavior of all past Python releases.
1 parent 1237fb6 commit 418befd

File tree

3 files changed

+97
-13
lines changed

3 files changed

+97
-13
lines changed

Lib/test/test_property.py

+56-5
Original file line numberDiff line numberDiff line change
@@ -246,16 +246,67 @@ class PropertySubSlots(property):
246246
class PropertySubclassTests(unittest.TestCase):
247247

248248
def test_slots_docstring_copy_exception(self):
249-
try:
249+
# A special case error that we preserve despite the GH-98963 behavior
250+
# that would otherwise silently ignore this error.
251+
# This came from commit b18500d39d791c879e9904ebac293402b4a7cd34
252+
# as part of https://bugs.python.org/issue5890 which allowed docs to
253+
# be set via property subclasses in the first place.
254+
with self.assertRaises(AttributeError):
250255
class Foo(object):
251256
@PropertySubSlots
252257
def spam(self):
253258
"""Trying to copy this docstring will raise an exception"""
254259
return 1
255-
except AttributeError:
256-
pass
257-
else:
258-
raise Exception("AttributeError not raised")
260+
261+
def test_property_with_slots_no_docstring(self):
262+
# https://github.com/python/cpython/issues/98963#issuecomment-1574413319
263+
class slotted_prop(property):
264+
__slots__ = ("foo",)
265+
266+
p = slotted_prop() # no AttributeError
267+
self.assertIsNone(getattr(p, "__doc__", None))
268+
269+
def undocumented_getter():
270+
return 4
271+
272+
p = slotted_prop(undocumented_getter) # New in 3.12: no AttributeError
273+
self.assertIsNone(getattr(p, "__doc__", None))
274+
275+
@unittest.skipIf(sys.flags.optimize >= 2,
276+
"Docstrings are omitted with -O2 and above")
277+
def test_property_with_slots_docstring_silently_dropped(self):
278+
# https://github.com/python/cpython/issues/98963#issuecomment-1574413319
279+
class slotted_prop(property):
280+
__slots__ = ("foo",)
281+
282+
p = slotted_prop(doc="what's up") # no AttributeError
283+
self.assertIsNone(p.__doc__)
284+
285+
def documented_getter():
286+
"""getter doc."""
287+
return 4
288+
289+
# Historical behavior: A docstring from a getter always raises.
290+
# (matches test_slots_docstring_copy_exception above).
291+
with self.assertRaises(AttributeError):
292+
p = slotted_prop(documented_getter)
293+
294+
@unittest.skipIf(sys.flags.optimize >= 2,
295+
"Docstrings are omitted with -O2 and above")
296+
def test_property_with_slots_and_doc_slot_docstring_present(self):
297+
# https://github.com/python/cpython/issues/98963#issuecomment-1574413319
298+
class slotted_prop(property):
299+
__slots__ = ("foo", "__doc__")
300+
301+
p = slotted_prop(doc="what's up")
302+
self.assertEqual("what's up", p.__doc__) # new in 3.12: This gets set.
303+
304+
def documented_getter():
305+
"""what's up getter doc?"""
306+
return 4
307+
308+
p = slotted_prop(documented_getter)
309+
self.assertEqual("what's up getter doc?", p.__doc__)
259310

260311
@unittest.skipIf(sys.flags.optimize >= 2,
261312
"Docstrings are omitted with -O2 and above")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Restore the ability for a subclass of :class:`property` to define ``__slots__``
2+
or otherwise be dict-less by ignoring failures to set a docstring on such a
3+
class. This behavior had regressed in 3.12beta1. An :exc:`AttributeError`
4+
where there had not previously been one was disruptive to existing code.

Objects/descrobject.c

+37-8
Original file line numberDiff line numberDiff line change
@@ -1485,7 +1485,10 @@ class property(object):
14851485
self.__get = fget
14861486
self.__set = fset
14871487
self.__del = fdel
1488-
self.__doc__ = doc
1488+
try:
1489+
self.__doc__ = doc
1490+
except AttributeError: # read-only or dict-less class
1491+
pass
14891492
14901493
def __get__(self, inst, type=None):
14911494
if inst is None:
@@ -1791,6 +1794,19 @@ property_init_impl(propertyobject *self, PyObject *fget, PyObject *fset,
17911794
if (rc <= 0) {
17921795
return rc;
17931796
}
1797+
if (!Py_IS_TYPE(self, &PyProperty_Type) &&
1798+
prop_doc != NULL && prop_doc != Py_None) {
1799+
// This oddity preserves the long existing behavior of surfacing
1800+
// an AttributeError when using a dict-less (__slots__) property
1801+
// subclass as a decorator on a getter method with a docstring.
1802+
// See PropertySubclassTest.test_slots_docstring_copy_exception.
1803+
int err = PyObject_SetAttr(
1804+
(PyObject *)self, &_Py_ID(__doc__), prop_doc);
1805+
if (err < 0) {
1806+
Py_DECREF(prop_doc); // release our new reference.
1807+
return -1;
1808+
}
1809+
}
17941810
if (prop_doc == Py_None) {
17951811
prop_doc = NULL;
17961812
Py_DECREF(Py_None);
@@ -1806,19 +1822,32 @@ property_init_impl(propertyobject *self, PyObject *fget, PyObject *fset,
18061822
if (Py_IS_TYPE(self, &PyProperty_Type)) {
18071823
Py_XSETREF(self->prop_doc, prop_doc);
18081824
} else {
1809-
/* If this is a property subclass, put __doc__
1810-
in dict of the subclass instance instead,
1811-
otherwise it gets shadowed by __doc__ in the
1812-
class's dict. */
1825+
/* If this is a property subclass, put __doc__ in the dict
1826+
or designated slot of the subclass instance instead, otherwise
1827+
it gets shadowed by __doc__ in the class's dict. */
18131828

18141829
if (prop_doc == NULL) {
18151830
prop_doc = Py_NewRef(Py_None);
18161831
}
18171832
int err = PyObject_SetAttr(
18181833
(PyObject *)self, &_Py_ID(__doc__), prop_doc);
1819-
Py_XDECREF(prop_doc);
1820-
if (err < 0)
1821-
return -1;
1834+
Py_DECREF(prop_doc);
1835+
if (err < 0) {
1836+
assert(PyErr_Occurred());
1837+
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
1838+
PyErr_Clear();
1839+
// https://github.com/python/cpython/issues/98963#issuecomment-1574413319
1840+
// Python silently dropped this doc assignment through 3.11.
1841+
// We preserve that behavior for backwards compatibility.
1842+
//
1843+
// If we ever want to deprecate this behavior, only raise a
1844+
// warning or error when proc_doc is not None so that
1845+
// property without a specific doc= still works.
1846+
return 0;
1847+
} else {
1848+
return -1;
1849+
}
1850+
}
18221851
}
18231852

18241853
return 0;

0 commit comments

Comments
 (0)