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

2025-03-10 08:52:00
admin
原创
77
摘要:问题描述:如果我没记错的话,在 Python 中创建一个函数的工作原理如下:def my_func(param1, param2): # stuff 但是,您实际上并没有给出这些参数的类型。另外,如果我没记错的话,Python 是一种强类型语言,因此,Python 似乎不应该让您传入与函数创建者预期类...

问题描述:

如果我没记错的话,在 Python 中创建一个函数的工作原理如下:

def my_func(param1, param2):
    # stuff

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


解决方案 1:

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

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

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

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

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

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

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

请注意,我们现在将字符串作为 的类型l,这在语法上是允许的,但不适合以编程方式进行解析(我们稍后会回顾)。

需要注意的是,TypeError如果你将浮点数传递给,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 中得到了更多的关注,它引入了typing类型提示的标准模块。

这些类型提示来自类型检查器mypy ( GitHub ),它现在符合PEP 484标准。

typing模块附带了相当全面的类型提示集合,包括:

  • List,,,——分别表示,,和。TupleSet`Dictlisttuplesetdict`

  • Iterable- 对发电机有用。

  • Any- 当它可能为任何东西时。

  • Union- 当它可以是指定类型集合内的任何东西时,而不是Any

  • Optional- 可能None。 的简写Union[T, None]

  • TypeVar- 与泛型一起使用。

  • Callable- 主要用于函数,但也可以用于其他可调用函数。

这些是最常见的类型提示。完整列表可在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]

如您所见,这需要多写几行代码(具体数量取决于您想要的明确程度以及文档字符串的格式)。但现在您应该清楚PEP 3107如何提供一种在许多(所有?)方面都更胜一筹的替代方案。与PEP 484结合使用尤其如此,正如我们所见,PEP 484 提供了一个标准模块,为这些类型提示/注释定义了一种语法,可以以明确、精确且灵活的方式来使用,从而形成一个强大的组合。

在我看来,这是 Python 有史以来最伟大的功能之一。我迫不及待地想让人们开始利用它的强大功能。抱歉回答太长了,但这就是我兴奋时发生的事情。


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

解决方案 2:

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

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

Python 中的名称可以在不同时间完美地引用不同的对象(大多数编程语言都是如此,但不是全部)——并且对名称没有任何约束,例如,如果它曾经引用过类型 X 的对象,那么它就永远只能引用类型 X 的其他对象。名称约束不属于“强类型”概念的一部分,但一些静态类型(名称确实受到约束,并且也是以静态的,也就是编译时的方式)的爱好者确实以这种方式误用该术语。

解决方案 3:

您没有指定类型。如果该方法尝试访问传入参数上未定义的属性,则该方法只会在运行时失败。

所以这个简单的函数:

def no_op(param1, param2):
    pass

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

但是,此功能:

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

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

解决方案 4:

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

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,而不检查参数。

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

解决方案 5:

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

在其他语言中,当你说:

a = 1

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

在 Python 中,

a = 1

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

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

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

同样,每当你定义一个函数时

def funcname(param1, param2):

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

解决方案 6:

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

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

解决方案 7:

正如Alex Martelli 解释的那样,

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

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

解决方案 8:

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

解决方案 9:

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

但是,您可以为函数参数及其返回类型提供类型提示。例如,def foo(bar: str) -> List[float]提示 bar 应为字符串,而函数返回浮点值列表。如果类型不匹配(在函数中使用参数之前,或返回类型之前),则在调用方法时会导致类型检查错误。在我看来,这在捕获此类错误方面比在方法调用中某处缺少字段或方法的错误更有帮助。我建议阅读官方 Python 文档《类型提示支持》。

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

解决方案 10:

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

一般来说,由您的代码来确保您传递的对象是正确类型的 - 没有编译器可以提前强制执行这一点。

解决方案 11:

本页中有一个臭名昭著的鸭子类型例外值得一提。

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

解决方案 12:

在 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

解决方案 13:

我没有看到其他答案中提到这一点,所以我将其添加到锅中。

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

解决方案 14:

为了有效使用 typing 模块(Python 3.5 中的新功能),请包含所有(*)。

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...

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2560  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1552  
  IPD(Integrated Product Development)流程作为一种先进的产品开发管理模式,在众多企业中得到了广泛应用。其中,技术评审与决策评审是IPD流程中至关重要的环节,它们既有明显的区别,又存在紧密的协同关系。深入理解这两者的区别与协同,对于企业有效实施IPD流程,提升产品开发效率与质量具有重要意义...
IPD管理流程   1  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、ClickUp、Freshdesk、GanttPRO、Planview、Smartsheet、Asana、Nifty、HubPlanner、Teamwork。在当今快速变化的商业环境中,项目管理软件已成为企业提升效率、优化资源分配和确保项目按时交付的关键工具。然而...
项目管理系统   2  
  建设工程项目质量关乎社会公众的生命财产安全,也影响着企业的声誉和可持续发展。高质量的建设工程不仅能为使用者提供舒适、安全的环境,还能提升城市形象,推动经济的健康发展。在实际的项目操作中,诸多因素会对工程质量产生影响,从规划设计到施工建设,再到后期的验收维护,每一个环节都至关重要。因此,探寻并运用有效的方法来提升建设工程...
工程项目管理制度   3  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用