Область видимости блока в Python

94

Когда вы пишете код на других языках, вы иногда создаете область видимости блока, например:

statement
...
statement
{
    statement
    ...
    statement
}
statement
...
statement

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

Есть ли идиоматический способ сделать то же самое в Python?

Йохан Роде
источник
2
One purpose (of many) is to improve code readability- Код Python, написанный правильно (т.е. следуя дзену Python ), не нуждается в таком украшении, чтобы его можно было читать. Фактически, это одна из (многих) вещей, которые мне нравятся в Python.
Бурхан Халид
Я пытался играть с __exit__и withзаявления, изменение , globals()но мне не удалось.
Руджеро Турра,
1
было бы очень полезно определить время жизни переменной, связанной с получением ресурса
Руджеро Турра
25
@BurhanKhalid: Это неправда. Дзен Python не мешает вам загрязнять локальную область видимости временной переменной здесь и там. Если вы превратите каждое использование одной временной переменной, например, в определение вложенной функции, которая вызывается немедленно, дзен Python тоже не будет счастлив. Явное ограничение объема переменной - это инструмент для улучшения читабельности, потому что он напрямую отвечает на вопрос «используются ли эти идентификаторы ниже?» - вопрос, который может возникнуть при чтении даже самого элегантного кода Python.
bluenote10
18
@BurhanKhalid Это нормально - не иметь функции. Но называть это «дзен» просто противно.
Фил

Ответы:

83

Нет, нет языковой поддержки для создания области блока.

Следующие конструкции создают область видимости:

  • модуль
  • класс
  • функция (включая лямбда)
  • генератор выражения
  • понимания (dict, set, list (в Python 3.x))
ThomasH
источник
38

Идиоматический способ в Python - делать ваши функции короткими. Если вы думаете, что вам это нужно, реорганизуйте свой код! :)

Python создает новую область видимости для каждого модуля, класса, функции, выражения генератора, понимания dict, понимания множества, а в Python 3.x также для каждого понимания списка. Помимо этого, внутри функций нет вложенных областей видимости.

Свен Марнах
источник
12
«Самая важная вещь в программировании - это способность давать чему-то имя. Вторая по важности вещь - не требовать давать чему-то имя». По большей части Python требует, чтобы области действия (для переменных и т. Д.) Имели имена. В этом отношении переменные Python - второй по важности тест.
Krazy Glew
19
Наиболее важные вещи в программировании - это возможность управлять зависимостями вашего приложения и управлять объемом блоков кода. Анонимные блоки позволяют ограничить время жизни обратных вызовов, тогда как в противном случае ваши обратные вызовы используются только один раз, но действуют на протяжении всей программы, это вызывает беспорядок в глобальной области видимости и ухудшает читаемость кода.
Дмитрий
Я только что заметил, что переменные также являются локальными, чтобы определять / устанавливать понимание. Я пробовал Python 2.7 и 3.3, но не уверен, зависит ли он от версии.
wjandrea
1
@wjandrea Вы правы - добавили в список. Для них не должно быть разницы между версиями Python.
Sven Marnach
4
Я бы перефразировал последнее предложение, поскольку вы вполне можете создавать функции внутри функций. Итак, внутри функций есть вложенные области видимости.
ThomasH
19

Вы можете сделать что-то похожее на область видимости блока C ++ в Python, объявив функцию внутри своей функции и затем немедленно вызвав ее. Например:

def my_func():
    shared_variable = calculate_thing()

    def do_first_thing():
        ... = shared_variable
    do_first_thing()

    def do_second_thing():
        foo(shared_variable)
        ...
    do_second_thing()

Если вы не уверены, зачем вам это нужно, это видео может вас убедить.

Основной принцип состоит в том, чтобы охватить все как можно более плотно, не вводя какой-либо `` мусор '' (дополнительные типы / функции) в более широкую область, чем это абсолютно необходимо - никто другой не хочет использовать do_first_thing()метод, например, поэтому он не должен выходить за вызывающая функция.

Бен
источник
Это также способ, который используют разработчики Google в обучающих материалах TensorFlow, как показано, например, здесь
Нино Филиу
13

