Python函数如何处理您传入的参数类型?


305

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

def my_func(param1, param2):
    # stuff

但是,您实际上并未提供这些参数的类型。另外,如果我记得,Python是一种强类型语言,因此,似乎Python不应让您传递与函数创建者所期望的类型不同的参数。但是,Python如何知道函数的用户正在传递正确的类型?假定函数实际上使用了参数,那么如果类型错误,程序会死掉吗?您必须指定类型吗?


15
我认为在这个问题上可以接受的答案应该进行更新,以更符合Python 当前提供的功能。我认为这个答案是可行的。
code_dredd

Answers:


172

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

这与名称无关。Python中的名称没有“具有类型”:如果且在定义名称时,该名称指向一个对象,并且该对象确实具有一个类型(但实际上并不会强制该名称使用类型:名称是一个名称)。

Python中的名称可以很好地在不同时间引用不同的对象(就像在大多数编程语言中一样,尽管不是全部)-并且名称不受任何限制,因此,如果它曾经引用过X类型的对象,这样一来,便永远只能引用其他类型为X的对象。名称的约束不属于“强类型”概念的一部分,尽管一些静态类型的爱好者(名称确实受到约束,并且在静态AKA中会编译-时间,时尚也是如此)请勿以这种方式滥用该术语。


71
所以看起来强类型并没有那么强,在这种情况下,它比静态类型要弱。恕我直言,对名称/变量/引用的编译时类型约束实际上非常重要,因此我大胆地声称python不如静态类型好在这方面。如果我错了,请纠正我。

19
@liang这是一种意见,所以您不能是非。当然,这也是我的观点,并且我尝试了多种语言。我不能使用我的IDE来找出参数的类型(以及成员),这是python的主要缺点。如果这种缺点比鸭子的优点更重要,则取决于您要询问的人。
Maarten Bodewes,2013年

6
但这并不能回答任何问题:“但是,Python如何知道该函数的用户正在传递正确的类型?如果该函数使用了错误的类型,并且假设该函数实际上使用了参数,那么程序会死掉吗?您必须指定类型吗?” 或
-qPCR4vir

4
@ qPCR4vir,任何对象都可以作为参数传递。错误(一个例外,该计划不会“死”,如果它的编码抓住它,见try/ except)时才会发生,如果操作尝试的对象不支持。在Python 3.5中,您现在可以选择“指定类型”的参数,但是如果违反了规范,就不会发生错误。键入符号仅用于帮助执行分析等工作的单独工具,不会改变Python本身的行为。
Alex Martelli'3

2
@AlexMartelli。谢谢!对我来说,这是正确的答案:“错误(一个例外,如果对它进行编码以捕获该错误,该程序将不会“死亡”,请参阅try / except)..”
qPCR4vir

753

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

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

但是,自2010年(首次提出该问题)以来,发生了一件有趣的事情,即PEP 3107的实现(在Python 3中实现)。现在,您实际上可以像这样指定参数的类型和函数的返回类型的类型:

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

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

因此,这里暗示的l是一个整数列表,我们可以很轻松地看到它,但是对于更复杂的函数,该列表应包含的内容可能会有些混乱。我们还希望默认值为index0。要解决此问题,您可以选择这样写pick

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

请注意,我们现在在字符串中添加了类型为的字符串l,这在语法上是允许的,但是对于以编程方式进行解析不是很好(我们将在后面介绍)。

重要的是要注意,TypeError如果将float传递给Python,Python不会引发index,这是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引入了用于类型提示的标准模块。

这些类型提示来自类型检查器mypyGitHub),它现在符合PEP 484

键入模块随附了非常全面的类型提示集合,包括:

  • ListTupleSetMap-为listtuplesetmap分别。
  • Iterable -对发电机有用。
  • Any -什么时候可以。
  • Union-相对于,它可以是指定类型集中的任何内容Any
  • Optional- 可能为“无”时。的简写Union[T, None]
  • TypeVar -与泛型一起使用。
  • Callable -主要用于函数,但可以用于其他可调用项。

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

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

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]

如您所见,这会花费很多额外的行(确切的行数取决于您想要的显式程度以及格式化文档字符串的方式)。但是,现在您应该清楚PEP 3107如何提供在许多(所有方式)方面都优越的替代方案。如我们所见,与PEP 484结合使用时尤其如此,如我们所见,PEP 484提供了一个标准模块,该模块定义了这些类型提示/注释的语法,该语法可以以明确,准确而灵活的方式使用,从而强大的组合。

我个人认为,这是Python上最伟大的功能之一。我等不及人们开始利用它的力量了。抱歉,答案很长,但是当我兴奋时就会发生这种情况。


