Python中的函数参数types

除非我错了,否则在Python中创build一个函数就像这样:

def my_func(param1, param2): # stuff 

但是,实际上并没有给出这些参数的types。 另外,如果我记得,Python是一种强types语言,因此,Python似乎不应该让你传入一个不同于函数创build者期望的types的参数。 但是,Python如何知道函数的用户正在传递适当的types呢? 如果程序是错误的types,程序是否会死亡,假设函数实际上使用参数? 你必须指定types?

Python是强types的,因为每个对象都有一个types,每个对象都知道它的types,所以不可能无意或故意地使用一个types的对象,就好像它是一个不同types的对象,对象的所有基本操作都是委托给它的types。

这与名字无关。 Python中的名称不是“有一个types”:如果当名称被定义,名称引用一个对象 ,并且该对象确实有一个types(但实际上并不强制名称上的types:a名字是一个名字)。

Python中的名称可以很好地在不同的时间引用不同的对象(就像在大多数编程语言中一样,尽pipe不是全部) – 并且对名称没有限制,如果它曾经引用typesX的对象,那么它只会引用其他types为X的对象。对名称的约束不是“强types”概念的一部分,虽然有些静态types的发烧友(名称受到限制,而在静态的AKA编译器中)时间,时尚也是这样)滥用这个词。

其他答案在解释鸭子打字和tzot的简单回答方面做得很好 :

Python没有variables,就像variables有一个types和一个值的其他语言一样; 它有名字指向对象,知道他们的types。

然而 ,自2010年(当第一个问题提出时)有一件有趣的事情发生了变化,即PEP 3107 (在Python 3中实现)的实现。 现在可以实际指定参数的types和函数的返回types的types,如下所示:

 def pick(l: list, index: int) -> int: return l[index] 

我们可以在这里看到, pick 2个参数,一个列表l和一个整数index 。 它也应该返回一个整数。

所以这里暗示l是一个我们可以毫不费力地看到的整数列表,但是对于更复杂的函数,列表应该包含什么可能会有点混乱。 我们也希望index的默认值为0.为了解决这个问题,你可以select像这样写:

 def pick(l: "list of ints", index: int = 0) -> int: return l[index] 

请注意,我们现在将string作为l的types,这在语法上是允许的,但不适合以编程方式进行parsing(我们稍后会回顾)。

需要注意的是,如果你将一个float传入index ,Python不会引发TypeError ,这是Pythondevise哲学中的一个主要点: “我们都在这里同意成人” ,这意味着你期望要知道你可以传递给一个函数什么你不能。 如果你真的想编写引发TypeErrors的代码,你可以使用isinstance函数来检查传入的参数是否是正确的types或它的子类(更多关于为什么你应该很less这样做,你应该做的是写在下一节和评论中):

 def pick(l: list, index: int = 0) -> int: if not isinstance(l, list): raise TypeError return l[index] 

PEP 3107不仅提高了代码的可读性,而且还有几个适合的用例,您可以在这里阅读。


在Python 3.5中引入types注释得到了更多的关注,引入了一个标准的提示types的PEP 484 。

此语法来自正在开发(和PEP 484兼容)的可选静态types检查器工具mypy ( GitHub )。

随着打字模块带有一个非常全面的types提示集合,其中包括:

  • ListTupleSetMap – 分别用于listtuplemapmap
  • Iterable – 对发电机有用。
  • Any – 当它可能是任何东西。
  • Union – 当它可以是指定的一组types内的任何东西时,而不是Any
  • Optional – 当它可能是无。 Union[T, None]简写Union[T, None]
  • TypeVar – 与genericsTypeVar使用。
  • Callable – 主要用于function,但可用于其他可调用。

这个列表代表了最常见的types提示,但是它并不全面。 在打字模块的文档中可以find完整的列表。

以下是使用打字模块中引入的注释方法的旧示例:

 from typing import List def pick(l: List[int], index: int) -> int: return l[index] 

一个强大的function是Callable ,它允许你input以函数作为参数的注释方法。 例如:

 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来更加精确,但是这已经被作为练习留给读者了,因为我相信我已经在我的答案中填充了太多关于types提示所启用的奇妙新特性的信息。


以前,当一个loggingPython代码与例如狮身人面像一些上述function可以通过编写文档格式如下所示获得:

 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] 

正如你所看到的,这需要一些额外的行(确切的数字取决于你想要如何明确以及如何格式化你的文档string)。 但是,现在应该清楚PEP 3107如何提供一种在许多(所有)方面都优越的替代scheme。 与PEP 484结合使用尤其如此,正如我们所看到的那样,它提供了一个标准模块,它为这些types提示/注释定义了语法,可以以明确,精确而灵活的方式使用这些语法/注释,强大的组合。

我个人认为,这是Python有史以来最伟大的function之一。 我不能等待人们开始利用它的力量。 对不起,很长的回答,但这是什么时候我兴奋。


大量使用types提示的Python代码示例可以在这里find。

你不指定一个types。 如果该方法试图访问传入的参数中未定义的属性,该方法将只会失败(在运行时)

所以这个简单的function:

 def no_op(param1, param2): pass 

