【问题标题】:Python: Typecheck functions that forward *args, **kwargs without a wrapping decorator (PEP 612)Python:在没有包装装饰器的情况下转发 *args、**kwargs 的类型检查函数 (PEP 612)
【发布时间】:2022-06-17 22:45:07
【问题描述】:

PEP612ParameterSpec 添加到typing 模块,允许您对由函数装饰器包装的函数进行类型检查(并在Concatenate 的帮助下对装饰器本身进行类型检查)。

在导致接受 PEP 的讨论之一中,引用了函数简单地将 *args、**kwargs 转发到其他函数的场景,但据我所知,除非您使用装饰器,否则仍然不支持这种情况因为 ParamSpec 只能在 Callable 类型已经在作用域内时使用。

例如,我不知道以下任何一个是否适合(如果有的话):

def plot_special(df: pd.DataFrame, p1: int, p2: int, *plot_args, **plot_kwargs) -> None:
   # do something with p1, p2
   df.plot(*plot_args, **plot_kwargs)

class A:
   def f(self, x: int, y: int) -> int:
      return x + y

class B:
   def __init__(self) -> None:
      self.a = A()

   f = A.a # Does not work, self is not of type A

   # Since B.f is not wrapping A.f, does not seem to be a way
   # to contextualize a ParameterSpec
   def f(self, *args, **kwargs) -> int:
      self.a.f(*args, **kwargs)

class A:
    def __int__(self, p1: int, p2: int) -> None:
      self.p1 = p1
      self.p2 = p2

   def f(x: int, y: int) -> int:
      return x + y

class MixinForA:
   def __init__(self, p3: str, *args, **kwargs) -> None:
      self.p3 = p3
      super().__init__(*args, **kwargs)

除非 *args 和 **kwargs 是同质的,否则我们似乎仍然无法利用从其他函数调用的类型检查函数,而这些函数只希望传递 *args、**kwargs(而不是复制函数签名)。

【问题讨论】:

    标签: python types mypy typechecking


    【解决方案1】:

    我问自己同样的问题。我不是专家,但我和你一样相信ParamSpec 提供的 API 并没有涵盖简单的“包装器”用例。

    我的解释是我们错过了一种语法方式来告诉“这些参数将被此类内部语句使用”;今天可用的语法更容易,因为所有信息都包含在签名中。

    但是(我为此感到非常自豪),我发现你可以通过编写一次性使用的装饰器来“作弊”并让它发挥作用。它在某些用例中可能很有用(尽管我相信一些优秀的 Python 专家有一天能够启动另一个 PEP,这会使这段代码变得无用)

    # demo wrapper
    # objective: write a type-checked function
    # that calls inner()
    
    import typing as tp
    import typing_extensions as tp_ext
    
    P = tp_ext.ParamSpec("P") # arguments
    T = tp.TypeVar("T") # return type
    
    def inner(a: int, b: float, *, c: str) -> bool:
        return True
    
    
    def outer_naive(verbose:bool, *args, **kwargs) -> bool:
        if verbose:
            print("Got arguments!", args, kwargs)
        return inner(*args, **kwargs)
    
    # pass type checks, 
    # even though it would fail at runtime because a,b,c are missing
    outer_naive(verbose=True)
    
    def outer_factory(to_wrap: tp.Callable[P, T]) -> tp.Callable[tp_ext.Concatenate[bool, P],T]:
        def outer_aware(verbose: bool, *args: P.args, **kwargs: P.kwargs) -> T:
            if verbose:
                print("Got arguments!", args, kwargs)
            return to_wrap(*args, **kwargs)
        return outer_aware
    
    outer_with_type = outer_factory(inner)
    
    # fails type check ! 
    # Pyright successfully inferred the need for the 3 arguments a,b,c
    inner(True)
    
    # fails type check
    inner(True, "should be an int", 2.5, c="hey")
    
    # pass type check
    inner(True, 1, 1.0, c="hey")
    

    这是我在最后几行使用 VSCode + Pylance 得到的反馈截图:

    【讨论】:

      猜你喜欢
      • 2018-08-28
      • 1970-01-01
      • 1970-01-01
      • 2020-06-15
      • 1970-01-01
      • 1970-01-01
      • 2013-09-25
      • 2016-08-21
      • 2021-10-08
      相关资源
      最近更新 更多