Descriptor HowTo: Update to include attributes added in Python 3.10 (GH-103666)

This commit is contained in:
Raymond Hettinger 2023-04-22 08:29:40 -05:00 committed by GitHub
parent b2862950dc
commit 7b134d3e71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 90 additions and 8 deletions

View File

@ -1273,11 +1273,14 @@ Using the non-data descriptor protocol, a pure Python version of
.. testcode::
import functools
class StaticMethod:
"Emulate PyStaticMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f
functools.update_wrapper(self, f)
def __get__(self, obj, objtype=None):
return self.f
@ -1285,13 +1288,19 @@ Using the non-data descriptor protocol, a pure Python version of
def __call__(self, *args, **kwds):
return self.f(*args, **kwds)
The :func:`functools.update_wrapper` call adds a ``__wrapped__`` attribute
that refers to the underlying function. Also it carries forward
the attributes necessary to make the wrapper look like the wrapped
function: ``__name__``, ``__qualname__``, ``__doc__``, and ``__annotations__``.
.. testcode::
:hide:
class E_sim:
@StaticMethod
def f(x):
return x * 10
def f(x: int) -> str:
"Simple function example"
return "!" * x
wrapped_ord = StaticMethod(ord)
@ -1299,11 +1308,51 @@ Using the non-data descriptor protocol, a pure Python version of
:hide:
>>> E_sim.f(3)
30
'!!!'
>>> E_sim().f(3)
30
'!!!'
>>> sm = vars(E_sim)['f']
>>> type(sm).__name__
'StaticMethod'
>>> f = E_sim.f
>>> type(f).__name__
'function'
>>> sm.__name__
'f'
>>> f.__name__
'f'
>>> sm.__qualname__
'E_sim.f'
>>> f.__qualname__
'E_sim.f'
>>> sm.__doc__
'Simple function example'
>>> f.__doc__
'Simple function example'
>>> sm.__annotations__
{'x': <class 'int'>, 'return': <class 'str'>}
>>> f.__annotations__
{'x': <class 'int'>, 'return': <class 'str'>}
>>> sm.__module__ == f.__module__
True
>>> sm(3)
'!!!'
>>> f(3)
'!!!'
>>> wrapped_ord('A')
65
>>> wrapped_ord.__module__ == ord.__module__
True
>>> wrapped_ord.__wrapped__ == ord
True
>>> wrapped_ord.__name__ == ord.__name__
True
>>> wrapped_ord.__qualname__ == ord.__qualname__
True
>>> wrapped_ord.__doc__ == ord.__doc__
True
Class methods
@ -1359,11 +1408,14 @@ Using the non-data descriptor protocol, a pure Python version of
.. testcode::
import functools
class ClassMethod:
"Emulate PyClassMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f
functools.update_wrapper(self, f)
def __get__(self, obj, cls=None):
if cls is None:
@ -1380,8 +1432,9 @@ Using the non-data descriptor protocol, a pure Python version of
# Verify the emulation works
class T:
@ClassMethod
def cm(cls, x, y):
return (cls, x, y)
def cm(cls, x: int, y: str) -> tuple[str, int, str]:
"Class method that returns a tuple"
return (cls.__name__, x, y)
@ClassMethod
@property
@ -1393,17 +1446,40 @@ Using the non-data descriptor protocol, a pure Python version of
:hide:
>>> T.cm(11, 22)
(<class 'T'>, 11, 22)
('T', 11, 22)
# Also call it from an instance
>>> t = T()
>>> t.cm(11, 22)
(<class 'T'>, 11, 22)
('T', 11, 22)
# Check the alternate path for chained descriptors
>>> T.__doc__
"A doc for 'T'"
# Verify that T uses our emulation
>>> type(vars(T)['cm']).__name__
'ClassMethod'
# Verify that update_wrapper() correctly copied attributes
>>> T.cm.__name__
'cm'
>>> T.cm.__qualname__
'T.cm'
>>> T.cm.__doc__
'Class method that returns a tuple'
>>> T.cm.__annotations__
{'x': <class 'int'>, 'y': <class 'str'>, 'return': tuple[str, int, str]}
# Verify that __wrapped__ was added and works correctly
>>> f = vars(T)['cm'].__wrapped__
>>> type(f).__name__
'function'
>>> f.__name__
'cm'
>>> f(T, 11, 22)
('T', 11, 22)
The code path for ``hasattr(type(self.f), '__get__')`` was added in
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.
>>> G.__doc__
"A doc for 'G'"
The :func:`functools.update_wrapper` call in ``ClassMethod`` adds a
``__wrapped__`` attribute that refers to the underlying function. Also
it carries forward the attributes necessary to make the wrapper look
like the wrapped function: ``__name__``, ``__qualname__``, ``__doc__``,
and ``__annotations__``.
Member objects and __slots__
----------------------------