gpt4 book ai didi

Python、 `let`、 `with`、局部作用域、调试打印和临时变量

转载 作者:行者123 更新时间:2023-12-04 13:36:46 25 4
gpt4 key购买 nike

我正在尝试重构一个针对 Python 3.6 和 pytest 的项目。测试套件包含许多调试语句,例如:

print('This is how something looks right now', random_thing.foo.bar.start,
random_thing.foo.bar.middle, random_thing.foo.bar.end)

这些陈述背后的想法是,如果将来某个测试开始失败,我们将有一些上下文来帮助我们追踪可能是什么问题。没有必要在该测试中测试当前的实际值,但是一旦事情开始失败,拥有该信息对于进一步调试很重要。

我想避免重复 random_thing.foo.bar.很多次。我可以将它分配给一个临时变量,但代码并不真正需要该变量之后可用。我并不是真的担心性能,但我非常喜欢保持代码“干净”——而“泄露”这些变量名会让我感到不舒服。在我熟悉的其他语言中有这样的功能,所以我想知道如何在 Python 中做到这一点。

我精通 C++,在那里我可能只是将该调试打印放入一个额外的范围:

{
const auto& bar = random_thing.foo.bar;
debug << "start: " << bar.start << ", middle: " << bar.middle << ", end: " << bar.end;
}

鉴于 there are no anonymous blocks in Python ,有没有一种“Pythonic”的方式来避免这种命名空间困惑?我并不是真的在寻找意见或人气竞赛,而是根据使用 Python 时间比我长的人对这些方法的看法进行评论,所以这里有一些我尝试过的事情:

1. 只需添加那个该死的变量和 del之后

嗯,我不喜欢重复做机器应该为我做的事情。

2. with声明和 contextlib.nullcontext

在 Python 中, with 没有新的作用域。声明,所以这留下了 opj本地变量可用:

>>> import os
>>> import os.path
>>> import contextlib
>>> with contextlib.nullcontext(os.path.join) as opj:
... print(type(opj))
...
<class 'function'>
>>> print(type(opj))
<class 'function'>

3. with声明和弗拉基米尔·雅科夫列夫的 let statement decorator

from contextlib import contextmanager
from inspect import currentframe, getouterframes

@contextmanager
def let(**bindings):
frame = getouterframes(currentframe(), 2)[-1][0] # 2 because first frame in `contextmanager` is the decorator
locals_ = frame.f_locals
original = {var: locals_.get(var) for var in bindings.keys()}
locals_.update(bindings)
yield
locals_.update(original)

代码对我来说看起来很棒:
>>> a = 3
>>> b = 4
>>> with let(a=33, b=44):
... print(a, b)
...
(33, 44)
>>> print(a, b)
(3, 4)

它没有 undef以前 undefined variable ,但很容易添加。以这种方式操作堆栈是一个理智的想法吗?我的 Python-fu 是有限的,所以我在将其视为 super 酷和 super 黑客之间左右为难。最终结果是“合理的 Pythonic”吗?

4. 包裹 print**kwargs
让我们使用 **kwargs :

def print_me(format, **kwargs):
print(format.format(**kwargs))

print_me('This is it: {bar.start} {bar.middle} {bar.end}', bar=random_thing.foo.bar)

这已经足够了,但是 f-strings可以包含实际表达式,例如:

foo = 10
print(f'{foo + 1}')

我想保留这个功能。我明白 str.format 由于传递用户定义的输入的安全含义,因此无法真正支持这一点。

最佳答案

您最好的选择是创建变量并将其留在那里,或者 del如果它真的让你那么烦恼,那么它之后。
with不是一个可行的方法。特别是,let事情是完全坏了以多种方式。

它错误的最重要的方式是修改 f_locals是未定义的行为 ,但由于其他错误,这在测试中不会立即显现。其他两个错误是 2控制与作者想法完全无关的东西,以及[-1]正在从错误的一端建立索引。这些错误导致代码访问“根”堆栈帧,即堆栈开始处的堆栈帧,而不是作者想要的帧。最后,它没有实际清除变量的处理——它只能将它们设置为 None .

如果您 test it with a function ,你会发现它不起作用:

from contextlib import contextmanager
from inspect import currentframe, getouterframes

@contextmanager
def let(**bindings):
frame = getouterframes(currentframe(), 2)[-1][0] # 2 because first frame in `contextmanager` is the decorator
locals_ = frame.f_locals
original = {var: locals_.get(var) for var in bindings.keys()}
locals_.update(bindings)
yield
locals_.update(original)

def f():
x = 1
with let(x=3):
print(x)

f()

print(x)

输出:
1
None
3在应该看到它的代码中不可见,还有一个额外的 None之后在错误的范围内徘徊。

没有什么好方法可以从 with 中获得您想要的功能陈述。默认 with范围规则不会做你想做的事,而且 Python 没有为上下文管理器提供一种方法来混淆调用它的代码的局部变量。

如果你真的讨厌那个变量并且你不想使用 del ,最接近好的选择可能是使用 Javascript 风格的立即调用 lambda:
(lambda x: print(f'start: {x.start}, middle: {x.middle}, end: {x.end}'))(
random_thing.foo.bar)

我认为这个选项比仅仅分配 x 差很多。正常的方式,但也许你的想法不同。

关于Python、 `let`、 `with`、局部作用域、调试打印和临时变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61371134/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com