我正试图重构一个以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++,在C++中,我可能只是把那个调试打印放到一个额外的作用域中。
{
const auto& bar = random_thing.foo.bar;
debug << "start: " << bar.start << ", middle: " << bar.middle << ", end: " << bar.end;
}
鉴于 在Python中没有匿名块有没有一种 "Pythonic "的方法可以避免这种命名空间的混乱?我并不是真的在寻找意见或者人气竞赛,而是想根据比我做Python时间长的人对这些方法的看法来进行评测,所以这里有一些我尝试过的东西。
del
其后嗯,我不喜欢重复做一些机器应该为我做的事情。
with
声明和 contextlib.nullcontext
在Python中,没有新的作用域与 with
语句,所以这就剩下 opj
通过locals可以获得的变量。
>>> 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'>
with
语句和Vladimir Iakovlev的 let
饰家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
一个之前没有定义的变量,但这很容易添加。这样操作栈是一个正常的想法吗?我的Python功底有限,所以我在这两者之间徘徊,觉得这既是超级酷,又是超级黑客。最终的结果是 "合理的Pythonic "吗?
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)
这已经很好了,但是 弦 可以包含实际的表达式,例如。
foo = 10
print(f'{foo + 1}')
我想保留这个功能 我知道 str.format
因为传递用户定义的输入会带来安全隐患,所以不能真正支持。
你最好的选择是直接创建变量并把它留在那里,或者是 del
后,如果它真的那么困扰你。
with
是不可行的办法。尤其是,那 let
事情是 稀里哗啦 在多个方面。
最重要的错误方式就是 修改 f_locals
是未定义的行为但由于其他bug的存在,这在测试中并不是很明显。其他两个bug是 2
控制着与作者所想完全无关的东西,而这个 [-1]
从错误的一端开始索引。这些错误导致代码访问 "根 "堆栈框架,即堆栈开始的那个框架,而不是作者想要的框架。最后,它没有对实际清除变量的处理--它只能将变量设置为 None
.
如果你 试用,你会发现它不工作。
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并没有提供一种方法让上下文管理器来搞乱调用它的代码的locals。
如果你真的讨厌那个变量,而且你也不想使用 del
最接近一个好的选择可能是使用一个Javascript风格的立即调用lambda。
(lambda x: print(f'start: {x.start}, middle: {x.middle}, end: {x.end}'))(
random_thing.foo.bar)
我认为这个选项比单纯的赋值要差很多 x
正常的方式,但也许你有不同的想法。
下面就给大家讲讲它的乐趣。
#Fake object structure 👇
class Bar:
start="mystart"
middle= "mymiddle"
end="theend"
class Foo:
bar = Bar
class Rando:
foo = Foo
random_thing = Rando()
#Fake object structure 👆
def printme(tmpl, di_g={}, di_l={}, **kwargs):
""" use passed-in dictionaries, typically globals(), locals() then kwargs
last-one wins.
"""
di = di_g.copy()
di.update(**di_l)
di.update(**kwargs)
print(tmpl.format(**di))
bar = random_thing.foo.bar
printme('This is it: {bar.start} {bar.middle} {bar.end}', globals())
printme('This is it: {bar.start} {bar.middle} {bar.end}', bar=Bar)
def letsdoit():
"using locals and overriding bar"
bar = Bar()
bar.middle = "themiddle"
printme('This is it: {bar.start} {bar.middle} {bar.end} {fooplus}', globals(), locals(), fooplus=(10+1))
letsdoit()
This is it: mystart mymiddle theend
This is it: mystart mymiddle theend
This is it: mystart themiddle theend 11