Python中的函数参数类型
除非我误解了,否则在Python中创建一个函数就像这样工作:
def my_func(param1, param2):
# stuff
但是,实际上并没有给出这些参数的类型。 另外,如果我还记得,Python是一种强类型语言,因此Python看起来不应该让你传入一个不同于函数创建者期望类型的参数。 但是,Python如何知道函数的用户正在传递适当的类型? 如果程序是错误的类型,程序是否会死亡,假设函数实际使用参数? 你必须指定类型吗?
Python是强类型的,因为每个对象都有一个类型,每个对象都知道它的类型,所以不可能无意或故意使用类型的对象“好像”它是不同类型的对象,并且对象的所有基本操作都是委托给它的类型。
这与名字无关。 Python中的名称没有“有一个类型”:如果和当名称的定义,名称引用一个对象,并且该对象确实有一个类型(但实际上并不强制名称上的类型:a名字是一个名字)。
Python中的名称可以很好地在不同的时间引用不同的对象(就像在大多数编程语言中一样,尽管不是全部) - 并且名称没有限制,因此如果它曾经引用类型X的对象,它只会引用其他类型为X的对象。 名称上的约束不是“强类型”概念的一部分,虽然有些静态类型的发烧友(名称受到限制,而在静态的AKA编译器中)时间,时尚)也会这样滥用这个术语。
其他答案在解释鸭子打字和tzot的简单回答方面做得很好:
Python没有变量,就像变量具有类型和值的其他语言一样; 它有名称指向对象,它知道它们的类型。
然而 ,自2010年以来(当第一个问题被提出时),一个有趣的事情发生了变化,即PEP 3107(在Python 3中实现)的实现。 现在可以实际指定参数的类型和函数的返回类型的类型,如下所示:
def pick(l: list, index: int) -> int:
return l[index]
我们可以在这里看到, pick
2个参数,一个列表l
和一个整数index
。 它也应该返回一个整数。
因此,这里暗示l
是一个我们可以毫不费力地看到的整数列表,但对于更复杂的函数,它可能会让列表包含的内容有点混乱。 我们也希望的默认值index
为0。为了解决这个问题,你可以选择写pick
喜欢这个:
def pick(l: "list of ints", index: int = 0) -> int:
return l[index]
请注意,我们现在将一个字符串作为l
的类型,这在语法上是允许的,但它不适用于以编程方式解析(我们稍后会回来)。
值得注意的是,如果你将一个float传入index
,那么Python不会引发TypeError
,因为Python的设计理念中的一个要点是:“我们都在这里同意成人”,这意味着你是期望知道你可以传递给一个函数的东西,以及你不能。 如果你真的想编写引发TypeError的代码,你可以使用isinstance
函数来检查传入的参数是否是正确的类型或它的子类,如下所示:
def pick(l: list, index: int = 0) -> int:
if not isinstance(l, list):
raise TypeError
return l[index]
更多关于为什么你应该很少这样做,你应该做什么,而不是在下一节和评论中讨论。
PEP 3107不仅提高了代码的可读性,而且还有几个适合的用例,您可以在这里阅读。
在Python 3.5中引入类型注释得到了更多关注,引入了PEP 484,它引入了用于提示类型的标准模块。
此语法来自正在开发(和PEP 484兼容)的可选静态类型检查器工具mypy(GitHub)。
随着打字模块带有一个非常全面的类型提示集合,其中包括:
List
, Tuple
, Set
, Map
-为list
, tuple
, set
和map
分别。 Iterable
- 对发电机有用。 Any
- 当它可能是任何东西。 Union
- 当它可以是指定的一组类型中的Any
,与Any
相对。 Optional
- 当它可能是无。 Union[T, None]
简写Union[T, None]
。 TypeVar
- 与泛型TypeVar
使用。 Callable
- 主要用于功能,但可用于其他可调用。 这个列表代表了最常见的类型提示,但它并非详尽无遗。 完整的列表可以在打字模块的文档中找到。
以下是使用打字模块中引入的注释方法的旧示例:
from typing import List
def pick(l: List[int], index: int) -> int:
return l[index]
一个强大的功能是Callable
,它允许您键入以函数作为参数的批注方法。 例如:
from typing import Callable, Any, Iterable
def imap(f: Callable[[Any], Any], l: Iterable[Any]) -> List[Any]:
"""An immediate version of map, don't pass it any infinite iterables!"""
return list(map(f, l))
使用TypeVar
而不是Any
可以使上述示例更加精确,但是这已经作为练习留给读者了,因为我相信我已经在我的答案中填入了太多有关由类型提示启用的精彩新功能的信息。
以前,当一个人用例如Sphinx记录Python代码时,可以通过编写像这样格式化的文档字符串来获得上述某些功能:
def pick(l, index):
"""
:param l: list of integers
:type l: list
:param index: index at which to pick an integer from *l*
:type index: int
:returns: integer at *index* in *l*
:rtype: int
"""
return l[index]
正如你所看到的,这需要一些额外的行(确切的数字取决于你想要如何明确以及如何格式化你的文档字符串)。 但是现在应该清楚PEP 3107如何提供一种在许多(所有)方面都优越的替代方案。 正如我们所看到的,这与PEP 484相结合尤其如此,它提供了一个标准模块,它定义了这些类型提示/注释的语法,可以以明确且精确而灵活的方式使用,从而为强大的组合。
在我个人看来,这是Python有史以来最伟大的功能之一。 我不能等待人们开始利用它的力量。 很抱歉,很长的回答,但这是我兴奋时发生的事情。
大量使用类型提示的Python代码示例可以在这里找到。
您不指定类型。 如果该方法试图访问传入的参数中未定义的属性,该方法将只会失败(在运行时)。
所以这个简单的功能:
def no_op(param1, param2):
pass
不管两个参数通过什么都不会失败。
但是,这个功能:
def call_quack(param1, param2):
param1.quack()
param2.quack()
...在运行时会失败,如果param1
和param2
都不具有名为quack
可调用属性。