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

问题描述 投票:3回答:1

我正试图重构一个以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时间长的人对这些方法的看法来进行评测,所以这里有一些我尝试过的东西。

1. 只要添加那个该死的变量和 del 其后

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

2. 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'>

3. 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 "吗?

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)

这已经很好了,但是 可以包含实际的表达式,例如。

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

我想保留这个功能 我知道 str.format 因为传递用户定义的输入会带来安全隐患,所以不能真正支持。

python debugging with-statement let contextmanager
1个回答
1
投票

你最好的选择是直接创建变量并把它留在那里,或者是 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 正常的方式,但也许你有不同的想法。


0
投票

下面就给大家讲讲它的乐趣。


#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
© www.soinside.com 2019 - 2024. All rights reserved.