@@ -385,8 +385,8 @@ Functions
385385 using ``getattr() `` and ``dict.get() `` for safety.
386386 * Supports objects that provide their own :attr: `~object.__annotate__ ` method,
387387 such as :class: `functools.partial ` and :class: `functools.partialmethod `.
388- See :ref: ` below < functools-objects-annotations >` for details on using
389- :func: ` !get_annotations ` with :mod: ` functools ` objects .
388+ See the :mod: ` functools ` module documentation for details on how these
389+ objects support annotations .
390390
391391 *eval_str * controls whether or not values of type :class: `!str ` are
392392 replaced with the result of calling :func: `eval ` on those values:
@@ -442,93 +442,33 @@ Functions
442442
443443 .. versionadded :: 3.14
444444
445- .. _functools-objects-annotations :
446-
447- Using :func: `!get_annotations ` with :mod: `functools ` objects
448- --------------------------------------------------------------
449-
450- :func: `get_annotations ` has special support for :class: `functools.partial `
451- and :class: `functools.partialmethod ` objects. When called on these objects,
452- it returns only the annotations for parameters that have not been bound by
453- the partial application, along with the return annotation if present.
454-
455- For :class: `functools.partial ` objects, positional arguments bind to parameters
456- in order, and the annotations for those parameters are excluded from the result:
457-
458- .. doctest ::
459-
460- >>> from functools import partial
461- >>> def func (a : int , b : str , c : float ) -> bool :
462- ... return True
463- >>> partial_func = partial(func, 1 ) # Binds 'a'
464- >>> get_annotations(partial_func)
465- {'b': <class 'str'>, 'c': <class 'float'>, 'return': <class 'bool'>}
466-
467- Keyword arguments in :class: `functools.partial ` set default values but do not
468- remove parameters from the signature, so their annotations are retained:
469-
470- .. doctest ::
471-
472- >>> partial_func_kw = partial(func, b = " hello" ) # Sets default for 'b'
473- >>> get_annotations(partial_func_kw)
474- {'a': <class 'int'>, 'b': <class 'str'>, 'c': <class 'float'>, 'return': <class 'bool'>}
475-
476- For :class: `functools.partialmethod ` objects accessed through a class (unbound),
477- the first parameter (usually ``self `` or ``cls ``) is preserved, and subsequent
478- parameters are handled similarly to :class: `functools.partial `:
479-
480- .. doctest ::
481-
482- >>> from functools import partialmethod
483- >>> class MyClass :
484- ... def method (self , a : int , b : str ) -> bool :
485- ... return True
486- ... partial_method = partialmethod(method, 1 ) # Binds 'a'
487- >>> get_annotations(MyClass.partial_method)
488- {'b': <class 'str'>, 'return': <class 'bool'>}
489-
490- When a :class: `functools.partialmethod ` is accessed through an instance (bound),
491- it becomes a :class: `functools.partial ` object and is handled accordingly:
492-
493- .. doctest ::
494-
495- >>> obj = MyClass()
496- >>> get_annotations(obj.partial_method) # Same as above, 'self' is also bound
497- {'b': <class 'str'>, 'return': <class 'bool'>}
498-
499- This behavior ensures that :func: `get_annotations ` returns annotations that
500- accurately reflect the signature of the partial or partialmethod object, as
501- determined by :func: `inspect.signature `.
502-
503- If :func: `!get_annotations ` cannot reliably determine which parameters are bound
504- (for example, if :func: `inspect.signature ` raises an error), it will raise a
505- :exc: `TypeError ` rather than returning incorrect annotations. This ensures that
506- you either get correct annotations or a clear error, never incorrect annotations:
507-
508- .. doctest ::
509-
510- >>> from functools import partial
511- >>> import inspect
512- >>> def func (a : int , b : str ) -> bool :
513- ... return True
514- >>> partial_func = partial(func, 1 )
515- >>> # Simulate a case where signature inspection fails
516- >>> original_sig = inspect.signature
517- >>> def broken_signature (obj ):
518- ... if isinstance (obj, partial):
519- ... raise ValueError (" Cannot inspect signature" )
520- ... return original_sig(obj)
521- >>> inspect.signature = broken_signature
522- >>> try :
523- ... get_annotations(partial_func)
524- ... except TypeError as e:
525- ... print (f " Got expected error: { e} " )
526- ... finally :
527- ... inspect.signature = original_sig
528- Got expected error: Cannot compute annotations for ...: unable to determine signature
529-
530- This design prevents the common error of returning annotations that include
531- parameters which have already been bound by the partial application.
445+ Supporting annotations in custom objects
446+ -------------------------------------------
447+
448+ Objects can support annotation introspection by implementing the :attr: `~object.__annotate__ `
449+ protocol. When an object provides an :attr: `!__annotate__ ` attribute, :func: `get_annotations `
450+ will call it to retrieve the annotations for that object. The :attr: `!__annotate__ ` function
451+ should accept a single argument, a member of the :class: `Format ` enum, and return a dictionary
452+ mapping annotation names to their values in the requested format.
453+
454+ This mechanism allows objects to dynamically compute their annotations based on their state.
455+ For example, :class: `functools.partial ` and :class: `functools.partialmethod ` objects use
456+ :attr: `!__annotate__ ` to provide annotations that reflect only the unbound parameters,
457+ excluding parameters that have been filled by the partial application. See the
458+ :mod: `functools ` module documentation for details on how these specific objects handle
459+ annotations.
460+
461+ Other examples of objects that implement :attr: `!__annotate__ ` include:
462+
463+ * :class: `typing.TypedDict ` classes created through the functional syntax
464+ * Generic classes and functions with type parameters
465+
466+ When implementing :attr: `!__annotate__ ` for custom objects, the function should handle
467+ all three primary formats (:attr: `~Format.VALUE `, :attr: `~Format.FORWARDREF `, and
468+ :attr: `~Format.STRING `) by either returning appropriate values or raising
469+ :exc: `NotImplementedError ` to fall back to default behavior. Helper functions like
470+ :func: `annotations_to_string ` and :func: `call_annotate_function ` can assist with
471+ implementing format support.
532472
533473
534474Recipes
0 commit comments