@@ -1273,37 +1273,86 @@ Using the non-data descriptor protocol, a pure Python version of
1273
1273
1274
1274
.. testcode ::
1275
1275
1276
+ import functools
1277
+
1276
1278
class StaticMethod:
1277
1279
"Emulate PyStaticMethod_Type() in Objects/funcobject.c"
1278
1280
1279
1281
def __init__(self, f):
1280
1282
self.f = f
1283
+ functools.update_wrapper(self, f)
1281
1284
1282
1285
def __get__(self, obj, objtype=None):
1283
1286
return self.f
1284
1287
1285
1288
def __call__(self, *args, **kwds):
1286
1289
return self.f(*args, **kwds)
1287
1290
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
+
1288
1296
.. testcode ::
1289
1297
:hide:
1290
1298
1291
1299
class E_sim:
1292
1300
@StaticMethod
1293
- def f(x):
1294
- return x * 10
1301
+ def f(x: int) -> str:
1302
+ "Simple function example"
1303
+ return "!" * x
1295
1304
1296
1305
wrapped_ord = StaticMethod(ord)
1297
1306
1298
1307
.. doctest ::
1299
1308
:hide:
1300
1309
1301
1310
>>> E_sim.f(3 )
1302
- 30
1311
+ '!!!'
1303
1312
>>> 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
+
1305
1344
>>> wrapped_ord(' A' )
1306
1345
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
1307
1356
1308
1357
1309
1358
Class methods
@@ -1359,11 +1408,14 @@ Using the non-data descriptor protocol, a pure Python version of
1359
1408
1360
1409
.. testcode ::
1361
1410
1411
+ import functools
1412
+
1362
1413
class ClassMethod:
1363
1414
"Emulate PyClassMethod_Type() in Objects/funcobject.c"
1364
1415
1365
1416
def __init__(self, f):
1366
1417
self.f = f
1418
+ functools.update_wrapper(self, f)
1367
1419
1368
1420
def __get__(self, obj, cls=None):
1369
1421
if cls is None:
@@ -1380,8 +1432,9 @@ Using the non-data descriptor protocol, a pure Python version of
1380
1432
# Verify the emulation works
1381
1433
class T:
1382
1434
@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)
1385
1438
1386
1439
@ClassMethod
1387
1440
@property
@@ -1393,17 +1446,40 @@ Using the non-data descriptor protocol, a pure Python version of
1393
1446
:hide:
1394
1447
1395
1448
>>> T.cm(11 , 22 )
1396
- (<class 'T'> , 11, 22)
1449
+ ('T', 11, 22)
1397
1450
1398
1451
# Also call it from an instance
1399
1452
>>> t = T()
1400
1453
>>> t.cm(11 , 22 )
1401
- (<class 'T'> , 11, 22)
1454
+ ('T', 11, 22)
1402
1455
1403
1456
# Check the alternate path for chained descriptors
1404
1457
>>> T.__doc__
1405
1458
"A doc for 'T'"
1406
1459
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
+
1407
1483
1408
1484
The code path for ``hasattr(type(self.f), '__get__') `` was added in
1409
1485
Python 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.
1423
1499
>>> G.__doc__
1424
1500
"A doc for 'G'"
1425
1501
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
+
1426
1508
1427
1509
Member objects and __slots__
1428
1510
----------------------------
0 commit comments