Я согласен, что блокировка отсутствует. Но одно место в python 3 делает его ВИДОМ, как будто он имеет область видимости блока.

что случилось, что придало этому виду? Это работало правильно в python 2, но чтобы остановить утечку переменной в python 3, они сделали этот трюк, и это изменение заставляет его выглядеть так, как будто здесь есть область действия блока.

Позволь мне объяснить.


Согласно идее области видимости, когда мы вводим переменные с одинаковыми именами внутри одной и той же области, ее значение должно быть изменено.

это то, что происходит в python 2

>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'W'

Но в python 3, даже несмотря на то, что переменная с тем же именем введена, она не переопределяет, понимание списка по какой-то причине действует как песочница и похоже на создание в ней новой области.

>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'OLD'

и этот ответ противоречит утверждению ответчика @ Thomas . Единственное средство для создания области видимости - это функции, классы или модули, потому что это похоже на еще одно место для создания новой области.

Хариш Каяроханам
источник
0

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

В качестве мотивации и для того, чтобы указывать людям правильное направление, я думаю, полезно привести явные примеры некоторых конструкций области видимости Python. Сначала я объясню свою неудачную попытку использовать классы Python для реализации области видимости блока. Затем я объясню, как я добился чего-то более полезного, используя модули Python. В конце я описываю практическое применение пакетов для загрузки и фильтрации данных.

Попытка блокировки области с классами

На какое-то время мне показалось, что я достиг области видимости блока, вставив код внутри объявления класса:

x = 5
class BlockScopeAttempt:
    x = 10
    print(x) # Output: 10
print(x) # Output: 5

К сожалению, это не работает, когда функция определяется:

x = 5 
class BlockScopeAttempt: 
    x = 10
    print(x) # Output: 10
    def printx2(): 
        print(x) 
    printx2() # Output: 5!!!

Это потому, что функции, определенные в классе, используют глобальную область видимости. Самый простой (но не единственный) способ исправить это - явно указать класс:

x = 5 
class BlockScopeAttempt: 
    x = 10
    print(x) # Output: 10
    def printx2(): 
        print(BlockScopeAttempt.x)  # Added class name
    printx2() # Output: 10

Это не так элегантно, потому что нужно писать функции по-разному в зависимости от того, содержатся они в классе или нет.

Лучшие результаты с модулями Python

Модули очень похожи на статические классы, но, по моему опыту, модули намного чище. Чтобы сделать то же самое с модулями, я создаю файл, вызываемый my_module.pyв текущем рабочем каталоге, со следующим содержимым:

x = 10
print(x) # (A)

def printx():
    global x
    print(x) # (B)

Затем в моем основном файле или интерактивном сеансе (например, Jupyter) я делаю

x = 5
import my_module # Output: 10 from (A)
my_module.printx() # Output: 10 from (B)
print(x) # Output: 5

В качестве объяснения каждый файл Python определяет модуль, который имеет собственное глобальное пространство имен. Импорт модуля позволяет вам получить доступ к переменным в этом пространстве имен с помощью .синтаксиса.

Если вы работаете с модулями в интерактивном сеансе, вы можете выполнить эти две строки в начале

%load_ext autoreload
%autoreload 2

и модули будут автоматически перезагружены при изменении соответствующих файлов.

Пакеты для загрузки и фильтрации данных

Идея пакетов - это небольшое расширение концепции модулей. Пакет - это каталог, содержащий (возможно, пустой) __init__.pyфайл, который выполняется при импорте. Доступ к модулям / пакетам в этом каталоге можно получить с помощью .синтаксиса.

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

В настоящее время с Python я определяю пакет с именем, my_dataкоторый содержит подмодули с именами loadи filter. Внутри filter.pyя могу сделать относительный импорт:

from .load import raw_data

Если я изменю filter.py, то autoreloadобнаружу изменения. Он не перезагружается load.py, поэтому мне не нужно перезагружать мои данные. Таким образом, я могу создать прототип своего кода фильтрации в блокноте Jupyter, обернуть его как функцию, а затем вырезать и вставить прямо из блокнота в filter.py. Осознание этого произвело революцию в моем рабочем процессе и превратило меня из скептика в сторонника «дзен Python».

Бен Марес
источник