跳转到主要内容

延迟允许对默认参数进行后期绑定

项目描述

license version fury downloads tests

包 Late 1.3.0b1

Python默认参数的延迟绑定

这是什么?

包 Late 提供了装饰器和函数,以解决Python中早期绑定默认参数值产生的问题。

以下内容对于Python新手来说并不直观,但这是每个人都会很快学会的事情

>>> def f(x=[]):
...     x.append(1)
...     return x
...
>>> f()
[1]
>>> f()
[1, 1]
>>> f()
[1, 1, 1]

Python中的行为是在每次函数调用时传递相同的初始化器值,因此使用可变值会产生上述结果。

解决上述问题的编码模式是使用 None 作为初始化器,并在函数代码的开始处检查参数值

>>> def f(x=None):
...     if x is None:
...         x = []
...     x.append(1)
...     return x
...
>>> f()
[1]
>>> f()
[1]
>>> f()
[1]

它很丑,但管用。

现在来看看另一个丑陋的部分。当使用类型注解时,上述函数必须以某种方式声明,以便类型检查器不会对使用 None 作为默认值提出异议

def f(x: list[Any] | None = None) -> list[Any]:

def f(x: Optional[list[Any]] = None) -> list[Any]:

上述声明的一个问题是调用 f(None) 会通过类型检查,这可能是人们不太希望的情况。

一个解决方案

包 Late 提供了一种使用一些装饰器魔法来解决上述丑陋问题的方法。以下是使用一些魔法后的代码看起来如何

from late import latebinding, __


@latebinding
def f(x: list[Any] = __([])) -> list[Any]:
    x.append(1)
    return x

assert f() == [1]
assert f() == [1]
assert f() == [1]

对于基本结构类型的构造函数,可以省略 __() 调用

@latebinding
def f(x: list[Any] = []) -> list[Any]:

与类一起工作

包 Late 也可以与类和 dataclass 一起使用。 @latebinding 装饰器必须是外部的

@latebinding
@dataclass
class C:
    x: list[Any] = __([])  # noqa

c = C()
assert c.x == []

d = C()
assert d.x == []
c.x = [1]
assert c.x == [1]
assert d.x == []

assert d.x is not c.x

与迭代器一起工作

包Late允许将迭代器作为默认参数值传递,并在每次函数调用时提供下一个值。此特性的实用性尚不清楚,但这是在讨论默认参数时出现的一个想法,因此包Late实现了它。

    def fib() -> Iterator[int]:
        x, y = 0, 1
        while True:
            yield x
            x, y = y, x + y


    @latebinding
    def f(x: int = __(fib())) -> int:
        return x

    assert f() == 0
    assert f() == 1
    assert f() == 1
    assert f() == 2
    assert f() == 3
    assert f() == 5

这是迭代器功能可能的应用之一。想象一个需要唯一ID的函数,如果没有提供,则会生成一个。没有包Late,声明将是:

def get_session(uniqueid: int | None = None) -> Session:
    if uniqueid is None:
        uniqueid = make_unique_id()

使用包Late,声明可以是:

def unique_id_generator() -> Iterator[int]:
    while True:
        yield make_unique_id()

@latebinding
def get_session(uniqueid: int = __(unique_id_generator())) -> Session:

与函数一起工作

包Late还允许函数的延迟绑定,因此上述示例可以使用函数而不是生成器来实现

@latebinding
def get_session(uniqueid: int = __(make_unique_id)) -> Session:

给定函数将在每次省略uniqueid参数时调用一次。

关于命名选择

包Late导出的名称在需要的地方是明确的,且不会干扰声明的视觉。特别是,选择__()是为了尽可能少地干扰函数声明的阅读(late()是它的另一个名称,而在Python代码中很少使用__)。

无论如何,包Late非常简单和小巧,您可以随意更改它,并将其作为代码的另一个部分使用,而不是将其作为库安装。

它是如何工作的?

对于不可变类型的值,__()将返回相同的值。对于所有其他类型,__()将使用特殊格式namedtuple(actual=value)包装值。在函数调用时,发生以下情况:

  • 如果参数名已在kwargs中,则不执行任何操作
  • 如果包装的值是迭代器,则使用next(actual)
  • 如果包装的值是函数,则使用actual()
  • 在其他所有情况下,使用copy.deepcopy(actual)

为了方便类型检查,__()被声明为根据参数具有期望的类型

def late(o: _T | Iterator[_V] | Callable[[], _R]) -> _T | _V | _R:

延迟绑定?

包Late使用的延迟绑定的定义是在运行时而不是在编译时解析。

为什么Python解释器不解决这个问题?

尽管长期以来已经承认并讨论了当前情况的丑陋和不便,但从未就解决方案的有用性、语义和语法达成一致。因此,现状保持不变。

您可以在Python Ideas网站上找到有关这些主题的最近讨论。

安装

$ pip install Late

许可证

包Late的许可方式请参阅LICENSE

现在...这是!

您可以使用表示"wrap"的汉字,而不是__来延迟绑定参数。

@latebinding
def f(x: list[Any] = ([])) -> list[Any]:

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。

源分发

Late-1.2.2.tar.gz (8.8 kB 查看哈希值)

上传时间: 源代码

构建发行版

Late-1.2.2-py3-none-any.whl (12.0 kB 查看哈希值)

上传时间: Python 3