在外部作用域中定义阴影名称有多糟糕?


207

我刚刚切换到Pycharm,对所有警告和提示它为我提供了改进我的代码感到非常高兴。除了我不了解的那一项:

This inspection detects shadowing names defined in outer scopes.

我知道从外部作用域访问变量是一种不好的做法,但是隐藏外部作用域有什么问题呢?

这是一个示例,其中Pycharm给我警告消息:

data = [4, 5, 6]

def print_data(data): # <-- Warning: "Shadows 'data' from outer scope
    print data

print_data(data)

1
我也搜索了字符串“此检查检测到...”,但在pycharm联机帮助中什么也没找到:jetbrains.com/pycharm/webhelp/getting-help.html
Framester,

1
要在PyCharm中关闭此消息,请执行以下操作:<Ctrl> + <Alt> + s(设置),Editor编辑)Inspections(检查),“ Shadowing names from external scope ”。取消选中。
ChaimG

Answers:


222

在上面的代码片段中没什么大不了的,但是请想象一个具有更多参数和更多代码行的函数。然后,您决定将data参数重命名为,yadda但错过了函数主体中使用该参数的位置之一...现在data是指全局变量,您开始有怪异的行为- NameError如果不这样做,您将拥有更明显的表现有一个全球的名字data

还要记住,在Python中,所有东西都是对象(包括模块,类和函数),因此对于函数,模块或类没有明显的命名空间。另一种情况是将函数导入foo到模块顶部,然后在函数主体中的某个位置使用它。然后,在函数中添加一个新参数,并将其命名为-bad lucky- foo

最后,内置函数和类型也位于相同的名称空间中,并且可以以相同的方式进行阴影处理。

如果您的功能短,命名合理且单元测试范围广,那么这些都不是什么大问题,但是好吧,有时您必须维护的代码不够完美,并且被警告可能存在的问题。


21
幸运的是,PyCharm(由OP使用)具有很好的重命名操作,可以在同一作用域中使用该变量的任何地方重命名该变量,这使得重命名错误的可能性降低。
wojtow

除了PyCharm的重命名操作外,我还希望对引用外部作用域的变量使用特殊的语法突出显示。这两个应该使这个耗时的阴影分辨率游戏无关紧要。
狮子座

旁注:您可以使用nonlocal关键字使外部得分参照(如在闭包中)明确。请注意,这与阴影不同,因为它明确地不会从外部阴影变量。
Felix D.

147

当前最受投票和接受的答案以及此处的大多数答案都没有抓住重点。

函数有多长,或描述性地命名变量(希望将潜在的名称冲突机会降到最低)都没有关系。

函数的局部变量或其参数恰好在全局范围内共享名称这一事实是完全不相关的。实际上,无论您多么仔细地选择本地变量名称,您的函数都无法预见到“ yadda将来我的好名字是否也将用作全局变量?”。解决方案?根本不用担心!正确的心态是将函数设计为仅使用签名中参数的输入,而无需使用全局范围内的(或将要)什么,然后阴影根本就不是问题。

换句话说,仅当函数需要使用相同名称的局部变量和全局变量时,阴影问题才重要。但是您首先应该避免这种设计。OP的代码实际上并没有这样的设计问题。仅仅是PyCharm不够聪明,它会发出警告以防万一。因此,只是为了使PyCharm满意,并使我们的代码整洁,请参见silyevsk的回答中引用的此解决方案以完全删除全局变量。

def print_data(data):
    print data

def main():
    data = [4, 5, 6]
    print_data(data)

main()

这是解决问题的正确方法,方法是修复/删除全局对象,而不调整当前的局部函数。


11
好吧,可以肯定的是,在一个理想的世界中,您可能会打错字,或者在更改参数时忘记搜索之一,但是会发生错误,这就是PyCharm在说的:“警告-从技术上讲,没有任何错误,但这可能很容易成为一个问题”
dwanderson

1
@dwanderson您提到的情况并不新鲜,在当前选择的答案中已对其进行了明确说明。但是,我要说明的一点是,我们应该避免全局变量,而不是避免对全局变量产生阴影。后者没有抓住重点。得到它?得到它了?
RayLuo

4
我完全同意函数应该尽可能“纯净”这一事实,但是您完全错过了两个要点:如果不是本地定义的,则没有办法限制Python在封闭范围内查找名称,以及所有内容(模块,函数,类等)是一个对象,并且与任何其他“变量”都位于同一命名空间中。在上面的代码段中,print_data是一个全局变量。考虑一下...
bruno desthuilliers

