除非我弄错了,在Python中创建一个函数是这样的:

def my_func(param1, param2):
    # stuff

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


从静态或编译时类型检查的意义上讲,Python不是强类型的。

大多数Python代码都属于所谓的“Duck Typing”——例如,你寻找一个对象上读取的方法——你不关心对象是磁盘上的文件还是套接字,你只想从中读取N个字节。


你从不指定类型;Python有鸭子类型的概念;基本上,处理参数的代码将对参数做出某些假设——可能通过调用参数期望实现的某些方法。如果参数类型错误,则会引发异常。

一般来说,这取决于你的代码来确保你传递的对象是正确类型的——没有编译器提前强制这一点。


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

这个简单的函数:

def no_op(param1, param2):
    pass

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

然而,这个函数:

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

... 如果param1和param2都没有名为quack的可调用属性,将在运行时失败。


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


Alex Martelli解释道,

正常的、Pythonic的、首选的解决方案几乎总是“duck typing”:尝试使用参数,好像它是某种所需的类型,在try/except语句中执行它,捕捉如果参数实际上不是该类型(或任何其他类型,很好地模仿它;-)可能出现的所有异常,并在except子句中尝试其他内容(使用参数“as if”它是其他类型)。

阅读他的文章以获得有用的信息。


Python是强类型的,因为每个对象都有一个类型,每个对象都知道它的类型,不可能意外或故意使用一个类型的对象,“好像”它是一个不同类型的对象,对象上的所有基本操作都委托给它的类型。

这和名字无关。Python中的名称没有“类型”:如果定义了名称,则名称指向对象,并且对象确实具有类型(但这实际上并不强制名称具有类型:名称就是名称)。

A name in Python can perfectly well refer to different objects at different times (as in most programming languages, though not all) -- and there is no constraint on the name such that, if it has once referred to an object of type X, it's then forevermore constrained to refer only to other objects of type X. Constraints on names are not part of the concept of "strong typing", though some enthusiasts of static typing (where names do get constrained, and in a static, AKA compile-time, fashion, too) do misuse the term this way.


在Python中,所有东西都有类型。如果参数类型支持,Python函数将执行它被要求执行的任何操作。

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

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没有变量;它有对象,你用名字来指代这些对象。

在其他语言中,当你说:

a = 1

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

在Python中,

a = 1

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

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

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

同样,无论何时定义函数

def funcname(param1, param2):

该函数接收两个对象,并将它们命名为param1和param2,而不考虑它们的类型。如果您想要确保接收到的对象是特定类型的,那么就按照它们是所需的类型来编写函数,如果不是,则捕获抛出的异常。抛出的异常通常是TypeError(您使用了无效操作)和AttributeError(您试图访问不存在的成员(方法也是成员))。


其他的回答很好地解释了鸭子的打字方式和tzot的简单回答:

Python没有变量,不像其他语言,变量有类型和值;它具有指向对象的名称,这些对象知道它们的类型。

然而,自2010年(第一次提出这个问题的时候)以来,有一件有趣的事情发生了变化,那就是PEP 3107的实现(在Python 3中实现)。现在你实际上可以像这样指定形参的类型和函数的返回类型:

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

这里我们可以看到pick有两个参数,一个列表l和一个整数索引。它还应该返回一个整数。

因此,这里暗示l是一个整数列表,我们可以很容易地看到,但对于更复杂的函数,列表应该包含什么可能有点令人困惑。我们还希望index的默认值为0。要解决这个问题,你可以选择这样写pick:

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

请注意,我们现在放入一个字符串作为l类型,这在语法上是允许的,但它不适用于以编程方式解析(稍后我们将回到这一点)。

需要注意的是,如果你将浮点数传递给索引,Python不会引发TypeError,原因是Python设计哲学中的主要观点之一:“我们都是成年人”,这意味着你应该知道什么可以传递给函数,什么不能。如果你真的想编写抛出TypeErrors的代码,你可以使用isinstance函数来检查传入的参数是否属于正确的类型或它的子类,如下所示:

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

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

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


随着PEP 484的引入,类型注释在Python 3.5中得到了更多的关注,PEP 484引入了用于类型提示的标准模块类型。

