延迟允许对默认参数进行后期绑定
项目描述
包 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 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 9a15d44d20e53b046a125e45561f99dde4e9126fa5d19871b608cfad34b5a9a4 |
|
MD5 | 0241cde8503183f14a3314386d7a5727 |
|
BLAKE2b-256 | 6133ed8ff374932b2f2a406370530d7223f7ac9de3dda23f4150aefaf0e7afd9 |
Late-1.2.2-py3-none-any.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 0a28987d056a9e0ec39564d429c6dc5804b71decd3d5eefdf2b1c0f778115e99 |
|
MD5 | 5f121d0fcf7f3ec78fc66ab18743bed1 |
|
BLAKE2b-256 | 251f209df37c2cf89ec9d3f41d5cf598477b1da74603d4efe427b18cecc23e81 |