Насколько плохо теневые имена определены во внешних областях?

209

Я только что переключился на 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)
Framester
источник
1
Я также искал строку «Эта проверка обнаруживает ...», но ничего не нашел в онлайн-справке pycharm
Framester
1
Чтобы отключить это сообщение в PyCharm: <Ctrl> + <Alt> + s (настройки), редактор , проверки , « Затенение имен из внешних областей ». Снимите флажок.
ChaimG

Ответы:

223

В приведенном выше фрагменте нет ничего сложного, но представьте себе функцию с несколькими аргументами и несколькими строками кода. Затем вы решаете переименовать свой dataаргумент, yaddaно пропустите одно из мест, где он используется в теле функции ... Теперь dataотносится к глобальному, и вы начинаете странно себя вести - где у вас было бы гораздо более очевидное, NameErrorесли бы вы этого не сделали иметь глобальное имя data.

Также помните, что в Python все является объектом (включая модули, классы и функции), поэтому нет отдельных пространств имен для функций, модулей или классов. Другой сценарий заключается в том, что вы импортируете функцию fooв верхней части вашего модуля и используете ее где-то в теле функции. Затем вы добавляете новый аргумент в вашу функцию и называете его - невезение - foo.

Наконец, встроенные функции и типы также находятся в одном и том же пространстве имен и могут быть затенены одинаково.

Ничего из этого не представляет большой проблемы, если у вас короткие функции, хорошее наименование и приличное покрытие юнит-тестами, но иногда вам приходится поддерживать не совсем идеальный код, и может помочь предупреждение о таких возможных проблемах.

бруно дестхюльер
источник
21
К счастью, PyCharm (как используется OP) имеет очень хорошую операцию переименования, которая переименовывает переменную везде, где она используется в той же области видимости, что снижает вероятность переименования ошибок.
Wojtow
В дополнение к операции переименования PyCharm я хотел бы иметь специальные подсветки синтаксиса для переменных, которые относятся к внешней области видимости. Эти двое должны сделать эту затрачиваемую на время игру с теневым разрешением неактуальной.
Лев
Примечание: вы можете использовать nonlocalключевое слово, чтобы сделать ссылку на внешнюю оценку (как в замыканиях) явной. Обратите внимание, что это отличается от теневого копирования, поскольку явно не скрывает переменные извне.
Феликс Д.
150

В настоящее время наиболее одобренный и принятый ответ и большинство ответов здесь пропускают суть.

Неважно, какова длина вашей функции или как вы называете переменную описательно (чтобы минимизировать вероятность потенциального конфликта имен).

Тот факт, что локальная переменная вашей функции или ее параметр совместно используют имя в глобальной области видимости, совершенно не имеет значения. И на самом деле, как бы тщательно вы ни выбирали имя локальной переменной, ваша функция никогда не сможет предвидеть «будет ли мое классное имя yaddaтакже использоваться в качестве глобальной переменной в будущем?». Решение? Просто не беспокойся об этом! Правильное мышление заключается в том, чтобы спроектировать вашу функцию так, чтобы она использовала входные данные и только из ее параметров в сигнатуре , таким образом вам не нужно заботиться о том, что находится (или будет) в глобальной области видимости, и тогда затенение вообще не будет проблемой.

Другими словами, проблема теневого копирования имеет значение только тогда, когда вашей функции необходимо использовать локальную переменную с тем же именем И глобальную переменную. Но вы должны избегать такого дизайна в первую очередь. Код OP на самом деле не имеет такой проблемы дизайна. Просто PyCharm недостаточно умен и выдает предупреждение на всякий случай. Итак, просто чтобы порадовать PyCharm, а также сделать наш код чистым, посмотрите, как это решение цитирует ответ Сильевска на полное удаление глобальной переменной.

def print_data(data):
    print data

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

main()

Это правильный способ «решить» эту проблему, исправляя / удаляя вашу глобальную вещь, а не настраивая текущую локальную функцию.

RayLuo
источник
11
Что ж, конечно, в идеальном мире вы всегда делаете опечатку или забываете одну из своих замен поиска, когда вы меняете параметр, но ошибки случаются, и это то, о чем говорит PyCharm - «Предупреждение - технически нет ошибок, но это может легко стать проблемой "
dwanderson
1
@dwanderson Ситуация, которую вы упомянули, не является чем-то новым, она четко описана в выбранном вами ответе. Однако я хочу подчеркнуть, что мы должны избегать глобальных переменных, а не избегать дублирования глобальных переменных. Последний упускает суть. Возьми? Понял?
RayLuo
4
Я полностью согласен с тем фактом, что функции должны быть настолько «чистыми», насколько это возможно, но вы полностью упускаете два важных момента: нет способа ограничить Python в поиске имени во вложенных областях, если оно не определено локально, и всего (модули). , функции, классы и т. д.) является объектом и живет в том же пространстве имен, что и любая другая «переменная». В приведенном выше фрагменте, print_dataIS является глобальной переменной. Подумайте об этом ...
Бруно 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()
silyevsk
источник
Да. Я думаю, что хороший идеал способен обрабатывать локальные и глобальные переменные путем рефакторинга. Ваш совет действительно помогает устранить такие потенциальные риски для примитивных
идей
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)
JoeC
источник
47
Я, например, не смущен. Это довольно очевидно, параметр.
2
@delnan Возможно, вас не смущает этот тривиальный пример, но что, если другие функции, определенные поблизости, используют глобальные data, все в глубине нескольких сотен строк кода?
Джон Коландуони
13
@HevyLight Мне не нужно смотреть на другие функции поблизости. Я смотрю только на эту функцию и вижу, чтоdata это локальное имя этой функции, так что я даже не потрудился проверить / вспомнить ли глобальная то же имя существует , не говоря уже , что она содержит.
4
Я не думаю, что это обоснование справедливо, только потому, что для использования глобальных, вам необходимо определить «глобальные данные» внутри функции. В противном случае глобальное не доступно.
CodyF
1
@CodyF False- если вы не определяете, а просто пытаетесь использовать data, он просматривает области видимости, пока не найдет его, поэтому это действительно найти глобальную data. data = [1, 2, 3]; def foo(): print(data); foo()
Двандерсон
3

Мне нравится видеть зеленую галочку в правом верхнем углу в pycharm. Я добавляю имена переменных с подчеркиванием, чтобы очистить это предупреждение, чтобы сосредоточиться на важных предупреждениях.

data = [4, 5, 6]

def print_data(data_): 
    print(data_)

print_data(data)
Baz
источник
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 и всех остальных

Andrei.Danciuc
источник