为什么在 Python 函数调用中参数太多/太少时会出现 TypeError

oro*_*aki 5 python api-design

我无法理解为什么TypeError当你提供不属于方法签名的参数时 Python 会引发 a 。

例子:

>>> def funky():
...    pass
... 
>>> funky(500)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: funky() takes no arguments (1 given)
Run Code Online (Sandbox Code Playgroud)

我想,如果这是因为*args预期是None[]在无参数函数的范围内,那就是一个泄漏的抽象,所以我查了一下。

我发现了什么

在PEP-3102TypeError上进行的页面搜索发现,似乎是在其中提出的一种上下文的合理性,但我不明白其合理性。PEP 的示例本质上是在说明该功能基本上是. 在这种情况下是一个非空列表,而不是一个空列表......它们都是相同的类型。如果我没记错的话,这确实合理的,也许 a会更合适。然而,这仍然是一种有漏洞的抽象,因为该示例是用 Python 编写的,这使其更多地是特定用例的实现细节,而不是语言功能。类似的东西对我来说听起来更合适,这让我相信有一些明显的解释我错过了为什么有意义。TypeErrorif args: raise TypeError()argsValueErrorArgumentErrorTypeError

小智 1

该函数接受n参数。你提供的mn != m。这可能是某人代码中的错误或某人对某些 API 的误解的症状。另外,我认为我们无法就这种情况下应该发生的有意义、有用的语义达成一致。多余的论据应该被忽略吗?这会让这样的错误过去,而且由于“错误永远不会悄无声息地过去”,这是不可接受的。类似地,为省略的参数提供一些默认值允许在拼写错误等情况下出现无声的不当行为,因此违反了相同的原则,更不用说“显式优于隐式”,即如果您想传递一些特殊值,您应该只把它写出来。

您引用的 PEP 及其if args: raise TypeError(...)语义仅适用于具有仅关键字参数的函数,这些参数不接受可变参数并*在参数列表中使用普通参数来对此进行编码。有些函数接受有限数量的位置参数,并且可以从仅关键字参数中受益,但可变参数对它们来说没有意义。对于这些, plain 的*存在是为了允许仅关键字参数,而不需要样板代码,并且仍然通知程序员以防有人提供太多参数(出于答案第一部分中列出的原因,这是一件好事)。

至于为什么TypeError选择:“参数数量错误”是静态语言中的编译时类型错误/类型不匹配。我们在 Python 中没有对此类事情进行编译时检查,但这仍然是一种“类型错误”。如果(在 Python 中,非正式的)类型签名说“我接受两个参数”,而您提供了三个,那么这显然违反了该约定。TypeError不仅仅意味着not isinstance(x, expected),像许多其他与打字相关的主题一样(只需考虑继承和“获取父母的东西”与“是父母”),它是一个更广泛的概念。

编辑以回应您对静态类型比较的批评:考虑一元函数与二元函数不同不仅仅是静态类型系统的一个任意限制。即使在像 Python 这样的语言中,它也非常有用,因为两者不够相似,无法“就好像它们走路和嘎嘎一样”(如鸭子打字) - 它们不能以相同的方式使用。(例外情况是,包装任意数量函数的元编程由***解包运算符处理。)它们碰巧共享它们是可调用的事实,但即使 Python 的内置对象层次结构不创建大量相同的对象,它们也是不兼容的仅数量和名称不同的类。type(f)可能会说function,但这并不总是故事的结局。

  • @delnan - 你的回答让我明白了为什么Python会这样做(我对静态相关的东西不太了解),但是你解释得越多,它听起来就越像是一个有漏洞的抽象称之为“类型错误”。Python 处理情况的内部结构与函数 API 无关,对吧?虽然通过各种底层工具、内省等方式暴露出来,但函数定义和函数调用的实现与API没有区别吗? (2认同)