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提示集合,其中包括:
-
List
,Tuple
,Set
,Map
– 分别用于list
,tuple
,map
和map
。 -
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()
…在运行时会失败,如果param1
和param2
都不具有名为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):
该函数接收两个对象,并将它们命名为param1
和param2
,而不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 – 长度参数:您可能需要处理一个函数,用于定义函数时指定的更多参数。 这些参数被称为“可变长度参数”,在函数定义中没有被命名,与所需和缺省参数不同。