How to get type annotation within python function scope?

2024/10/10 12:26:16

For example:

def test():a: intb: strprint(__annotations__)
test()

This function call raises a NameError: name '__annotations__' is not defined error.

What I want is to get the type annotation within the function test, like the returned dict of annotations in the global scope or class scope.

It there are any ways can achieve this ?

If it's not possiblle, why this syntax exists?

Answer

Within a function, annotations for local variables are not retained, and so can't be accessed within the function. Only annotations for variables at the module and class-level result in an __annotations__ object being attached.

From the PEP 526 specification:

Annotating a local variable will cause the interpreter to treat it as a local, even if it was never assigned to. Annotations for local variables will not be evaluated[.]

[...]

In addition, at the module or class level, if the item being annotated is a simple name, then it and the annotation will be stored in the __annotations__ attribute of that module or class[.]

The __annotations__ global is only set if there are actual module-level annotations defined; the data model states it is optional:

Modules
[...] Predefined (writable) attributes: [...]; __annotations__ (optional) is a dictionary containing variable annotations collected during module body execution; [...].

When defined, you can access it from functions within the module, or via the globals() function.

If you try this within a function inside a class statement, then know that the class body namespace is not part of the scope of nested functions:

The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes comprehensions and generator expressions since they are implemented using a function scope.

You'd instead access the class namespace via a reference to the class. You can get such a reference by using the class global name, or inside bound methods, via type(self), inside class methods via the cls argument. Just use ClassObject.__annotations__ in that case.

If you must have access to annotations in the function local body, you'll need to parse the source code yourself. The Python AST does retain local annotations:

>>> import ast
>>> mod = ast.parse("def foo():\n    a: int = 0")
>>> print(ast.dump(mod.body[0], indent=4))
FunctionDef(name='foo',args=arguments(posonlyargs=[],args=[],kwonlyargs=[],kw_defaults=[],defaults=[]),body=[AnnAssign(target=Name(id='a', ctx=Store()),annotation=Name(id='int', ctx=Load()),value=Constant(value=0),simple=1)],decorator_list=[])

The above shows a text representation for the body of a function with a single annotation; the AnnAssign node tells us that a is annotated as int. You could collect such annotations with:

import inspect
import astclass AnnotationsCollector(ast.NodeVisitor):"""Collects AnnAssign nodes for 'simple' annotation assignments"""def __init__(self):self.annotations = {}def visit_AnnAssign(self, node):if node.simple:# 'simple' == a single name, not an attribute or subscription.# we can therefore count on `node.target.id` to exist. This is# the same criteria used for module and class-level variable# annotations.self.annotations[node.target.id] = node.annotationdef function_local_annotations(func):"""Return a mapping of name to string annotations for function localsPython does not retain PEP 526 "variable: annotation" variable annotationswithin a function body, as local variables do not have a lifetime beyondthe local namespace. This function extracts the mapping from functions thathave source code available."""source = inspect.getsource(func)mod = ast.parse(source)assert mod.body and isinstance(mod.body[0], (ast.FunctionDef, ast.AsyncFunctionDef))collector = AnnotationsCollector()collector.visit(mod.body[0])return {name: ast.get_source_segment(source, node)for name, node in collector.annotations.items()}

The above walker finds all AnnAssignment annotations in the source code for a function object (and so requires that there is a source file available), then uses the AST source line and column information to extract the annotation source.

Given your test function, the above produces:

>>> function_local_annotations(test)
{'a': 'int', 'b': 'str'}

The type hints are not resolved, they are just strings, so you'll still have to use the typing.get_type_hints() function to turn these annotations into type objects.

https://en.xdnf.cn/q/69895.html

Related Q&A

General explanation of how epoll works?

Im doing a technical write-up on switching from a database-polling (via synchronous stored procedure call) to a message queue (via pub/sub). Id like to be able to explain how polling a database is vas…

How to return cost, grad as tuple for scipys fmin_cg function

How can I make scipys fmin_cg use one function that returns cost and gradient as a tuple? The problem with having f for cost and fprime for gradient, is that I might have to perform an operation twice…

n-gram name analysis in non-english languages (CJK, etc)

Im working on deduping a database of people. For a first pass, Im following a basic 2-step process to avoid an O(n^2) operation over the whole database, as described in the literature. First, I "b…

How to retrieve all the attributes of LDAP database

I am using ldap module of python to connect to ldap server. I am able to query the database but I dont know how to retrieve the fields present in the database, so that I can notify the user in advance …

Why would this dataset implementation run out of memory?

I follow this instruction and write the following code to create a Dataset for images(COCO2014 training set)from pathlib import Path import tensorflow as tfdef image_dataset(filepath, image_size, batch…

Paramiko: Creating a PKey from a public key string

Im trying to use the SSH protocol at a low level (i.e. I dont want to start a shell or anything, I just want to pass data). Thus, I am using Paramikos Transport class directly.Ive got the server side d…

Appending to the end of a file in a concurrent environment

What steps need to be taken to ensure that "full" lines are always correctly appended to the end of a file if multiple of the following (example) program are running concurrently.#!/usr/bin/e…

Cython Pickling in Package not found as Error

Im having trouble pickling a Cython class, but only when its defined inside a package. This problem was noted previously online, but they didnt state how it was resolved. There are two components here:…

How can I process images faster with Python?

Id trying to write a script that will detect an RGB value on the screen then click the x,y values. I know how to perform the click but I need to process the image a lot faster than my code below curren…

KFolds Cross Validation vs train_test_split

I just built my first random forest classifier today and I am trying to improve its performance. I was reading about how cross-validation is important to avoid overfitting of data and hence obtain bett…