Python:Typecheck函数向前 *args,** kwargs没有包装装饰器(PEP 612)

发布于 2025-01-22 06:28:31 字数 1417 浏览 2 评论 0 原文

pep612 添加 parameterspec parameterspec 键入模块,允许您键入由功能装饰器包裹的检查功能(并借助 condenate )。

在导致PEP接受的讨论之一中,引用了函数的函数 *args,** kwargs到其他功能的情况,但是据我所知,除非您使用decorator,否则仍然不支持这一点,因为 ParamSpec 仅在可可类型已经在范围内时才能使用。

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

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(而不是复制函数签名)。

PEP612 adds ParameterSpec to the typing module, allowing you to type-check functions that are wrapped by function decorators (and type-check the decorators themselves with the help of Concatenate).

In one of the discussions leading to acceptance of the PEP, scenarios where functions simply forwarded *args, **kwargs to other functions were cited, but from what I can tell, this is still not supported unless you are using a decorator because ParamSpec can only be used when a Callable type is already in-scope.

For example, I don't know how any of the following fits in (if at all):

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)

or

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)

or

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)

Unless *args and **kwargs are homogeneous, it seems we're still unable to take advantage of type-checking functions that a invoked from other functions where those functions wish to only pass along *args, **kwargs (instead of duplicating the function signatures).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

行至春深 2025-01-29 06:28:31

我在问自己完全一样的问题。我不是专家,但我确实相信您喜欢 paramspec 给出的API不涵盖简单的“包装器”用例。

我的解释是,我们错过了一种语法方式来讲述“这些论点将由这种内部陈述使用”;今天可用的语法更容易,因为所有信息都包含在签名中。

但是(我为此感到非常自豪),我发现您可以通过写一个一次使用装饰器来“作弊”并使其起作用。在某些用例中,它可能很有用(尽管我相信有些好的毕达斯坦主义者将能够有一天能够使此代码毫无用处地旋转,

# 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获得的反馈的屏幕截图:

I was asking myself exactly the same question. I'm no expert, but I do believe like you that the API given by ParamSpec does not cover the simple "wrapper" use case.

My interpretation is that we miss a syntaxical way to tell "those arguments will be used by such internal statement"; the syntax available today is easier because all the information is contained in the signature.

However (I'm quite proud of it), I discovered than you can "cheat" a bit and have it work, by writing a one-time use decorator. It might be useful in some use cases (though I trust some good pythonist will be able to spin up a further PEP one day that would render this code useless)

# 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")

Here's a screenshot of the feedback I get with VSCode + Pylance for the last lines:

Feedback on VSCode+Pylance

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文