python 函数返回它自己的 运行 时间参数的签名

A python function returning signature of it's own run time arguments

我想要一个功能:

def sig(*args, **kwargs):
    """User-friendly inspect.Signature construction"""
    # I'm guessing some introspection of frames or something...
    pass

这就像它的文档所说的那样:轻松创建 Signature 个实例。

其行为的一个示例是:

>>> sig(a, b: int, c=1, d: str='bar')
<Signature (a, b: int, c=1, d: str = 'bar')>
>>> sig(something_else)
<Signature (something_else)>
>>> sig()
<Signature ()>

使用 inspect 模块后置框架的能力似乎有些神奇,但到目前为止我只能设法想出丑陋的技巧。

有人有优雅的东西吗?

编辑(约一小时后)

响应者指出,我对简单性的渴望在句法上是不可能的。因此,我将改写这个问题:哪些解决方案可能会达到这种简单性,可能会牺牲一些完整性(例如,没有注释)。

在最新的 python 3.8.2 中,您不能将值传递给带有注释类型的函数。

File "main.py", line 10
    print(sig(1, 2: int, c=3, d:str = "Hello"))
                  ^
SyntaxError: invalid syntax

另外,无论你指定什么类型,在运行时都是一样的。但是您可以尝试以这种方式推断类型:

>>> type(123).__name__
'int'

正如@D34DStone 所指出的,您想要实现的目标是不可能的,因为它不是有效的语法。 但是,您可以将 "spec" 作为字符串传递。

这是实现它的一种方法。虽然不是很漂亮......(并且可能不安全)。

def sig(x):
    locals = dict(x=x)
    exec('''
import inspect
def f(%s): pass
s = inspect.signature(f)
    ''' % x, {}, locals)
    return locals['s']

sig("a, b: int, c=1, d: str='bar'")
=> <Signature (a, b:int, c=1, d:str='bar')>

考虑到回复者和评论者所说的一切,这就是我得到的。

确切的愿望在句法上是不可能的,但有两种方法可以实现所寻求的简单性。

只需使用inspect.signature作为装饰器:

>>> from inspect import signature
>>> @signature
... def sig1(a, b: int, c=0, d: float=0.0): ...
>>> @signature
... def sig2(something_else): ...
>>> @signature
... def sig3(): ...
>>>
>>> sig1
<Signature (a, b: int, c=0, d: float = 0.0)>
>>> sig2
<Signature (something_else)>
>>> sig3
<Signature ()>

但是如果你需要更有活力的东西,我建议如下:

def sig(obj: Union[Signature, Callable, Mapping, None] = None, return_annotations=_empty, **annotations):
    """Convenience function to make a signature or inject annotations to an existing one.
    """
    if obj is None:
        return Signature()
    if callable(obj):
        obj = Signature.from_callable(obj)  # get a signature object from a callable
    if isinstance(obj, Signature):
        obj = obj.parameters  # get the parameters attribute from a signature
    params = dict(obj)  # get a writable copy of parameters
    if not annotations:
        return Signature(params.values(), return_annotation=return_annotations)
    else:
        assert set(annotations) <= set(params), \
            f"These argument names weren't found in the signature: {set(annotations) - set(params)}"
        for name, annotation in annotations.items():
            p = params[name]
            params[name] = Parameter(name=name, kind=p.kind, default=p.default, annotation=annotation)
        return Signature(params.values(), return_annotation=return_annotations)

演示:

>>> s = sig(lambda a, b, c=1, d='bar': ..., b=int, d=str)
>>> s
<Signature (a, b: int, c=1, d: str = 'bar')>
>>> # showing that sig can take a signature input, and overwrite an existing annotation:
>>> sig(s, a=list, b=float)  # note the b=float
<Signature (a: list, b: float, c=1, d: str = 'bar')>
>>> sig()
<Signature ()>
>>> sig(lambda a, b=2, c=3: ..., d=int)  # trying to annotate an argument that doesn't exist
Traceback (most recent call last):
...
AssertionError: These argument names weren't found in the signature: {'d'}