Функция python, возвращающая сигнатуру собственных аргументов времени выполнения - PullRequest
0 голосов
/ 14 апреля 2020

Мне нужна функция:

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

, которая делает то, что делает c говорит: Легко создавайте 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 ()>

Кажется, какой-нибудь волшебник dry, использующий способность модуля inspect к задним кадрам, подойдет, но пока только удалось придумать уродливые хаки.

У кого-нибудь есть что-нибудь изящное?

Редактировать (~ час спустя)

Ответчики указали, что мои желания простоты были синтаксически невозможны. Поэтому я перефразирую вопрос: какие решения могут приблизиться к этой простоте, возможно, пожертвовав некоторой полнотой (например, без аннотаций).

Ответы [ 3 ]

1 голос
/ 14 апреля 2020

Как указал @ D34DStone, то, чего вы хотите достичь, невозможно, потому что это неверный синтаксис. Однако вместо этого вы можете передать свое «spe c» в виде строки.

Вот один из способов добиться этого. Не очень красиво, хотя ... (и, возможно, не безопасно).

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')>
0 голосов
/ 15 апреля 2020

Учитывая все, что сказали респонденты и комментаторы, вот что я получил.

Точный w sh синтаксически невозможен, но есть два способа достижения искомой простоты.

Просто используйте 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 ()>

Но если вам нужно что-то более динамичное c, я предлагаю следующее:

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'}
0 голосов
/ 14 апреля 2020

В новейшем 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'
...