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

2024-04-27 03:40:11 发布

您现在位置:Python中文网/ 问答频道 /正文

我正试图重构一个针对Python3.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的时间更长的人对这些方法的看法来进行评论,所以我尝试了以下几点:

一,。只需添加那个该死的变量,然后del它就可以了

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

二,with语句和^{}

在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'>

三,with声明和弗拉基米尔·亚科夫列夫的^{} 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以前没有定义过的变量,但是很容易添加。以这种方式操纵堆栈是明智的想法吗?我的Python fu是有限的,所以我在将其视为超级酷和超级黑客之间左右为难。最终的结果是“合理的Python”吗

四,。用**kwargs围绕print的包装器

让我们使用**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}')

我想保留这个功能。我理解^{}无法真正支持这一点,因为传递用户定义的输入会带来安全隐患


Tags: importmiddlefooiswithbarrandom语句
2条回答

您最好的选择是创建变量并将其保留在那里,或者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,那么最接近于一个好的选择可能是使用立即调用lambda的Javascript样式:

(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

相关问题 更多 >