2
我之所以结束这个线程,是因为我使用的是在函数中定义的函数,以使外部函数更具可读性,而不会造成全局名称空间的混乱或使用单独的文件繁琐地处理。这里的示例不适用于非局部非全局变量被遮盖的一般情况。
micseydel

2
同意。这里的问题是Python作用域。对当前范围之外的对象的非显式访问会带来麻烦。谁会想要的!感到遗憾的是,否则Python是一种经过深思熟虑的语言(在模块命名中没有类似的歧义)。
CodeCabbie

24

在某些情况下,一个好的解决方法是将vars +代码移至另一个函数:

def print_data(data):
    print data

def main():
    data = [4, 5, 6]
    print_data(data)

main()

是。我认为一个好的想法可以通过重构来处理局部变量和全局变量。您的提示确实有助于消除这种原始思想的潜在风险
stanleyxu2005'2

5

这取决于功能的持续时间。功能越长,将来有人对其进行修改的机会就越多,data以为它意味着全局。实际上,这意味着本地,但是由于功能太长了,因此对于他们来说并不明显存在具有该名称的本地。

对于您的示例函数,我认为遮盖全局一点也不差。


5

做这个:

data = [4, 5, 6]

def print_data():
    global data
    print(data)

print_data()

3
data = [4, 5, 6] #your global variable

def print_data(data): # <-- Pass in a parameter called "data"
    print data  # <-- Note: You can access global variable inside your function, BUT for now, which is which? the parameter or the global variable? Confused, huh?

print_data(data)

47
我一个人不困惑。这显然是参数。

2
@delnan在这个琐碎的示例中,您可能不会感到困惑,但是,如果在附近定义的其他函数都使用了global data,那么在几百行代码中却全部使用了呢?
John Colanduoni

13
@HevyLight我不需要查看附近的其他功能。我仅查看此函数,并且可以看到它data函数中的本地名称,因此,我什至不必费心检查/记住是否存在相同名称的全局变量,更不用说它包含的内容了。

4
我认为这种推理是不正确的,仅仅是因为要使用全局变量,您需要在函数内部定义“全局数据”。否则,将无法访问全局。
CodyF

1
@CodyF- False如果您未定义,但只是尝试使用data它,它将在范围内查找直到找到一个范围,因此它找到全局范围datadata = [1, 2, 3]; def foo(): print(data); foo()
dwanderson '17

3

我喜欢在pycharm的右上角看到一个绿色的勾号。我为变量名加上下划线只是为了清除此警告,因此我可以将重点放在重要警告上。

data = [4, 5, 6]

def print_data(data_): 
    print(data_)

print_data(data)

2

看起来像是100%pytest代码模式

看到:

https://docs.pytest.org/en/latest/fixture.html#conftest-py-sharing-fixture-functions

我也有同样的问题,这就是为什么我找到这篇文章的原因;)

# ./tests/test_twitter1.py
import os
import pytest

from mylib import db
# ...

@pytest.fixture
def twitter():
    twitter_ = db.Twitter()
    twitter_._debug = True
    return twitter_

@pytest.mark.parametrize("query,expected", [
    ("BANCO PROVINCIAL", 8),
    ("name", 6),
    ("castlabs", 42),
])
def test_search(twitter: db.Twitter, query: str, expected: int):

    for query in queries:
        res = twitter.search(query)
        print(res)
        assert res

它会警告 This inspection detects shadowing names defined in outer scopes.

要解决此问题,只需将twitter灯具移入./tests/conftest.py

# ./tests/conftest.py
import pytest

from syntropy import db


@pytest.fixture
def twitter():
    twitter_ = db.Twitter()
    twitter_._debug = True
    return twitter_

然后移除twitter固定装置./tests/test_twitter2.py

# ./tests/test_twitter2.py
import os
import pytest

from mylib import db
# ...

@pytest.mark.parametrize("query,expected", [
    ("BANCO PROVINCIAL", 8),
    ("name", 6),
    ("castlabs", 42),
])
def test_search(twitter: db.Twitter, query: str, expected: int):

    for query in queries:
        res = twitter.search(query)
        print(res)
        assert res

这会让QA,Pycharm和所有人感到高兴

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.