这些类型提示来自类型检查器myypy (GitHub),它现在与PEP 484兼容。

typing模块提供了一个非常全面的类型提示集合,包括:

List, Tuple, Set, Dict -分别用于List, Tuple, Set和Dict。 Iterable -用于生成器。 什么都有可能。 Union——可以是指定类型集中的任何内容,而不是Any。 可选—当它可能为None时。联合的简写[T, None]。 TypeVar -与泛型一起使用。 可调用的-主要用于函数,但也可以用于其他可调用的。

这些是最常见的类型提示。在typing模块的文档中可以找到完整的清单。

下面是使用typing模块中引入的注释方法的旧示例:

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]

As you can see, this takes a number of extra lines (the exact number depends on how explicit you want to be and how you format your docstring). But it should now be clear to you how PEP 3107 provides an alternative that is in many (all?) ways superior. This is especially true in combination with PEP 484 which, as we have seen, provides a standard module that defines a syntax for these type hints/annotations that can be used in such a way that it is unambiguous and precise yet flexible, making for a powerful combination.

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


一个大量使用类型提示的Python代码示例可以在这里找到。


在这个页面上,有一个臭名昭著的例外值得提及。

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

>>> 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不会对函数或方法参数强制执行类型。假设您知道自己在做什么,如果您确实需要知道传入的内容的类型,您将检查它并决定自己要做什么。

完成此任务的主要工具之一是isinstance()函数。

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

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++这样的静态语言中是一场噩梦。


要有效地使用typing模块(Python 3.5新增),请包含all(*)。

from typing import *

你将准备使用:

List, Tuple, Set, Map - for list, tuple, set and map respectively.
Iterable - useful for generators.
Any - when it could be anything.
Union - when it could be anything within a specified set of types, as opposed to Any.
Optional - when it might be None. Shorthand for Union[T, None].
TypeVar - used with generics.
Callable - used primarily for functions, but could be used for other callables.

然而,你仍然可以使用类型名称,如int, list, dict,…


如果有人想指定变量类型,我已经实现了一个包装器。

import functools
    
def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for i in range(len(args)):
            v = args[i]
            v_name = list(func.__annotations__.keys())[i]
            v_type = list(func.__annotations__.values())[i]
            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        v = result
        v_name = 'return'
        v_type = func.__annotations__['return']
        error_msg = 'Variable `' + str(v_name) + '` should be type ('
        error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
        if not isinstance(v, v_type):
                raise TypeError(error_msg)
        return result

    return check

使用它作为:

@type_check
def test(name : str) -> float:
    return 3.0

@type_check
def test2(name : str) -> str:
    return 3.0

>> test('asd')
>> 3.0

>> test(42)
>> TypeError: Variable `name` should be type (<class 'str'>) but instead is type (<class 'int'>)

>> test2('asd')
>> TypeError: Variable `return` should be type (<class 'str'>) but instead is type (<class 'float'>)

EDIT

如果没有声明任何参数的(或返回值的)类型,上面的代码就不能工作。下面的编辑可以提供帮助,另一方面,它只对kwarg有效,不检查args。

def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for name, value in kwargs.items():
            v = value
            v_name = name
            if name not in func.__annotations__:
                continue
                
            v_type = func.__annotations__[name]

            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ') '
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        if 'return' in func.__annotations__:
            v = result
            v_name = 'return'
            v_type = func.__annotations__['return']
            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                    raise TypeError(error_msg)
        return result

    return check


无论您是否指定类型提示,都将在运行时失败。

However, you can provide type hints for both function arguments and its return type. For example, def foo(bar: str) -> List[float] hints that bar is expected to be a string and the function returns a list of float values. This will result in a type check error when the method is invoked if the types don't match (before the use of the parameter in the function, or of the return type). This IMOHO is much more helpful in catching such errors vs an error about a missing field or method somewhere in the method call. I recommend reading the official Python documentation Typing - Support for type hints.

此外,如果使用类型提示,则可以使用静态类型检查器来验证代码的正确性。python中内置的一个这样的工具是myypy(官方文档)。关于静态类型检查的文章的这一部分很好地介绍了如何使用它。