Наскільки погано визначено затінення імен у зовнішніх областях?


208

Я щойно перейшов на 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
Framester

1
Щоб вимкнути це повідомлення в PyCharm: <Ctrl> + <Alt> + s (налаштування), Редактор , Інспекції , " Затінення імен із зовнішніх областей ". Зніміть прапорець.
ChaimG

Відповіді:


222

У вашому вище фрагменті нічого не важливого, але уявіть собі функцію з ще кількома аргументами та ще кількома рядками коду. Тоді ви вирішите перейменувати свій dataаргумент як, yaddaале пропустите одне з місць, яке воно використовується в тілі функції ... Тепер dataвідноситься до глобального, і ви починаєте дивну поведінку - де ви мали б набагато очевидніше, NameErrorякби не мають глобальну назву data.

Також пам’ятайте, що в Python все є об’єктом (включаючи модулі, класи та функції), тому немає чітких просторів імен для функцій, модулів чи класів. Інший сценарій полягає в тому, що ви імпортуєте функцію fooу верхній частині свого модуля та використовуєте її десь у тілі функції. Тоді ви додаєте новий аргумент до своєї функції і назвали її - невдача - foo.

Нарешті, вбудовані функції та типи також живуть в одному просторі імен і можуть бути затінені однаково.

Нічого з цього не викликає особливих проблем, якщо у вас короткі функції, гарне іменування та пристойне охоплення одиничним тестом, але добре, що іноді вам доведеться підтримувати менше, ніж ідеальний код, і попередження про такі можливі проблеми може допомогти.


21
На щастя, PyCharm (як використовується ОП) має дуже хорошу операцію перейменування, яка перейменовує змінну скрізь, де вона використовується в тому ж обсязі, що робить помилки перейменування менш ймовірними.
wojtow

Окрім операції з перейменування PyCharm, я хотів би мати спеціальні синтаксичні виділення для змінних, що стосуються зовнішньої області. Ці двоє повинні зробити цю заборювальну гру з тіньовою грою неактуальною.
Лев

Побічна примітка: Ви можете використовувати nonlocalключове слово, щоб зробити явні зовнішні оцінки (наприклад, у закриттях) явними. Зауважте, що це відрізняється від затінення, оскільки воно явно не затінює змінні зовні.
Фелікс Д.

149

Наразі відповідь, якою найбільше відповідають голоси, і більшість відповідей тут не вдається.

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

Той факт, що локальна змінна вашої функції або її параметр має спільне використання імені в глобальній області, абсолютно не має значення. Насправді, незалежно від того, наскільки ретельно ви вибираєте локальну назву змінної, ваша функція ніколи не може передбачити "чи моє круте ім'я yaddaтакож буде використовуватися як глобальна змінна в майбутньому?". Рішення? Просто не хвилюйтеся з цього приводу! Правильний спосіб мислення полягає в тому, щоб розробити свою функцію для споживання вхідних даних і тільки з їхніх параметрів на підпис , таким чином вам не потрібно дбати про те, що є (або буде) у глобальному масштабі, і тоді затінення не стає взагалі проблемою.

Іншими словами, проблема тінізації має значення лише тоді, коли вашій функції потрібно використовувати ту ж ім'я локальної змінної І глобальну змінну. Але вам слід уникати такого дизайну в першу чергу. Код ОП насправді не має такої дизайнерської проблеми. Просто PyCharm недостатньо розумний, і він видає попередження про всяк випадок. Отже, щоб зробити PyCharm щасливим, а також зробити наш код чистим, дивіться це рішення з цитуванням відповіді Сілєвська, щоб повністю видалити глобальну змінну.

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_dataIS є глобальною змінною. Подумайте над цим ...
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()

Так. Я думаю, що хороша ідея здатна обробляти локальні та глобальні змінні шляхом рефакторингу. Ваша порада справді допомагає усунути такі потенційні ризики для примітивної
ідеї

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 Вас не можуть збентежити в цьому тривіальному прикладі, але що робити, якщо інші функції, визначені поблизу, використовували глобальну data, все глибоко в межах декількох сотень рядків коду?
Джон Коландуні

13
@HevyLight Мені не потрібно дивитись на інші функції поблизу. Я дивлюся лише на цю функцію і бачу, що dataце локальне ім'я в цій функції, тому я навіть не переймаюся перевіряти / пам’ятати, чи існує однойменний глобальний , не кажучи вже про те, що він містить.

4
Я не думаю, що це міркування є дійсним, лише тому, що для використання глобальної функції вам потрібно буде визначити "глобальні дані" всередині функції. Інакше глобальний не доступний.
CodyF

1
@CodyF False- якщо ви не визначити, а просто намагаєтеся використовувати data, він дивиться через приціли до тих пір , поки не знайде один, так що це дійсно знайти глобальну data. data = [1, 2, 3]; def foo(): print(data); foo()
dwanderson

3

Мені подобається бачити зелений галочку в правому верхньому куті в піхармі. Я додаю назви змінних із підкресленням, щоб очистити це попередження, щоб я міг зосередитись на важливих попередженнях.

data = [4, 5, 6]

def print_data(data_): 
    print(data_)

print_data(data)

2

Схоже, це 100% пітестний код

побачити:

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 та всі

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.