检查函数参数类型是Pythonic吗?

2024-05-17 16:19:32 发布

您现在位置:Python中文网/ 问答频道 /正文

我知道,在Python中,类型检查函数参数通常是不受欢迎的,但是我想我已经找到了这样做有意义的情况。

在我的项目中,我有一个抽象基类Coord,它有一个子类Vector,它有更多的特性,如旋转、改变幅度等。数字的列表和元组也将返回True,因为isinstance(x, Coord).我还有很多函数和方法接受这些协调类型作为参数。我已经设置了decorators来检查这些方法的参数。以下是一个简化版本:

class accepts(object):
    def __init__(self, *types):
        self.types = types

    def __call__(self, func):
        def wrapper(*args):
            for i in len(args):
                if not isinstance(args[i], self.types[i]):
                    raise TypeError

            return func(*args)

        return wrapper

这个版本很简单,仍然有一些错误。只是为了说明这一点。它的用途如下:

@accepts(numbers.Number, numbers.Number)
def add(x, y):
    return x + y

注意:我只检查抽象基类的参数类型。

这是个好主意吗?有没有更好的方法来做到这一点,而不必在每个方法中重复类似的代码?

编辑:

如果我要做同样的事情,但不是在decorator中预先检查类型,而是在decorator中捕获异常:

class accepts(object):
    def __init__(self, *types):
        self.types = types

    def __call__(self, func):
        def wrapper(*args):

            try:
                return func(*args)
            except TypeError:
                raise TypeError, message
            except AttributeError:
                raise AttributeError, message

        return wrapper

这样更好吗?


Tags: 方法self类型参数returndefargswrapper
3条回答

你的品味可能会有所不同,但Python(Pythonic,tm)的风格是,只需继续使用你需要的对象。如果它们不支持您正在尝试的操作,则会引发异常。这就是所谓的duck typing

支持这种样式有几个原因:首先,它允许您在现有代码中使用新类型的对象,从而启用多态性,只要新对象支持正确的操作。其次,它通过避免大量检查简化了成功的途径。

当然,使用错误参数时得到的错误消息在类型检查中比duck类型更清楚,但正如我所说,您的品味可能会有所不同。

如果这是规则的例外,没关系。但是,如果项目的工程/设计围绕着类型检查每个函数(或大多数函数),那么也许您不想使用Python,那么用C#代替它怎么样?

根据我的判断,你制作一个用于类型检查的decorator通常意味着你将大量使用它。因此,在这种情况下,虽然将公共代码分解成decorator是pythonic,但用于类型检查的事实并不是非常pythonic。

Python中鼓励使用Duck类型的原因之一是,有人可能会包装您的一个对象,然后它看起来会是错误的类型,但仍然有效。

下面是包装对象的类的示例。LoggedObject在所有方面都像它包装的对象一样,但是当您调用LoggedObject时,它会在执行调用之前记录调用。

from somewhere import log
from myclass import A

class LoggedObject(object):
    def __init__(self, obj, name=None):
        if name is None:
            self.name = str(id(obj))
        else:
            self.name = name
        self.obj = obj
    def __call__(self, *args, **kwargs):
        log("%s: called with %d args" % (self.name, len(args)))
        return self.obj(*args, **kwargs)

a = LoggedObject(A(), name="a")
a(1, 2, 3)  # calls: log("a: called with 3 args")

如果显式测试isinstance(a, A),它将失败,因为aLoggedObject的实例。如果你让鸭子打字就行了,这就行了。

如果有人错误地传递了错误类型的对象,则会引发AttributeError之类的异常。如果显式地检查类型,异常可能会更清楚,但我认为总体来说,这种情况是duck类型的胜利。

有时候你真的需要测试这个类型。我最近学到的一点是:当你在编写与序列一起工作的代码时,有时你真的需要知道你是否有一个字符串,或者它是任何其他类型的序列。考虑一下:

def llen(arg):
    try:
        return max(len(arg), max(llen(x) for x in arg))
    except TypeError: # catch error when len() fails
        return 0 # not a sequence so length is 0

这应该返回序列的最长长度,或者任何嵌套在其中的序列。它起作用:

lst = [0, 1, [0, 1, 2], [0, 1, 2, 3, 4, 5, 6]]
llen(lst)  # returns 7

但是如果调用llen("foo")它将永远递归,直到堆栈溢出。

问题是字符串有一个特殊的属性,即使从字符串中取最小的元素,它们总是像一个序列;一个字符串仍然是一个序列。因此,如果没有字符串的显式测试,我们就不能编写llen()。

def llen(arg):
    if isinstance(arg, str):  # Python 3.x; for 3.x use isinstance(arg, basestring)
        return len(arg)
    try:
        return max(len(arg), max(llen(x) for x in arg))
    except TypeError: # catch error when len() fails
        return 0 # not a sequence so length is 0

相关问题 更多 >