如何在不调用函数的情况下调用Python的内置函数参数验证?

How can I invoke Python's built-in function argument verification without calling the function?

Python 显然有一种方法可以验证函数调用是否具有有效参数(正确的位置参数数量、正确的关键字参数等)。以下是我的意思的基本示例:

def test_func(x, y, z=None):
    print(x, y, z)

test_func(2)  # Raises a "missing positional argument" TypeError
test_func(1, 2, 3, a=5)  # Raises an "unexpected keyword argument" TypeError

有没有一种方法可以在不实际调用函数的情况下使用此参数验证?

我基本上是在尝试编写一个装饰器,它在调用包装函数本身之前根据函数参数执行一些预处理步骤,例如:

def preprocess(func):
    def wrapper(*args, **kwargs):
        # Verify *args and **kwargs are valid for the original function.
        # I want the exact behavior of calling func() in the case of bad arguments,
        # but without actually calling func() if the arguments are ok.

        preprocess_stuff(*args, **kwargs)
        func(*args, **kwargs)
    return wrapper

我希望我的 wrapper 函数在执行任何预处理工作之前验证参数在包装函数上使用时是否有效。

我想利用 Python 已经在您每次调用函数时进行的检查以及它会引发的各种异常。我只是不想 actually 调用函数,因为函数可能不是幂等的。编写自己的检查和异常感觉就像重新发明轮子。

你不能在不调用函数的情况下调用函数的实际内置参数验证,但你可以使用非常接近的东西。

inspect module has a function signature(), which returns a Signature object representing the argument signature of the function passed to it. That Signature object has a bind() method which attempts to create a BoundArguments 对象使用传递给它的参数。如果这些参数与签名不匹配,则会引发 TypeError

虽然这主要表现得像内置参数绑定逻辑,但它有一些不同之处。例如,它不能总是确定用 C 编写的函数的签名,它与装饰器的交互将取决于它们是否使用 functools.wraps(或设置 __wrapped__ 属性的其他东西)。也就是说,由于无法访问真正的参数绑定逻辑,因此 inspect.signature 是最佳选择。

我们可以使用所有这些来创建您的装饰器:

import functools
import inspect

def preprocess(func):
    sig = inspect.signature(func)
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            sig.bind(*args, **kwargs)
        except TypeError:
            pass  # bad arguments; skip preprocessing
        else:
            print("Preprocessing: args=%r, kwargs=%r" % (args, kwargs))
            # ... etc.
        return func(*args, **kwargs)
    return wrapper

用法:

@preprocess
def test_func(x, y, z=None):
    print(x, y, z)

>>> test_func(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 10, in wrapper
TypeError: test_func() missing 1 required positional argument: 'y'

>>> test_func(1, 2, 3, a=5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 10, in wrapper
TypeError: test_func() got an unexpected keyword argument 'a'

>>> test_func(1, 2)
Preprocessing: args=(1, 2), kwargs={}
1 2 None

请注意,如果提供了错误的参数,您 do 实际上想要调用该函数,因为您 "want the exact behavior of calling func() in the case of bad arguments"(引用您的评论),并且获得调用任意函数的确切行为(即使该行为立即失败)的唯一方法是实际调用它。在这种情况下,您不想做的是预处理,上面的装饰器为您完成了。