PEP 671 – 后期绑定函数参数默认值的语法

猫勺猫勺 03-15 150 阅读 0 评论

抽象

函数参数可以具有默认值,这些默认值在 函数定义并保存。该提案引入了一种新的形式 参数默认值,由要在函数中计算的表达式定义 通话时间。

赋予动机

可选的函数参数,如果省略,通常具有某种逻辑 默认值。当此值依赖于其他参数或需要 重新评估每个函数调用,目前没有干净的方法来声明 这在函数标头中。

目前法律上的习语包括:

# Very common: Use None and replace it in the functiondef bisect_right(a, x, lo=0, hi=None, *, key=None):
    if hi is None:
        hi = len(a)# Also well known: Use a unique custom sentinel object_USE_GLOBAL_DEFAULT = object()def connect(timeout=_USE_GLOBAL_DEFAULT):
    if timeout is _USE_GLOBAL_DEFAULT:
        timeout = default_timeout# Unusual: Accept star-args and then validatedef add_item(item, *optional_target):
    if not optional_target:
        target = []
    else:
        target = optional_target[0]

在每个表单中,都无法显示真正的默认值。每 一个也有其他问题;仅当 None 不有效时,using 才有效 本身是一个合理的函数参数,自定义哨兵需要一个全局 不断;使用 star-args 意味着可以有多个参数 鉴于。help(function)None

规范

可以使用新表示法定义函数默认参数:=>

def bisect_right(a, x, lo=0, hi=>len(a), *, key=None):def connect(timeout=>default_timeout):def add_item(item, target=>[]):def format_time(fmt, time_t=>time.time()):

表达式以源代码形式保存,以便检查, 用于评估它的字节码被附加到函数的主体之前。

值得注意的是,表达式是在函数的运行时范围内计算的,而不是 定义函数的作用域(早期绑定默认值也是如此)。这 允许表达式引用其他参数。

多个后期绑定参数从左到右计算,可以引用 转换为先前定义的值。顺序由函数定义,而不管 关键字参数的传递顺序。

def prevref(word=“foo”, a=>len(word), b=>a//2): # 有效 def selfref(spam=>spam): # UnboundLocalError def spaminate(sausage=>eggs + 1, eggs=>sausage - 1): # 混淆,不要这样做 def frob(n=>len(items), items=[]): # 见下文

评估顺序为从左到右;但是,实现可以选择这样做 在两个单独的传递中,首先针对所有传递的参数和早期绑定默认值, 然后是后期绑定默认值的第二次传递。否则,所有参数都将是 严格从左到右分配。

拒绝的拼写选择

虽然本文档指定了单一语法,但 拼写也同样合理。考虑了以下拼写:name=>expression

def bisect(a, hi=>len(a)):
def bisect(a, hi:=len(a)):
def bisect(a, hi?=len(a)):
def bisect(a, @hi=len(a)):

由于默认参数的行为基本相同,无论它们是早的还是晚的 bound,则选择的语法故意与现有的 early-bind 语法。hi=>len(a)

拒绝语法的一个原因是它带有注释的行为。 注释位于默认值之前,因此在所有语法选项中,它必须是 明确(对人类和解析者)这是否是一个注释, 默认值,或两者兼而有之。备用语法存在以下风险 被误解为省略了注释 错误,因此可能会掩盖错误。所选语法不会 有这个问题。:=target:=exprtarget:int=exprtarget=>expr

如何教这个

应始终首先教授早期绑定的默认参数,因为它们是 更简单、更有效的评估论点的方法。建立在他们的基础上,迟到了 绑定参数大致等同于函数顶部的代码:

def add_item(item, target=>[]):# Equivalent pseudocode:def add_item(item, target=<OPTIONAL>):
    if target was omitted: target = []

一个简单的经验法则是:当函数计算“target=expression”时 ,并在调用函数时计算“target=>expression”。 无论哪种方式,如果在调用时提供了参数,则忽略默认值。 虽然这并不能完全解释所有的微妙之处,但足以 涵盖此处的重要区别(以及它们相似的事实)。

与其他提案的互动

PEP 661 试图解决与此相同的问题之一。它寻求 改进了默认参数中哨兵值的文档,其中 该提案旨在消除许多常见情况下对哨兵的需求。PEP 661 能够改进任意复杂函数的文档(它 引用作为其主要动机,它有两个 必须同时指定两者或两者都不指定的参数);另一方面,许多 如果真正的默认值可以,则常见情况将不再需要哨兵 由函数定义。此外,专用的哨兵对象可以是 用作字典查找键,其中 PEP 671 不适用。traceback.print_exception

有时有人提议采用延期评价的通用制度(不 与特定于注释的 PEP 563 和 PEP 649 混淆)。 虽然从表面上看,后期绑定参数默认值似乎是 性质相似,它们实际上是不相关且正交的想法,两者都可以 对语言有价值。接受或拒绝该提案将 不影响推迟的评价建议的可行性,反之亦然。(一个 广义延迟评估和参数默认值之间的主要区别在于 该参数默认值将始终且仅在函数开始时计算 执行,而延迟表达式只有在引用时才能实现。

实施细节

以下内容与参考实现有关,不一定 规范的一部分。

参数默认值(位置或关键字)具有两个值,就像已经一样 保留,以及一条额外的信息。对于位置参数, extras 存储在 的元组中,并且仅对于关键字, 中的字典。如果此属性是 ,则为 等同于每个参数都默认。__defaults_extra____kwdefaults_extra__NoneNone

对于每个具有后期绑定默认值的参数,特殊值将存储为值占位符,以及相应的额外信息 需要查询。如果是 ,则默认值确实是 ;否则,它是一个描述性字符串,真正的值是 在函数开始时计算。EllipsisNoneEllipsis

当省略具有后期绑定默认值的参数时,该函数将开始 参数为 unbound。该函数首先测试每个参数 使用新的操作码 QUERY_FAST/QUERY_DEREF 的后期绑定默认值,如果 unbound,计算原始表达式。此操作码(仅适用于 fast locals and closure variables) 将 True 推送到堆栈上,如果给定 local 有一个值,如果不是,则为 False - 这意味着如果LOAD_FAST,它会推送 False 或 LOAD_DEREF 将引发 UnboundLocalError,如果成功,则引发 True。

允许无序变量引用,只要引用对象具有 参数或早期绑定默认值中的值。

成本

当未使用后期绑定参数默认值时,应将以下成本 所有发生的:

函数对象需要两个额外的指针,这将是 NULL

编译代码和构造函数具有额外的标志检查

用作默认值将需要运行时验证 以查看是否存在后期绑定默认值。Ellipsis

这些成本预计是最低的(在 64 位 Linux 上,这会增加所有成本 函数对象从 152 字节到 168 字节),在以下情况下几乎没有运行时成本 不使用后期绑定默认值。

向后不兼容

如果不使用后期绑定默认值,则行为应相同。关心 如果找到省略号,则应采用省略号,因为它可能不代表自己,但 除此之外,工具应该看到现有代码不变。

版权

本文档被置于公共领域或位于 CC0-1.0-通用许可证,以更宽松的许可证为准。

The End 微信扫一扫

文章声明:以上内容(如有图片或视频在内)除非注明,否则均为腾龙猫勺儿原创文章,转载或复制请以超链接形式并注明出处。

本文作者:猫勺本文链接:https://www.jo6.cn/post/62.html

上一篇 下一篇

相关阅读

发表评论

访客 访客
快捷回复: 表情:
评论列表 (暂无评论,150人围观)

还没有评论,来说两句吧...

取消
微信二维码
微信二维码
支付宝二维码