【问题标题】:A python function returning signature of it's own run time arguments一个python函数返回它自己的运行时参数的签名
【发布时间】:2020-07-27 15:30:30
【问题描述】:

我想要一个函数:

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 模块的后框能力似乎有些魔法可以做到,但到目前为止,我只设法想出了丑陋的黑客。

谁有优雅的东西?

编辑(约一小时后)

响应者指出,我对简单的渴望在语法上是不可能的。所以我将改写这个问题:什么解决方案可能会接近这种简单性,可能会牺牲一些完整性(例如,没有注释)。

【问题讨论】:

  • 函数调用语法和函数定义语法可能看起来有点相似,但它们的含义相差太大且不兼容。
  • “什么解决方案可能会接近这种简单性,可能会牺牲一些完整性(例如,没有注释)。” - 将 lambda 传递给 signature?
  • @user2357112supportsMonica,你在做点什么。我们无法在 lambdas 中获取注释,但我没有意识到我们可以在其中获取默认值。

标签: python python-3.x signature introspection inspect


【解决方案1】:

正如@D34DStone 指出的那样,您想要实现的目标是不可能的,因为它不是有效的语法。 但是,您可以改为将“规范”作为字符串传递。

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

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')>

【讨论】:

  • 对不起@D34DStone,你回答的最后一部分让我相信你没有理解答案。编辑,我会投票。
  • 感谢您的澄清。事实上,我将无法拥有我想要的简单性。您的 hack 类似于我所做的,但不希望必须解析字符串和 exec。我会看看其他人有什么建议,如果没有更好的,我会接受这个答案。
  • 我将稍微编辑我的帖子,以邀请替代解决方案来解决“简化签名实例构造”问题。
【解决方案2】:

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

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

只需使用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'}

【讨论】:

    【解决方案3】:

    在最新的 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'
    

    【讨论】:

    • 我不想在运行时推断类型。我正在尝试以更加用户友好的方式创建 inspect.Signature 实例。
    猜你喜欢
    • 2021-05-15
    • 1970-01-01
    • 2016-10-27
    • 1970-01-01
    • 2016-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多