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

def my_func(param1, param2):
    # stuff

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


当前回答

许多语言都有特定类型的变量,变量有一个值。Python没有变量;它有对象,你用名字来指代这些对象。

在其他语言中,当你说:

a = 1

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

在Python中,

a = 1

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

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

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

同样,无论何时定义函数

def funcname(param1, param2):

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

其他回答

在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

要有效地使用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

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

这个简单的函数:

def no_op(param1, param2):
    pass

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

然而,这个函数:

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

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

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

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