不pipe两个参数通过什么都不会失败

但是,这个function:

 def call_quack(param1, param2): param1.quack() param2.quack() 

…在运行时会失败,如果param1param2都不具有名为quack可调用属性。

从静态或编译时types检查的意义上来说,Python不是强types的。

大多数Python代码都属于所谓的“Duck Typing” ( 鸭子键入) – 例如,您read在对象上查找方法 – 您不关心对象是磁盘上的还是套接字上的文件,只需要读取N个字节。

许多语言都有variables,这些variables是特定types的,并且具有一定的价值。 Python没有variables; 它具有对象,并使用名称来引用这些对象。

在其他语言中,当你说:

 a = 1 

那么一个(通常是整数)variables将其内容更改为值1。

在Python中,

 a = 1 

意思是“使用名称a来引用对象1 ”。 您可以在交互式Python会话中执行以下操作:

 >>> type(1) <type 'int'> 

函数type用对象1调用; 因为每个对象都知道它的types,所以types很容易find所需的types并返回它。

同样,每当你定义一个函数

 def funcname(param1, param2): 

该函数接收两个对象,并将它们命名为param1param2 ,而不pipe它们的types如何。 如果你想确保收到的对​​象是一个特定的types,就像你需要的types一样对你的函数进行编码,如果没有,就捕获抛出的exception。 抛出的exception通常是TypeError (你使用了一个无效的操作)和AttributeError (你尝试访问一个不存在的成员(方法也是成员))。

正如Alex Martelli解释的那样 ,

正常的Pythonic首选的解决scheme几乎总是“鸭子打字”:尝试使用这个参数,就好像它是某种期望的types一样,在try / except语句中捕获所有可能出现的exception,如果该参数实际上不是事实(或者任何其他types的鸭子模仿它;-),并在except子句中,尝试其他的东西(使用参数“好像”它是其他types)。

阅读他的post的其余部分的有用信息。

Python不关心你传递给它的函数。 当你调用my_func(a,b) ,param1和param2variables将保存a和b的值。 Python不知道你正在调用适当types的函数,并期望程序员照顾。 如果你的函数将被调用不同types的参数,你可以用try / except块来包装访问它们的代码,并以任何你想要的方式评估参数。

你从不指定types; Python有鸭子打字的概念; 基本上处理这些参数的代码将会对它们做出一定的假设 – 可能是通过调用一个参数预期实现的某些方法。 如果参数的types错误,则会抛出exception。

一般情况下,由代码决定是否传递适当types的对象 – 没有编译器提前执行。

有一个臭名昭着的例外,从这个网页值得一提的鸭子打字。

str函数调用__str__类的方法时,它巧妙地将其types:

 >>> class A(object): ... def __str__(self): ... return 'a','b' ... >>> a = A() >>> print a.__str__() ('a', 'b') >>> print str(a) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __str__ returned non-string (type tuple) 

仿佛Guido提示我们,如果程序遇到意想不到的types时应该抛出exception。

在python中,一切都有一个types。 如果参数types支持Python函数,Python函数将执行任何操作。

例如: foo会添加所有可以被__add__ ed的东西;)而不必担心它的types。 所以这意味着,为了避免失败,你应该只提供那些支持添加的东西。

 def foo(a,b): return a + b class Bar(object): pass class Zoo(object): def __add__(self,other): return 'zoom' if __name__=='__main__': print foo(1,2) print foo('james','bond') print foo(Zoo(),Zoo()) print foo(Bar(),Bar()) # should fail 

我没有看到在其他答案中提到的这个,所以我将它添加到锅。 正如其他人所说,python不强制执行function或方法参数的types。 假设你知道你在做什么,如果你真的需要知道传入的东西的types,你会检查它并决定为自己做些什么。

执行此操作的主要工具之一是isinstance()函数。

例如,如果我写一个方法,希望得到原始的二进制文本数据,而不是正常的utf-8编码的string,我可以检查参数的types,以适应我find的,或提出拒绝的例外。

 def process(data): if not isinstance(data, bytes) and not isinstance(data, bytearray): raise TypeError('Invalid type: data must be a byte string or bytearray, not %r' % type(data)) # do more stuff 

Python还提供了各种工具来挖掘对象。 如果你很勇敢,甚至可以使用importlib来创build你自己的任意类的对象。 我这样做是从JSON数据重新创build对象。 这样的事情在C ++这样的静态语言中将是一场噩梦。

您可以使用以下types的forms参数来调用函数

1.必需的参数:必需的参数是以正确的位置顺序传递给函数的参数。 这里函数调用中的参数数量应该与函数定义完全匹配。

2.关键字参数:关键字参数与函数调用有关。 在函数调用中使用关键字参数时,调用方用参数名称标识参数。 这允许您跳过参数或将其置乱,因为Python解释器能够使用提供的关键字来将值与参数匹配。

3.默认参数:默认参数是一个假设默认值的参数,如果该参数的函数调用中没有提供一个值。

4.variables – 长度参数:您可能需要处理一个函数,用于定义函数时指定的更多参数。 这些参数被称为“可变长度参数”,在函数定义中没有被命名,与所需和缺省参数不同。