此处可以找到大量使用类型提示的Python代码示例。


2
@rickfoosusa:我怀疑您没有运行添加了该功能的Python 3。
erb 2015年

26
等一下!如果定义参数和返回类型没有引发a TypeError,那么pick(l: list, index: int) -> int像单行定义那样使用的意义何在?还是我弄错了,我不知道。
Erdin Eray

24
@Eray Erdin:这是一个普遍的误会,根本不是一个坏问题。它可用于文档目的,可通过使用静态分析(就像我在答案中提到的mypy一样)帮助IDE更好地完成自动填充并在运行时之前查找错误。希望运行时可以利用这些信息并实际加快程序速度,但这可能需要很长时间才能实现。您也许还可以创建一个为您引发TypeError的装饰器(信息存储在__annotations__函数对象的属性中)。
erb

2
@ErdinEray我应该补充一点,抛出TypeErrors是一个坏主意(调试不会变得很有趣,无论预期引发的异常情况如何)。但是不要担心,我的答案中描述的新功能的优点提供了一种更好的方法:不要在运行时依赖任何检查,使用mypy在运行时之前进行所有操作,或者使用可以为您执行静态分析的编辑器(例如PyCharm) 。
erb

2
@Tony:当您返回两个或更多对象时,您实际上会返回一个元组,因此您应该使用元组类型注释,即def f(a) -> Tuple[int, int]:
erb

14

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

所以这个简单的功能:

def no_op(param1, param2):
    pass

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

但是,此功能:

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

如果param1并且param2都不具有名为的可调用属性,则会在运行时失败quack


+1:属性和方法不是静态确定的。该“适当类型”或“错误类型”如何确定的概念取决于该类型在函数中是否正常工作。
美国洛特

11

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

用其他语言,当您说:

a = 1

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

在Python中,

a = 1

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

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

该函数type用对象调用1; 由于每个对象都知道其类型,因此很容易type找出所述类型并将其返回。

同样,无论何时定义函数

def funcname(param1, param2):

该函数接收两个对象,并为其命名为param1param2,无论它们的类型如何。如果要确保接收到的对象属于特定类型,请对函数进行编码,就好像它们属于所需的类型一样,并捕获不是的异常。引发的异常通常是TypeError(您使用了无效的操作)和AttributeError(您试图访问不存在的成员(方法也是成员))。


8

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

大多数Python代码都属于所谓的 “ Duck Typing”鸭子输入) -例如,您read在对象上寻找一种方法-不在乎对象是磁盘上的文件还是套接字上的文件,您只想读N字节。


21
Python 强类型的。它也是动态输入的。
Daniel Newby

1
但这并不能回答任何问题:“但是,Python如何知道该函数的用户正在传递正确的类型?如果该函数使用了错误的类型,并且假设该函数实际上使用了参数,那么程序会死掉吗?您必须指定类型吗?” 或
-qPCR4vir

6

正如Alex Martelli所说

正常的,Python式的首选解决方案几乎总是“鸭式输入”:尝试使用参数,就好像它是某个所需的类型一样,在try / except语句中进行操作,以捕获如果该参数实际上不是所有可能出现的异常该类型(或其他任何可以模仿它的类型;-),然后在except子句中尝试其他操作(使用参数“好像”它是其他类型)。

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


5

Python不在乎您将其传递给什么函数。当您调用时my_func(a,b),param1和param2变量将保存a和b的值。Python不知道您使用正确的类型来调用该函数,因此希望程序员来照顾好它。如果将使用不同类型的参数调用函数,则可以使用try / except块包装代码以访问它们,并以所需的任何方式评估参数。


11
Python没有变量,就像其他语言一样,变量具有类型和值。它具有指向对象的名称,这些对象知道其类型。
tzot

2

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

通常,由代码决定是否传递正确类型的对象-没有编译器可以提前实施此类型。


2

在这一页上,值得一提的是鸭子输入法,这是一个臭名昭著的例外。

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提示我们程序遇到意外类型时应引发哪个异常。


1

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

示例:foo将添加所有可以__add__编辑的内容;)不必担心其类型。因此,为了避免失败,您应该仅提供支持加法的那些东西。

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

1

我没有在其他答案中看到此内容,因此将其添加到锅中。

正如其他人所说的,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 ++这样的静态语言来说将是一场噩梦。


1

为了有效地使用键入模块(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.

但是,你仍然可以使用类的名称,如intlistdict,...


1

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

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'>)

编辑

如果未声明任何参数的类型(或返回值的类型),则以上代码将不起作用。另一方面,以下编辑可以提供帮助,它仅适用于kwargs,不检查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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.