外側のスコープで定義されたシャドウイング名はどのくらい悪いですか?


208

Pycharmに切り替えたところ、コードを改善するためのすべての警告とヒントに非常に満足しています。私が理解していないこれを除いて:

This inspection detects shadowing names defined in outer scopes.

外側のスコープから変数にアクセスすることは悪い習慣ですが、外側のスコープのシャドウイングの問題は何ですか?

Pycharmが警告メッセージを表示する1つの例を次に示します。

data = [4, 5, 6]

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

print_data(data)

1
また、「この検査は検出しました...」という文字列を検索しましたが、pycharmオンラインヘルプには何も見つかりませんでした。jetbrains.com/ pycharm
Framester

1
PyCharmでこのメッセージをオフにするには:<Ctrl> + <Alt> + s(設定)、エディター検査、「外部スコープからの名前のシャドウ」。チェックを外します。
ChaimG

回答:


222

上記のスニペットでは大したことはありませんが、引数がいくつかあり、コード行がかなり多い関数を想像してみてください。次に、data引数の名前をに変更することを決定しyaddaましたが、関数の本体で使用されている場所の1つを見逃しました...これでdataグローバルを参照し、奇妙な動作を開始します- NameErrorそうしなかった場合は、より明白になります。グローバル名を持っていdataます。

また、Pythonではすべてがオブジェクト(モジュール、クラス、関数を含む)であるため、関数、モジュール、またはクラスに固有の名前空間はないことを忘れないでください。別のシナリオではfoo、モジュールの上部で関数をインポートし、関数本体のどこかで使用します。次に、関数に新しい引数を追加して、「不運」という名前を付けますfoo

最後に、組み込み関数と型も同じ名前空間にあり、同じようにシャドウイングできます。

短い関数、優れたネーミング、まともなユニットテストカバレッジがある場合、これはそれほど問題にはなりませんが、場合によっては、完全ではないコードを維持する必要があり、そのような考えられる問題について警告することは役立つかもしれません。


21
幸い、PyCharm(OPで使用される)には、同じスコープ内で使用されているすべての場所で変数の名前を変更する非常に優れた名前変更操作があり、エラーの名前変更の可能性が低くなります。
wojtow 2017

PyCharmの名前変更操作に加えて、外部スコープを参照する変数の特別な構文の強調表示が欲しいです。これらの2つは、この時間のかかるシャドウ解像度ゲームを無関係にします。
レオ

補足:nonlocalキーワードを使用して、外部のスコア参照(クロージャー内など)を明示的にすることができます。これは、外部から変数を明示的にシャドウしないため、シャドウイングとは異なります。
Felix D.

149

現在最も投票されて受け入れられている回答と、ここでのほとんどの回答は要点を逃しています。

関数の長さや、変数にわかりやすい名前を付けることは重要ではありません(名前が衝突する可能性を最小限に抑えるためです)。

関数のローカル変数またはそのパラメーターがたまたまグローバルスコープで名前を共有しているという事実は、まったく無関係です。実際、ローカル変数名をどのように注意深く選択しても、関数は「私のクールな名前yaddaが将来グローバル変数としても使用されるかどうか」を予測できません。ソリューション?心配する必要はありません。正しい考え方は、シグニチャーのパラメーターからの入力のみからの入力を消費するように関数を設計することです。これにより、グローバルスコープの内容(または今後の内容)を気にする必要がなく、シャドウイングがまったく問題になりません。

言い換えると、シャドウイングの問題は、関数で同じ名前のローカル変数とグローバル変数を使用する必要がある場合にのみ問題になります。ただし、そもそもそのような設計は避けてください。OPのコードには、このような設計上の問題は実際にはありません。それはPyCharmが十分に賢くないというだけのことであり、念のために警告を発します。したがって、PyCharmを幸せにし、コードをクリーンにするために、silyevskの回答から引用したこのソリューションを参照してくださいするために、して、グローバル変数を完全に削除してください。

def print_data(data):
    print data

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

main()

これは、現在のローカル関数を調整するのではなく、グローバルなものを修正/削除することにより、この問題を「解決」する適切な方法です。


11
確かに、完璧な世界では、タイプミスを犯したり、パラメータを変更したときに検索置換の1つを忘れたりしますが、ミスが発生し、それがPyCharmが言っていることです-「警告-技術的にエラーはありませんが、これは簡単に問題になる可能性があります」
dwanderson 2017

1
@dwandersonあなたが言及した状況は新しいものではなく、現在選択されている回答で明確に説明されています。ただし、私が強調しようとしている点は、グローバル変数をシャドウするのではなく、グローバル変数を回避することです。後者は要点を逃しています。それを得る?とった?
RayLuo 2017

4
関数は可能な限り「純粋」である必要があるという事実に完全に同意しますが、2つの重要なポイントを完全に逃します。Pythonがローカルで定義されていない場合、それを囲むスコープで名前を検索することを制限する方法はなく、すべて(モジュール、関数、クラスなど)はオブジェクトであり、他の「変数」と同じ名前空間に存在します。上記のスニペットでは、print_dataISはグローバル変数です。考えてみてください
bruno desthuilliers 2017年

2
関数で定義された関数を使用しているため、グローバルネームスペースを乱雑にしたり、個別のファイルを多用したりせずに外部関数を読みやすくするため、このスレッドになりました。この例は、シャドーされる非ローカル非グローバル変数の一般的なケースには適用されません。
micseydel

2
同意します。ここでの問題はPythonのスコープです。現在のスコープ外のオブジェクトへの非明示的なアクセスは問題を求めています。誰がそれを望んでいるのでしょう!それ以外の場合、Pythonはかなりよく考えられた言語であるため、残念です(モジュールの命名における同様のあいまいさには耐えられません)。
CodeCabbie

24

場合によっては、vars +コードを別の関数に移動するのが適切な回避策です。

def print_data(data):
    print data

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

main()

はい。良いIDEは、リファクタリングによってローカル変数とグローバル変数を処理できると思います。あなたのヒントは、原始的な
IDEの

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
私は1つは混乱していません。それは明らかに明らかにパラメータです。

2
@delnanこのささいな例では混乱しないかもしれませんが、近くで定義されている他の関数がglobalを使用している場合はどうdataでしょうか。
John Colanduoni 2013年

13
@HevyLight近くにある他の関数を見る必要はありません。私はこの関数のみを見て、それdataこの関数のローカル名であることを確認できます。そのため、同じ名前のグローバルが存在するかどうかを確認したり、覚えたりすることさえありません。

4
グローバルを使用するには、関数内で「グローバルデータ」を定義する必要があるという理由だけで、この推論は有効ではないと思います。そうしないと、グローバルにアクセスできません。
CodyF 2015

1
@CodyF False-あなたが定義するが、ちょうど使用しようとしていない場合data、それは1見つかるまで、それはので、それは、スコープを通して見上げていグローバル見つけますdatadata = [1, 2, 3]; def foo(): print(data); foo()
dwanderson 2017

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、そしてみんなを幸せにするでしょう

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.