当前位置:  开发笔记 > 编程语言 > 正文

Python中的函数参数类型

如何解决《Python中的函数参数类型》经验,为你挑选了6个好方法。

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

def my_func(param1, param2):
    # stuff

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



1> erb..:

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

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

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

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

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

所以这里隐含的l是一个整数列表,我们可以毫不费力地看到它们,但是对于更复杂的函数,列表应包含的内容可能会有些混乱.我们还希望默认值为index0.要解决这个问题,您可以选择这样写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不仅提高了代码的可读性,还提供了几个适合的用例,您可以在这里阅读.


类型注释在Python 3.5中得到了更多关注,引入了PEP 484,它引入了类型提示的标准模块.

此语法来自正在开发的可选静态类型检查器工具mypy(GitHub)(与PEP 484兼容).

打字模块附带了一个非常全面的类型提示集合,包括:

List,Tuple,Set,Map-为list,tuple,setmap分别.

Iterable - 对发电机很有用.

Any - 什么时候可以.

Union- 当它可以是指定的一组类型中的任何东西时,而不是Any.

Optional- 当它可能是无.简写Union[T, None].

TypeVar - 与泛型一起使用.

Callable - 主要用于功能,但可用于其他callables.

此列表代表最常见的类型提示,但它远非详尽无遗.可以在打字模块的文档中找到完整的列表.

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

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代码示例.


@Eray Erdin:这是一个常见的误解,而不是一个糟糕的问题.它可以用于文档目的,通过使用静态分析帮助IDE更好地自动完成并在运行时发现错误(就像我在答案中提到的那样).希望运行时可以利用这些信息并实际加速程序,但这可能需要很长时间才能实现.你*可能*也能够创建一个为你抛出TypeErrors的装饰器(信息存储在函数对象的`__annotations__`属性中).
等一下!如果定义参数和返回类型没有引发`TypeError`,那么使用`pick(l:list,index:int) - > int`就像单线定义那样有什么意义呢?或者我弄错了,我不知道.
这绝对应该是公认的答案.
@rickfoosusa:我怀疑你没有运行添加了该功能的Python 3.

2> Alex Martell..:

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

这与名字无关.Python中的名称不具有"类型":如果名称定义,名称引用对象,并且对象确实具有类型(但实际上不强制名称上的类型:a名字是一个名字).

Python中的名称可以很好地在不同的时间引用不同的对象(如在大多数编程语言中,但不是全部) - 并且对名称没有约束,如果它曾经引用类型为X的对象,然后它永远受限于仅引用X类型的其他对象.名称上的约束不是"强类型"概念的一部分,尽管有些静态类型的爱好者(名称确实受到限制,并且在静态的AKA编译中)时间,时尚,也是这样滥用这个词.


因此看起来强打字不是那么强,在这种特殊情况下,它比静态输入弱.IMHO,编译时对名称/变量/引用的输入约束实际上非常重要,因此我大胆地声称python不如静态输入在这方面.如果我错了,请纠正我.
@liang这是一个意见,所以你不能对错.这当然也是我的看法,我尝试了很多语言.事实上,我不能使用我的IDE来找出参数的类型(以及成员)是python的一个主要缺点.如果这个缺点比鸭子打字的优势更重要取决于你问的人.
但是这并没有回答任何问题:"但是,Python如何知道函数的用户正在传递正确的类型?假如函数实际使用参数,程序是否会因为错误的类型而死?你必须指定类型吗?" 要么..
@ qPCR4vir,任何对象都可以作为参数传递.错误(例外,程序不会"死",如果它被编码为捕获它,请参阅`try` /`except`)将在尝试对象不支持的操作时发生.在Python 3.5中,您现在可以选择"指定类型"参数,但如果规范被违反,则本身不会发生错误; 打字表示法只是为了帮助分离执行分析的工具等,它不会改变Python本身的行为.
@AlexMartelli。谢谢!对我来说,这是正确的答案:“错误(一个例外,如果对它进行编码以捕获该错误,该程序将不会“死亡”,请参阅try / except)。”
@MaartenBodewes大多数语言中的静态类型给您一种错误的安全感:“我让编译器感到满意,因此它必须正确……”,然后您花费了整个周末来修复空指针异常。使用足够强大的类型系统来捕获人们实际犯下的实际错误(同样,对于Haskell基本上是正确的,对于Rust和Swift基本上都适用,而对于C ++,Java,Go等则完全不适用),静态打字可以使代码更安全。在类型系统中,程序的一半是动态强制转换,这只是在欺骗您没有意识到您的代码有多不安全。

3> TM...:

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

所以这个简单的功能:

def no_op(param1, param2):
    pass

......无论传递两个参数,都不会失败.

但是,这个功能:

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

...如果param1并且param2都没有命名可调用属性,则会在运行时失败quack.



4> tzot..:

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

在其他语言中,当你说:

a = 1

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

在Python中,

a = 1

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

>>> type(1)

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

同样,无论何时定义函数

def funcname(param1, param2):

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



5> Mark Rushako..:

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

大多数Python代码属于所谓的"Duck Typing" - 例如,你read在对象上寻找一个方法- 你不关心对象是磁盘上的文件还是套接字,你只想读N来自它的字节.


Python _is_强类型.它也是动态类型的.

6> Nick Presta..:

正如Alex Martelli解释的那样,

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

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

推荐阅读
360691894_8a5c48
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有