@@ -1273,37 +1273,86 @@ Using the non-data descriptor protocol, a pure Python version of
12731273
12741274.. testcode ::
12751275
1276+ import functools
1277+
12761278 class StaticMethod:
12771279 "Emulate PyStaticMethod_Type() in Objects/funcobject.c"
12781280
12791281 def __init__(self, f):
12801282 self.f = f
1283+ functools.update_wrapper(self, f)
12811284
12821285 def __get__(self, obj, objtype=None):
12831286 return self.f
12841287
12851288 def __call__(self, *args, **kwds):
12861289 return self.f(*args, **kwds)
12871290
1291+ The :func: `functools.update_wrapper ` call adds a ``__wrapped__ `` attribute
1292+ that refers to the underlying function. Also it carries forward
1293+ the attributes necessary to make the wrapper look like the wrapped
1294+ function: ``__name__ ``, ``__qualname__ ``, ``__doc__ ``, and ``__annotations__ ``.
1295+
12881296.. testcode ::
12891297 :hide:
12901298
12911299 class E_sim:
12921300 @StaticMethod
1293- def f(x):
1294- return x * 10
1301+ def f(x: int) -> str:
1302+ "Simple function example"
1303+ return "!" * x
12951304
12961305 wrapped_ord = StaticMethod(ord)
12971306
12981307.. doctest ::
12991308 :hide:
13001309
13011310 >>> E_sim.f(3 )
1302- 30
1311+ '!!!'
13031312 >>> E_sim().f(3 )
1304- 30
1313+ '!!!'
1314+
1315+ >>> sm = vars (E_sim)[' f' ]
1316+ >>> type (sm).__name__
1317+ 'StaticMethod'
1318+ >>> f = E_sim.f
1319+ >>> type (f).__name__
1320+ 'function'
1321+ >>> sm.__name__
1322+ 'f'
1323+ >>> f.__name__
1324+ 'f'
1325+ >>> sm.__qualname__
1326+ 'E_sim.f'
1327+ >>> f.__qualname__
1328+ 'E_sim.f'
1329+ >>> sm.__doc__
1330+ 'Simple function example'
1331+ >>> f.__doc__
1332+ 'Simple function example'
1333+ >>> sm.__annotations__
1334+ {'x': <class 'int'>, 'return': <class 'str'>}
1335+ >>> f.__annotations__
1336+ {'x': <class 'int'>, 'return': <class 'str'>}
1337+ >>> sm.__module__ == f.__module__
1338+ True
1339+ >>> sm(3 )
1340+ '!!!'
1341+ >>> f(3 )
1342+ '!!!'
1343+
13051344 >>> wrapped_ord(' A' )
13061345 65
1346+ >>> wrapped_ord.__module__ == ord .__module__
1347+ True
1348+ >>> wrapped_ord.__wrapped__ == ord
1349+ True
1350+ >>> wrapped_ord.__name__ == ord .__name__
1351+ True
1352+ >>> wrapped_ord.__qualname__ == ord .__qualname__
1353+ True
1354+ >>> wrapped_ord.__doc__ == ord .__doc__
1355+ True
13071356
13081357
13091358Class methods
@@ -1359,11 +1408,14 @@ Using the non-data descriptor protocol, a pure Python version of
13591408
13601409.. testcode ::
13611410
1411+ import functools
1412+
13621413 class ClassMethod:
13631414 "Emulate PyClassMethod_Type() in Objects/funcobject.c"
13641415
13651416 def __init__(self, f):
13661417 self.f = f
1418+ functools.update_wrapper(self, f)
13671419
13681420 def __get__(self, obj, cls=None):
13691421 if cls is None:
@@ -1380,8 +1432,9 @@ Using the non-data descriptor protocol, a pure Python version of
13801432 # Verify the emulation works
13811433 class T:
13821434 @ClassMethod
1383- def cm(cls, x, y):
1384- return (cls, x, y)
1435+ def cm(cls, x: int, y: str) -> tuple[str, int, str]:
1436+ "Class method that returns a tuple"
1437+ return (cls.__name__, x, y)
13851438
13861439 @ClassMethod
13871440 @property
@@ -1393,17 +1446,40 @@ Using the non-data descriptor protocol, a pure Python version of
13931446 :hide:
13941447
13951448 >>> T.cm(11 , 22 )
1396- (<class 'T'> , 11, 22)
1449+ ('T', 11, 22)
13971450
13981451 # Also call it from an instance
13991452 >>> t = T()
14001453 >>> t.cm(11 , 22 )
1401- (<class 'T'> , 11, 22)
1454+ ('T', 11, 22)
14021455
14031456 # Check the alternate path for chained descriptors
14041457 >>> T.__doc__
14051458 "A doc for 'T'"
14061459
1460+ # Verify that T uses our emulation
1461+ >>> type (vars (T)[' cm' ]).__name__
1462+ 'ClassMethod'
1463+
1464+ # Verify that update_wrapper() correctly copied attributes
1465+ >>> T.cm.__name__
1466+ 'cm'
1467+ >>> T.cm.__qualname__
1468+ 'T.cm'
1469+ >>> T.cm.__doc__
1470+ 'Class method that returns a tuple'
1471+ >>> T.cm.__annotations__
1472+ {'x': <class 'int'>, 'y': <class 'str'>, 'return': tuple[str, int, str]}
1473+
1474+ # Verify that __wrapped__ was added and works correctly
1475+ >>> f = vars (T)[' cm' ].__wrapped__
1476+ >>> type (f).__name__
1477+ 'function'
1478+ >>> f.__name__
1479+ 'cm'
1480+ >>> f(T, 11 , 22 )
1481+ ('T', 11, 22)
1482+
14071483
14081484The code path for ``hasattr(type(self.f), '__get__') `` was added in
14091485Python 3.9 and makes it possible for :func: `classmethod ` to support
@@ -1423,6 +1499,12 @@ chained together. In Python 3.11, this functionality was deprecated.
14231499 >>> G.__doc__
14241500 "A doc for 'G'"
14251501
1502+ The :func: `functools.update_wrapper ` call in ``ClassMethod `` adds a
1503+ ``__wrapped__ `` attribute that refers to the underlying function. Also
1504+ it carries forward the attributes necessary to make the wrapper look
1505+ like the wrapped function: ``__name__ ``, ``__qualname__ ``, ``__doc__ ``,
1506+ and ``__annotations__ ``.
1507+
14261508
14271509Member objects and __slots__
14281510----------------------------
0 commit comments