为什么Python lint要我为相同目的使用不同的局部变量名,而不是全局变量名?

12 投票
3 回答
9484 浏览
提问于 2025-04-18 15:36

给定这样的Python代码:

def func():
    for i in range(10):
        pass

for i in range(10):
    pass  

pylint会发出警告

Redefining name 'i' from outer scope 

那么,写上面的代码的“Pythonic”方式是什么呢?是不是应该在本地使用不同的变量,比如说j

但是为什么呢?因为这两个变量在这两种情况下的意思是完全一样的(i都是用来表示索引)。假设我把所有本地的索引都改成j,然后后来我发现我想在全局范围内用j作为第二个索引。那我还得再改一次吗?

我不能关闭lint的警告,我也不想有这些警告,我想写得更符合Python的风格,但在像上面这样简单的情况下,我又想对同样的东西使用相同的名字。这难道不可能吗?

3 个回答

3

这样做是为了避免你在以为自己在用一个东西的时候,实际上却在用另一个东西。Lint工具的目的是让你的代码更健壮。通过给所有变量起不同的名字,你可以确保不会出现这种冲突。

这在解释型语言中尤其重要,因为错误不会在“编译时”被检查。我曾经遇到过一个问题,第二次调用一个函数时出错,因为我重命名了一个函数,却没有意识到在某些情况下,有一个变量的名字和我的函数是一样的。因此,当我试图调用我的函数时,解释器却试图“调用”我新创建的变量,这根本行不通,真是搞笑。

这个Lint政策可以避免这种问题。

下面是一个示例代码(这是一个计算π的程序):

from fractions import Fraction


def order(x):
    r, old_r, n, old_n = 2, 1, 1, 0
    while (x>=r):
        r, old_r, n, old_n = r*r, r, 2*n, n
    return order(x >> old_n) + old_n if old_n > 0 else 0


def term(m, n, i):
    return Fraction(4 * m, n**(2*i+1) * (2*i+1))


def terms_generator(exp_prec):
    ws = [ [term(parm[1], parm[2], 0), 0] + list(parm)
          for parm in ((1, 44, 57),
                       (1, 7, 239),
                       (-1, 12, 682),
                       (1, 24, 12943))]
    digits = 0
    while digits<exp_prec:
        curws = max(ws, key=lambda col: col[0])
        digits = int(0.30103 *
                     (order(curws[0].denominator))
                      - order(curws[0].numerator))
        yield curws[2] * curws[0], digits
        curws[2] = -curws[2]                
        curws[1] += 1
        curws[0] = term(curws[3], curws[4], curws[1])

expected_precision = 1000
pi = 0
for term, dgts in terms_generator(expected_precision):
    pi += term

print("{} digits".format(dgts))
print("pi = 3.{}".format(int((pi-3)*10**expected_precision)))

在这个例子中,我从一个生成器初始化了一个变量,而这个生成器使用了另一个函数,这个函数的名字和我初始化的变量冲突。其实,这不是一个很好的例子,因为这里两个名字都是全局的,但从结构上看,这种冲突并不明显。

我的意思是,即使你知道怎么编程,你也会犯错,而这些做法可以帮助减少那些错误被隐藏的风险。

31

你可以通过不使用全局变量来避免全局变量冲突:

def func():
    for i in range(10):
        pass

def _init_func():
    for i in range(10):
        pass  

_init_func()

任何需要在模块初始化时运行的代码都可以放在一个单独的函数里。这样,在模块初始化时唯一需要执行的代码就是:def 语句、class 语句和一个函数调用。

同样,如果你的代码不是为了被 import 引入,而是一个要直接运行的脚本,

def func():
    for i in range(10):
        pass

def main():
    for i in range(10):
        pass  

if __name__=="__main__":
    main()
1

这个检查工具会发出警告,因为如果循环至少执行了一次,i 的值会在循环结束后继续存在。这意味着如果你在没有重新初始化它的情况下使用它,它仍然会保留在最后一次循环中所得到的值。

不过你使用它的方式是没问题的,因为i会在每次使用前重新初始化。

一个有用的做法是把外部作用域中的所有值都命名为大写字母(ALL_CAPS)。这样就不会出错了。

这个回答被认为是错误的,具体情况请查看:https://stackoverflow.com/a/25072186

撰写回答