python中的函数参数类型

Function parameter types in Python

除非我弄错了,否则在Python中创建函数的工作方式如下:

1
2
def my_func(param1, param2):
    # stuff

但是,实际上并没有给出这些参数的类型。另外,如果我还记得,python是一种强类型语言,因此,似乎python不应该让您传入与函数创建者期望的不同类型的参数。但是,python如何知道函数的用户正在传入正确的类型?如果程序是错误的类型,假设函数实际使用了参数,它会死吗?必须指定类型吗?


其他答案在解释duck输入和Tzot的简单答案方面做得很好:好的。

Python does not have variables, like other languages where variables have a type and a value; it has names pointing to objects, which know their type.

Ok.

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

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

我们可以看到,pick接受两个参数,一个列表l和一个整数index。它还应该返回一个整数。好的。

因此,这里暗示l是一个整数列表,我们不费吹灰之力就能看到它,但是对于更复杂的函数,它可能会对列表应该包含的内容有点混淆。我们还希望index的默认值为0。为了解决这个问题,您可以选择这样编写pick:好的。

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

注意,我们现在把一个字符串作为l的类型,这在语法上是允许的,但是它不适合以编程方式解析(稍后我们将讨论)。好的。

重要的是要注意,如果您将float传递到index中,python不会提升TypeError,这是python设计理念的一个要点:"我们在这里都是同意的成年人",这意味着您应该知道什么可以传递到函数,什么不能传递。如果您真的想编写代码that抛出typeerrors您可以使用isinstance函数检查传递的参数是否属于正确的类型或其子类,如下所示:好的。

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

更多关于为什么你很少这样做的原因,以及你应该做什么在下一节和评论中讨论。好的。

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

在python 3.5中,由于引入了PEP484,类型注释得到了更多的关注,PEP484引入了一个用于类型提示的标准模块。好的。

这些类型提示来自于类型检查器mypy(github),它现在符合PEP 484。好的。

在输入模块中,有相当全面的类型提示集合,包括:好的。

  • ListTupleSetMap—分别用于ListTupleSetMap
  • Iterable—适用于发电机。
  • Any—什么时候可以。
  • Union—它可以是一组特定类型中的任何内容,而不是Any
  • Optional—可能没有。Union[T, None]的简写。
  • TypeVar—与仿制药一起使用。
  • Callable—主要用于函数,但也可用于其他可调用文件。

这些是最常见的类型提示。在输入模块的文档中可以找到完整的列表。好的。

下面是使用输入模块中介绍的注释方法的旧示例:好的。

1
2
3
4
from typing import List

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

一个强大的特性是Callable,它允许您键入将函数作为参数的注释方法。例如:好的。

1
2
3
4
5
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可能会更加精确,但这是留给读者的练习,因为我相信我已经用太多关于类型暗示所实现的奇妙新功能的信息填充了我的答案。好的。

以前,当一个文档化的python代码使用例如sphinx时,可以通过编写如下格式的docstrings来获得上述一些功能:好的。

1
2
3
4
5
6
7
8
9
10
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]

如您所见,这需要一些额外的行(确切的数字取决于您想要的明确程度以及您如何格式化docstring)。但现在你应该清楚地知道,PEP 3107是如何提供一种在许多方面(全部)都有选择的。方法优越。这一点尤其适用于与PEP484的组合,正如我们所看到的,PEP484提供了一个标准模块,该模块为这些类型提示/注释定义了一种语法,可以以一种明确、精确而又灵活的方式使用,从而实现强大的组合。好的。

在我个人看来,这是Python有史以来最伟大的特性之一。我等不及人们开始利用它的力量。很抱歉回答得太长了,但这就是我兴奋时所发生的事情。好的。

这里可以找到大量使用类型暗示的Python代码示例。好的。好啊。


python是强类型的,因为每个对象都有一个类型,每个对象都知道它的类型,不可能意外地或故意地使用一个类型为"好像"的对象,它是一个不同类型的对象,并且对该对象的所有基本操作都委托给它的类型。

这与名字无关。python中的一个名称没有"有一个类型":如果定义了一个名称,则该名称引用了一个对象,而该对象确实有一个类型(但实际上并不强制在该名称上使用一个类型:名称是一个名称)。

