python 检查函数参数类型是 Pythonic 吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1950386/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is it Pythonic to check function argument types?
提问by Brad Zeis
I know, type checking function arguments is generally frowned upon in Python, but I think I've come up with a situation where it makes sense to do so.
我知道,在 Python 中通常不赞成类型检查函数参数,但我认为我已经提出了一种这样做的情况。
In my project I have an Abstract Base Class Coord
, with a subclass Vector
, which has more features like rotation, changing magnitude, etc. Lists and tuples of numbers will also return True for isinstance(x, Coord).
I also have many functions and methods that accept these Coord types as arguments. I've set up decorators to check the arguments of these methods. Here is a simplified version:
在我的项目中,我有一个 Abstract Base ClassCoord
和一个子类Vector
,它具有更多功能,如旋转、改变幅度等。数字列表和元组也将返回 True 因为isinstance(x, Coord).
我还有许多函数和方法接受这些 Coord 类型作为参数. 我已经设置了装饰器来检查这些方法的参数。这是一个简化版本:
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
This version is very simple, it still has some bugs. It's just there to illustrate the point. And it would be used like:
这个版本很简单,还是有一些bug。它只是为了说明这一点。它会像这样使用:
@accepts(numbers.Number, numbers.Number)
def add(x, y):
return x + y
Note: I'm only checking argument types against Abstract Base Classes.
注意:我只是根据抽象基类检查参数类型。
Is this a good idea? Is there a better way to do it without having to repeat similar code in every method?
这是一个好主意吗?有没有更好的方法来做到这一点,而不必在每种方法中重复类似的代码?
Edit:
编辑:
What if I were to do the same thing, but instead of checking the types beforehand in the decorator, I catch the exceptions in the 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
Is that any better?
这样更好吗?
回答by Ned Batchelder
Your taste may vary, but the Pythonic(tm) style is to just go ahead and use objects as you need to. If they don't support the operations you're attempting, an exception will be raised. This is known as duck typing.
您的品味可能会有所不同,但 Pythonic(tm) 风格是继续前进并根据需要使用对象。如果它们不支持您尝试的操作,则会引发异常。这称为鸭子类型。
There are a few reasons for favoring this style: first, it enables polymorphism by allowing you to use new kinds of objects with existing code so long as the new objects support the right operations. Second, it streamlines the successful path by avoiding numerous checks.
支持这种风格有几个原因:首先,只要新对象支持正确的操作,它就允许您在现有代码中使用新类型的对象,从而实现多态性。其次,它通过避免大量检查来简化成功的路径。
Of course, the error message you get when using wrong arguments will be clearer with type checking than with duck typing, but as I say, your taste may vary.
当然,使用错误参数时得到的错误信息在类型检查中会比鸭子类型更清晰,但正如我所说,你的品味可能会有所不同。
回答by steveha
One of the reasons Duck Typing is encouraged in Python is that someone might wrap one of your objects, and then it will look like the wrong type, but still work.
在 Python 中鼓励 Duck Typing 的原因之一是有人可能包装了你的一个对象,然后它看起来像是错误的类型,但仍然有效。
Here is an example of a class that wraps an object. A LoggedObject
acts in all ways like the object it wraps, but when you call the LoggedObject
, it logs the call before performing the call.
这是包装对象的类的示例。ALoggedObject
在所有方面都像它包装的对象一样,但是当您调用 时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")
If you explicitly test for isinstance(a, A)
it will fail, because a
is an instance of LoggedObject
. If you just let the duck typing do its thing, this will work.
如果你明确地测试isinstance(a, A)
它会失败,因为a
是LoggedObject
. 如果你只是让鸭子打字做它的事情,这会奏效。
If someone passes the wrong kind of object by mistake, some exception like AttributeError
will be raised. The exception might be clearer if you check for types explicitly, but I think overall this case is a win for duck typing.
如果有人错误地传递了错误类型的对象,AttributeError
则会引发一些异常。如果您明确检查类型,则异常可能会更清楚,但我认为总体而言,这种情况是鸭子类型的胜利。
There are times when you really need to test the type. The one I learned recently is: when you are writing code that works with sequences, sometimes you really need to know if you have a string, or it's any other kind of sequence. Consider this:
有时您确实需要测试类型。我最近学到的一点是:当你编写处理序列的代码时,有时你真的需要知道你是否有一个字符串,或者它是任何其他类型的序列。考虑一下:
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
This is supposed to return the longest length of a sequence, or any sequence nested inside it. It works:
这应该返回序列的最长长度,或嵌套在其中的任何序列。有用:
lst = [0, 1, [0, 1, 2], [0, 1, 2, 3, 4, 5, 6]]
llen(lst) # returns 7
But if you call llen("foo")
, it will recurse foreveruntil stack overflow.
但是如果你调用llen("foo")
,它将永远递归直到堆栈溢出。
The problem is that strings have the special property that they always act like a sequence, even when you take the smallest element from the string; a one-character string is still a sequence. So we cannot write llen() without an explicit test for a string.
问题是字符串有一个特殊的属性,它们总是像一个序列,即使你从字符串中取出最小的元素;一个单字符的字符串仍然是一个序列。所以我们不能在没有对字符串进行显式测试的情况下编写 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
回答by Jochen Ritzel
There has been some talk about this because Py3k supports a function annotationsof which type annotations are an application. There was also an effort to roll type checkingin Python2.
有一些关于这个的讨论,因为 Py3k 支持类型注释是应用程序的函数注释。还努力在 Python2 中滚动类型检查。
I think it never took off because the basic problem you're trying to solve ("find type bugs") is either trivial to begin with (you see a TypeError
) or pretty hard (slight difference in the type interfaces). Plus to get it right, you need typeclasses and classify every type in Python. It's a lot of work for mostly nothing. Not to mention you'd be doing runtimechecks all the time.
我认为它从未成功,因为您试图解决的基本问题(“查找类型错误”)要么开始时微不足道(您看到TypeError
),要么非常困难(类型接口略有不同)。另外,要做到正确,您需要类型类并对 Python 中的每种类型进行分类。这是很多工作,但几乎一无所获。更不用说您会一直进行运行时检查。
Python already has a strong and predictable type system. If we will ever see something more powerful, I hope it comes through type annotations and clever IDEs.
Python 已经拥有强大且可预测的类型系统。如果我们能看到更强大的东西,我希望它来自类型注释和聪明的 IDE。
回答by hasen
If this is an exception to the rule, it's ok. But if the engineering/design of your project revolves around type checking every function (or most of them) then maybe you don't want to use Python, how about C# instead?
如果这是规则的例外,那没关系。但是,如果您的项目的工程/设计围绕着对每个函数(或大多数函数)的类型检查,那么您可能不想使用 Python,那么 C# 呢?
From my judgment, you making a decorator for type checking generally means that you're going to be using it a lot. So in that case, while factoring common code into a decorator is pythonic, the fact that it's for type checking is not very pythonic.
根据我的判断,您为类型检查制作装饰器通常意味着您将大量使用它。所以在那种情况下,虽然将公共代码分解到装饰器中是 pythonic,但它用于类型检查的事实并不是非常 pythonic。
回答by Pasha
It is.
它是。
"Being Pythonic" is not a well-defined concept, but it is generally understood as writing code using appropriate language constructs, not being more verbose than necessary, following Python style guide (PEP 8), and generally striving to have code that is pleasant to read. We also have the Zen of Python (import this
) as guidance.
“Being Pythonic”不是一个明确定义的概念,但通常被理解为使用适当的语言结构编写代码,不要过于冗长,遵循 Python 风格指南 (PEP 8),并且通常努力编写令人愉悦的代码读书。我们也有 Python 之禅 ( import this
) 作为指导。
Does putting @accepts(...)
annotation on top of your function helps or hurts readability? Probably helps, because Rule #2 says "Explicit is better than implicit"
. There is also PEP-484which was specifically designed for exactly same purpose.
将@accepts(...)
注释放在函数之上是有助于还是损害可读性?可能有帮助,因为规则 #2 说"Explicit is better than implicit"
。还有专为完全相同的目的而设计的PEP-484。
Does checking types at run timecount as Pythonic? Surely, it takes a toll on the execution speed -- but the goal of Python was never to produce the most performant code possible, everything-else-be-damned. Of course fast code is better than slow, but then readable code is better than spaghetti code, maintainable code is better than hackish code, and reliable code is better than buggy. So, depending on the system you're writing, you may find that the tradeoff is worth it, and using runtime type checks is worth it.
在运行时检查类型算作 Pythonic 吗?当然,它会影响执行速度——但 Python 的目标从来不是尽可能地生成性能最高的代码,其他一切都该死。当然,快的代码比慢的好,但是可读的代码比意大利面条的代码好,可维护的代码比黑客的代码好,可靠的代码比错误的代码好。因此,根据您正在编写的系统,您可能会发现这种折衷是值得的,而使用运行时类型检查也是值得的。
In particular, Rule #10 "Errors should never pass silently."
may be viewed as supporting the extra type checks. As an example, consider the following simple case:
特别是,规则#10"Errors should never pass silently."
可以被视为支持额外的类型检查。例如,考虑以下简单案例:
class Person:
def __init__(self, firstname: str, lastname: str = ""):
self.firstname = firstname
self.lastname = lastname
def __repr__(self) -> str:
return self.firstname + " " + self.lastname
What happens when you call it like this: p = Person("John Smith".split())
? Well, nothing at first. (This is already problematic: an invalid Person
object was created, yet this error has passed silently). Then some time later you try to view the person, and get
当你这样称呼它时会发生什么:p = Person("John Smith".split())
?嗯,一开始什么都没有。(这已经有问题了:Person
创建了一个无效的对象,但这个错误已经悄悄地过去了)。然后一段时间后,您尝试查看此人,并获得
>>> print(p)
TypeError: can only concatenate tuple (not "str") to tuple
If you have JUST created the object, and if you're experienced Python programmer, then you'll figure out what's wrong fairly quickly. But what if not? The error message is borderline useless (i.e. you need to know the internals of the Person
class to make any use of it). And what if you did not view this particular object, but pickled it into a file, which was sent to another department and loaded few months later? By the time the error is identified and corrected, your job may already be in trouble...
如果您刚刚创建了对象,并且您是经验丰富的 Python 程序员,那么您会很快找出问题所在。但如果不是呢?错误消息是无用的(即您需要知道Person
类的内部结构才能使用它)。如果您没有查看此特定对象,而是将其腌制为一个文件,该文件被发送到另一个部门并在几个月后加载,该怎么办?当错误被识别并纠正时,您的工作可能已经陷入困境……
That being said, you don't have to write the type-checking decorators yourself. There already exist modules specifically for this purpose, for example
话虽如此,您不必自己编写类型检查装饰器。已经存在专门用于此目的的模块,例如
回答by Eric O Lebigot
In addition to the ideas already mentioned, you might want to "coerce" the input data into a type that has the operations you need. For instance, you might want to convert a tuple of coordinates into a Numpy array, so that you can perform linear algebra operations on it. The coercion code is quite general:
除了已经提到的想法之外,您可能希望将输入数据“强制”为具有所需操作的类型。例如,您可能希望将坐标元组转换为 Numpy 数组,以便对其执行线性代数运算。强制代码非常通用:
input_data_coerced = numpy.array(input_data) # Works for any input_data that is a sequence (tuple, list, Numpy array…)