8000 gh-98963: Restore the ability to have a dict-less property. by gpshead · Pull Request #105262 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-98963: Restore the ability to have a dict-less property. #105262

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 5, 2023
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
Next Next commit
gh-98963: Restore the ability to have a dict-less property.
Ignore doc string assignment failures in `property` as has been the
behavior of all past Python releases.

One behavior change: The longstanding tested behavior of raising an
`AttributeError` when using a slotted no-dict property subclass as a
decorator on a getter sporting a docstring is now consistent with the
subclassing behavior and does not produce an error.
  • Loading branch information
gpshead committed Jun 3, 2023
commit 93276d2be56837bc197d0700027a65bfcc85d89c
73 changes: 62 additions & 11 deletions Lib/test/test_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,17 +245,68 @@ class PropertySubSlots(property):

class PropertySubclassTests(unittest.TestCase):

def test_slots_docstring_copy_exception(self):
try:
class Foo(object):
@PropertySubSlots
def spam(self):
"""Trying to copy this docstring will raise an exception"""
return 1
except AttributeError:
pass
else:
raise Exception("AttributeError not raised")
@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
def test_slots_getter_docstring_inherited_from_property_subclass(self):
# This raised an AttributeError prior to 3.12, now it matches the
# other long existing behavior of not reporting errors when a property
# docstring cannot be set due to being a class without a dict.
class Foo(object):
@PropertySubSlots
def spam(self):
"""Trying to assign this docstring should silently fail."""
return 1

self.assertEqual(Foo.spam.__doc__, PropertySubSlots.__doc__)

def test_property_with_slots_no_docstring(self):
# https://github.com/python/cpython/issues/98963#issuecomment-1574413319
class slotted_prop(property):
__slots__ = ("foo",)

p = slotted_prop() # no AttributeError
self.assertIsNone(getattr(p, "__doc__", None))

def undocumented_getter():
return 4

p = slotted_prop(undocumented_getter) # no AttributeError
self.assertIsNone(getattr(p, "__doc__", None))

@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
def test_property_with_slots_docstring_silently_dropped(self):
# https://github.com/python/cpython/issues/98963#issuecomment-1574413319
class slotted_prop(property):
__slots__ = ("foo",)

p = slotted_prop(doc="what's up") # no AttributeError
self.assertIsNone(p.__doc__)

def documented_getter():
"""getter doc."""
return 4

# The getter doc, if any, is used if none is otherwise supplied.
p = slotted_prop(documented_getter) # no AttributeError
self.assertIsNone(p.__doc__)

@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
def test_property_with_slots_and_doc_slot_docstring_present(self):
# https://github.com/python/cpython/issues/98963#issuecomment-1574413319
class slotted_prop(property):
__slots__ = ("foo", "__doc__")

p = slotted_prop(doc="what's up")
self.assertEqual("what's up", p.__doc__) # meep meep!

def documented_getter():
"""what's up getter doc?"""
return 4

p = slotted_prop(documented_getter)
self.assertEqual("what's up getter doc?", p.__doc__) # meep meep!

@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Restore the ability for a subclass of :class:`property` to define
``__slots__`` or otherwise be dict-less by ignoring any attempt to set a
docstring on such a class. This behavior had regressed in 3.12beta1.
33 changes: 26 additions & 7 deletions Objects/descrobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1485,7 +1485,10 @@ class property(object):
self.__get = fget
self.__set = fset
self.__del = fdel
self.__doc__ = doc
try:
self.__doc__ = doc
except AttributeError: # read-only or dict-less class
pass

def __get__(self, inst, type=None):
if inst is None:
Expand Down Expand Up @@ -1806,19 +1809,35 @@ property_init_impl(propertyobject *self, PyObject *fget, PyObject *fset,
if (Py_IS_TYPE(self, &PyProperty_Type)) {
Py_XSETREF(self->prop_doc, prop_doc);
} else {
/* If this is a property subclass, put __doc__
in dict of the subclass instance instead,
otherwise it gets shadowed by __doc__ in the
class's dict. */
/* If this is a property subclass, put __doc__ in the dict
or designated slot of the subclass instance instead, otherwise
it gets shadowed by __doc__ in the class's dict. */

if (prop_doc == NULL) {
prop_doc = Py_NewRef(Py_None);
}
int err = PyObject_SetAttr(
(PyObject *)self, &_Py_ID(__doc__), prop_doc);
Py_XDECREF(prop_doc);
if (err < 0)
return -1;
if (err < 0) {
if (PyErr_Occurred() == PyExc_AttributeError) {
PyErr_Clear();
// https://github.com/python/cpython/issues/98963#issuecomment-1574413319
// Python silently dropped this doc assignment through 3.11.
// We preserve that behavior for backwards compatibility.
if (prop_doc == Py_None) {
Py_DECREF(prop_doc);
return 0;
}
// If we ever want to deprecate this behavior, here is where to
// raise a DeprecationWarning; do not generate such noise when
// prop_doc is None.
Py_DECREF(prop_doc);
return 0;
} else {
return -1;
}
}
}

return 0;
Expand Down
0