python中的名称可以在不同的时间完美地引用不同的对象(在大多数编程语言中,虽然不是全部),并且对名称没有约束,因此,如果它曾经引用过X类型的对象,那么它将被进一步约束为只引用X类型的其他对象。对名称的约束不是T的一部分。他提出了"强类型"的概念,尽管一些热衷于静态类型的人(在静态类型中,名字也会受到约束,也就是编译时和时尚的人)确实是这样滥用这个词的。


您没有指定类型。只有当该方法试图访问未在传入参数上定义的属性时(在运行时),该方法才会失败。

所以这个简单的函数:

1
2
def no_op(param1, param2):
    pass

…无论传入哪两个参数,都不会失败。

但是,此功能:

1
2
3
def call_quack(param1, param2):
    param1.quack()
    param2.quack()

…如果param1param2都没有名为quack的可调用属性,则运行时将失败。


许多语言都有变量,这些变量属于特定类型并具有值。python没有变量;它有对象,您使用名称来引用这些对象。

在其他语言中,当你说:

1
a = 1

然后(通常为整数)变量将其内容更改为值1。

在Python中,

1
a = 1

表示"使用名称a引用对象1"。您可以在交互式Python会话中执行以下操作:

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

函数type是用对象1调用的;由于每个对象都知道它的类型,所以type很容易找到并返回该类型。

同样,无论何时定义函数

1
def funcname(param1, param2):

函数接收两个对象,并将它们命名为param1param2,而不管它们的类型如何。如果要确保接收到的对象是特定类型的,请将函数编码为所需类型,如果不需要,则捕获引发的异常。引发的异常通常是TypeError(使用了无效操作)和AttributeError(尝试访问不存在的成员(方法也是成员))


python不是静态或编译时类型检查的强类型。

大多数python代码都属于所谓的"duck-typing"——例如,您在一个对象上寻找一个方法read——您不关心对象是磁盘上的文件还是套接字,您只想从中读取n个字节。


正如亚历克斯·马泰利解释的那样,

The normal, Pythonic, preferred solution is almost invariably"duck typing": try using the argument as if it was of a certain desired type, do it in a try/except statement catching all exceptions that could arise if the argument was not in fact of that type (or any other type nicely duck-mimicking it;-), and in the except clause, try something else (using the argument"as if" it was of some other type).

阅读他文章的其余部分以获取有用的信息。


python不关心您将什么传递给它的函数。当您调用my_func(a,b)时,param1和param2变量将保存a和b的值。python不知道您正在使用正确的类型调用函数,并希望程序员来处理这个问题。如果您的函数将使用不同类型的参数来调用,那么您可以用try/except块包装访问它们的代码,并以您想要的任何方式评估参数。


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

str函数调用__str__类方法时,它会巧妙地检查其类型:

1
2
3
4
5
6
7
8
9
10
11
>>> 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提示我们,如果程序遇到意外的类型,它应该引发哪个异常。


永远不要指定类型;python有duck类型的概念;基本上,处理参数的代码会对它们做出某些假设——可能通过调用某个参数要实现的某些方法。如果参数类型错误,则将引发异常。

通常情况下,由您的代码来确保您传递的是正确类型的对象——没有编译器可以提前强制执行这一点。


我在其他答案中没有看到这一点,所以我会把它加到罐子里。

正如其他人所说,python不在函数或方法参数上强制类型。假设你知道你在做什么,如果你真的需要知道被传递的东西的类型,你将检查它并决定为你自己做什么。

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

例如,如果我编写的方法希望获得原始的二进制文本数据,而不是普通的UTF-8编码字符串,那么我可以在输入时检查参数的类型,并根据我找到的内容进行调整,或者引发一个要拒绝的异常。

1
2
3
4
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动态地创建你自己的任意类的对象。我这样做是为了从JSON数据中重新创建对象。在C++这样的静态语言中,这样的事情将是一场噩梦。


在Python中,一切都有一个类型。如果参数类型支持的话,python函数可以做任何需要做的事情。

示例:foo将添加所有可以添加__add__ed;)的内容,而不必太担心它的类型。这意味着,为了避免失败,您应该只提供那些支持添加的东西。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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