This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author larry
Recipients JelleZijlstra, barry, eric.smith, gvanrossum, kj, larry, methane, xtreak
Date 2021-04-21.07:49:13
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1618991354.15.0.640032557008.issue43817@roundup.psfhosted.org>
In-reply-to
Content
Just over twelve hours ago, the Python Steering Committee announced that stringized annotations would no longer be default behavior in Python 3.10.  They will go back to being gated with "from __future__ import annotations".  I think we still need this function, but naturally the implementation will now be a bit different.

It's going to take time to change the default semantics of annotations back to the way they were in Python 3.9.  In the meantime, I've coded up a first draft of inspect.get_annotations(), written assuming stringized annotations are optional.  It's attached below.  I'll also paste in the definition and the docstring into the text box here for your reading convenience.

I assert that get_annotations() should definitely try to un-stringize stringized annotations by default.  The default behavior of eval_str is sliightly magical, but I'm not sure I can do anything any smarter (apart from "resist the temptation to guess").  Unsophisticated users will want to see real values, and it may be important to give sophisticated users control over whether or not eval() is called.

By the same token: if eval_str stays as part of the interface, I propose exposing it from the library functions that will call get_annotations():
  * inspect.signature()
  * functools.singledispatch()
That way, a project using stringized annotations due to gnarly circular imports/definitions problems could still use inspect.signature(), without having to worry about worrying about whether or not all the symbols were defined at runtime.

I'll interpret a lack of feedback as a sort of mumbled encouraging consensus.

-----

def get_annotations(obj, globals=None, locals=None, *, eval_str=ONLY_IF_ALL_STR):
  
Compute the annotations dict for an object.

obj may be a callable, class, or module.
Passing in any other type of object raises TypeError.

This function handles several details for you:

  * Values of type str may be un-stringized using eval(),
    depending on the value of eval_str.  This is intended
    for use with stringized annotations
    (from __future__ import annotations).
  * If obj doesn't have an annotations dict, returns an
    empty dict.  (Functions and methods always have an
    annotations dict; classes, modules, and other types of
    callables may not.)
  * Ignores inherited annotations on classes.  If a class
    doesn't have its own annotations dict, returns an empty dict.
  * Always, always, always returns a dict.

eval_str controls whether or not values of type str are replaced
with the result of calling eval() on those values:

  * If eval_str is true, eval() is called on values of type str.
  * If eval_str is false, values of type str are unchanged.
  * If eval_str is the special value inspect.ONLY_IF_ALL_STR,
    which is the default, eval() is called on values of type str
    only if *every* value in the dict is of type str.  This is a
    heuristic; the goal is to only eval() stringized annotations.
    (If, in a future version of Python, get_annotations() is able
    to determine authoritatively whether or not an annotations
    dict contains stringized annotations, inspect.ONLY_IF_ALL_STR
    will use that authoritative source instead of the heuristic.)

globals and locals are passed in to eval(); see the documentation
for eval() for more information.  If globals is None,
get_annotations() uses a context-specific default value,
contingent on type(obj):

  * If obj is a module, globals defaults to obj.__dict__.
  * If obj is a class, globals defaults to
    sys.modules[obj.__module__].__dict__.
  * If obj is a callable, globals defaults to obj.__globals__,
    although if obj is a wrapped function (using
    functools.update_wrapper()) it is first unwrapped.
History
Date User Action Args
2021-04-21 07:49:14larrysetrecipients: + larry, gvanrossum, barry, eric.smith, methane, JelleZijlstra, xtreak, kj
2021-04-21 07:49:14larrysetmessageid: <1618991354.15.0.640032557008.issue43817@roundup.psfhosted.org>
2021-04-21 07:49:14larrylinkissue43817 messages
2021-04-21 07:49:13larrycreate