Skip to content

Commit da5dc81

Browse files
committed
ensure Python fallback has same behavior as _PyObject_LookupSpecialMethod() on classmethod, staticmethod, and property
1 parent d3f6bda commit da5dc81

File tree

2 files changed

+31
-6
lines changed

2 files changed

+31
-6
lines changed

Lib/test/test_types.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,12 +747,15 @@ def __enter__(self):
747747
cm1 = CM1()
748748
meth = lookup(cm1, "__enter__")
749749
self.assertIsNotNone(meth)
750+
with self.assertRaisesRegex(
751+
TypeError, "missing 1 required positional argument") as cm:
752+
meth()
750753
self.assertEqual(meth(cm1), "__enter__ from class __dict__")
751754

752755
meth = lookup(cm1, "__missing__")
753756
self.assertIsNone(meth)
754757

755-
with self.assertRaises(TypeError):
758+
with self.assertRaisesRegex(TypeError, "attribute name must be string"):
756759
lookup(cm1, 123)
757760

758761
cm2 = CM2()
@@ -768,6 +771,27 @@ def __enter__(self):
768771
self.assertIsNotNone(meth)
769772
self.assertEqual(meth([]), 0)
770773

774+
class Person:
775+
@classmethod
776+
def hi(cls):
777+
return f"hi from {cls.__name__}"
778+
@staticmethod
779+
def hello():
780+
return "hello from static method"
781+
@property
782+
def name(self):
783+
return "name from property"
784+
p = Person()
785+
meth = lookup(p, "hi")
786+
self.assertIsNotNone(meth)
787+
self.assertEqual(meth(), "hi from Person")
788+
789+
meth = lookup(p, "hello")
790+
self.assertIsNotNone(meth)
791+
self.assertEqual(meth(), "hello from static method")
792+
793+
self.assertEqual(lookup(p, "name"), "name from property")
794+
771795
def test_lookup_special_method(self):
772796
c_lookup = getattr(c_types, "lookup_special_method")
773797
py_lookup = getattr(py_types, "lookup_special_method")

Lib/types.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,11 @@ def lookup_special_method(obj, attr, /):
110110
>>> enter_b(b)
111111
'B.__enter__'
112112
113-
For other descriptors (property, etc.), it returns the result of the
114-
descriptor's `__get__` method. Returns `None` if the method is not
115-
found.
113+
For other descriptors (classmethod, staticmethod, property, etc.), it
114+
returns the result of the descriptor's `__get__` method. Returns `None`
115+
if the method is not found.
116116
"""
117-
from inspect import getattr_static, isfunction, ismethoddescriptor
117+
from inspect import getattr_static, isfunction
118118
cls = type(obj)
119119
if not isinstance(attr, str):
120120
raise TypeError(
@@ -125,7 +125,8 @@ def lookup_special_method(obj, attr, /):
125125
except AttributeError:
126126
return None
127127
if hasattr(descr, "__get__"):
128-
if isfunction(descr) or ismethoddescriptor(descr):
128+
if isfunction(descr) or isinstance(descr,(
129+
MethodDescriptorType, WrapperDescriptorType)):
129130
# do not create bound method to mimic the behavior of
130131
# _PyObject_LookupSpecialMethod
131132
return descr

0 commit comments

Comments
 (0)