inspect.signature 与 PEP 563

inspect.signature with PEP 563

以下代码:

import inspect
from typing import NamedTuple

class Example(NamedTuple):
    a: str

if __name__== "__main__":
    signature: inspect.Signature = inspect.signature(Example)
    print(signature)

输出:

(a: str)

但是当启用 PEP 563 – Postponed Evaluation of Annotations:

from __future__ import annotations
import inspect
from typing import NamedTuple

class Example(NamedTuple):
    a: str

if __name__== "__main__":
    signature: inspect.Signature = inspect.signature(Example)
    print(signature)

输出为:

(a: 'str')

如何使用 PEP 563 获得类型 inspect.Signature 完全相同的对象?

首先,让我们运行另一个例子:

signature: inspect.Signature = inspect.signature(Example)
print(signature)
print(Example.__annotations__)

这会打印:

(a: str)
OrderedDict([('a', <class 'str'>)])

到目前为止一切顺利,我们已经 Signature 和我们预期的 __anotations__

现在让我们对第二个例子做同样的事情,它打印:

(a: 'str')
OrderedDict([('a', ForwardRef('str'))])

所以你在这里得到的相同 Signature。一个给你实际的 class,另一个给你一个 typing.ForwardRef 到 class。

使用 PEP 536 的目的是 评估注释,除非需要。签名仅报告注释。

如果出于您的目的需要解析注释,您必须自己动手。 PEP 536 告诉 documents how you do this:

For code that uses type hints, the typing.get_type_hints(obj, globalns=None, localns=None) function correctly evaluates expressions back from its string form.

[...]

For code which uses annotations for other purposes, a regular eval(ann, globals, locals) call is enough to resolve the annotation.

您甚至可以在获得签名之前使用 typing.get_type_hints() function 分配回 __annotations__

import typing

Example.__new__.__annotations__ = typing.get_type_hints(Example.__new__)
signature: inspect.Signature = inspect.signature(Example)

即使 from __future__ import annotations 没有被使用,这样做也是安全的。

您必须实际使用 eval 才能获得相同的行为:

from __future__ import annotations
import inspect
from typing import NamedTuple

class Example(NamedTuple):
    a: str

signature: inspect.Signature = inspect.signature(Example)
print(signature)

# extra bit
globalns = getattr(Example, '__globals__', {})
for param in list(signature.parameters.values()):
  if isinstance(param.annotation, str):
    param._annotation = eval(param.annotation, globalns)

print(signature)

您将获得:

(a: 'str')
(a: str)

或者您可以在调用 inspect.signature(obj) 之前修改 __annotations__,但我发现它太难了,因为我需要涵盖多个不同的案例。

@Martijn Pieters 的回答遗漏了一个关于 typing.get_type_hints 的细节:

if necessary adds Optional[t] if a default value equal to None is set

示例:

# without imporing annotations from __future__
import inspect
import typing

def func(a: str=None): pass
print(inspect.signature(func))
func.__annotations__ = typing.get_type_hints(func)
print(inspect.signature(func))

您将获得:

(a: str = None)
(a: Union[str, NoneType] = None)