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

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

我刚刚切换到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)

I just switched to Pycharm and I am very happy about all the warnings and hints it provides me to improve my code. Except for this one which I don’t understand:

This inspection detects shadowing names defined in outer scopes.

I know it is bad practice to access variable from the outer scope but what is the problem with shadowing the outer scope?

Here is one example, where Pycharm gives me the warning message:

data = [4, 5, 6]

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

print_data(data)

回答 0

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

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

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

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

No big deal in your above snippet, but imagine a function with a few more arguments and quite a few more lines of code. Then you decide to rename your data argument as yadda but miss one of the places it is used in the function’s body… Now data refers to the global, and you start having weird behaviour – where you would have a much more obvious NameError if you didn’t have a global name data.

Also remember that in Python everything is an object (including modules, classes and functions) so there’s no distinct namespaces for functions, modules or classes. Another scenario is that you import function foo at the top of your module, and use it somewhere in your function body. Then you add a new argument to your function and named it – bad luck – foo.

Finally, built-in functions and types also live in the same namespace and can be shadowed the same way.

None of this is much of a problem if you have short functions, good naming and a decent unittest coverage, but well, sometimes you have to maintain less than perfect code and being warned about such possible issues might help.


回答 1

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

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

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

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

def print_data(data):
    print data

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

main()

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

The currently most up-voted and accepted answer and most answers here miss the point.

It doesn’t matter how long your function is, or how you name your variable descriptively (to hopefully minimize the chance of potential name collision).

The fact that your function’s local variable or its parameter happens to share a name in the global scope is completely irrelevant. And in fact, no matter how carefully you choose you local variable name, your function can never foresee “whether my cool name yadda will also be used as a global variable in future?”. The solution? Simply don’t worry about that! The correct mindset is to design your function to consume input from and only from its parameters in signature, that way you don’t need to care what is (or will be) in global scope, and then shadowing becomes not an issue at all.

In other words, shadowing problem only matters when your function need to use the same name local variable AND the global variable. But you should avoid such design in the first place. The OP’s code does NOT really have such design problem. It is just that PyCharm is not smart enough and it gives out a warning just in case. So, just to make PyCharm happy, and also make our code clean, see this solution quoting from silyevsk ‘s answer to remove the global variable completely.

def print_data(data):
    print data

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

main()

This is the proper way to “solve” this problem, by fixing/removing your global thing, not adjusting your current local function.


回答 2

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

def print_data(data):
    print data

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

main()

A good workaround in some cases may be to move the vars + code to another function:

def print_data(data):
    print data

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

main()

回答 3

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

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

It depends how long the function is. The longer the function, the more chance that someone modifying it in future will write data thinking that it means the global. In fact it means the local but because the function is so long it’s not obvious to them that there exists a local with that name.

For your example function, I think that shadowing the global is not bad at all.


回答 4

做这个:

data = [4, 5, 6]

def print_data():
    global data
    print(data)

print_data()

Do this:

data = [4, 5, 6]

def print_data():
    global data
    print(data)

print_data()

回答 5

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)
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)

回答 6

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

data = [4, 5, 6]

def print_data(data_): 
    print(data_)

print_data(data)

I like to see a green tick in the top right corner in pycharm. I append the variable names with an underscore just to clear this warning so I can focus on the important warnings.

data = [4, 5, 6]

def print_data(data_): 
    print(data_)

print_data(data)

回答 7

看起来像是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和所有人感到高兴

It looks like it 100% pytest code pattern

see:

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

I had the same problem with, this is why I found this post ;)

# ./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

And it will warn with This inspection detects shadowing names defined in outer scopes.

To fix that just move your twitter fixture into ./tests/conftest.py

# ./tests/conftest.py
import pytest

from syntropy import db


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

And remove twitter fixture like in ./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

This will be make happy QA, Pycharm and everyone