Блок області в Python


93

Коли ви кодуєте іншими мовами, ви іноді створюєте область блоку, наприклад:

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

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

Чи існує ідіоматичний спосіб робити те саме в Python?


2
One purpose (of many) is to improve code readability- Код Python, написаний правильно (тобто, слідуючи дзену python ), не потребував би такого гарніру для читання. Насправді це одна з (багатьох) речей, які мені подобаються в Python.
Бурхан Халід

Я намагався пограти __exit__і висловитись with, змінивши, globals()але мені не вдалося.
Ruggero Turra

1
було б дуже корисно визначити термін служби змінної, пов’язаний із придбанням ресурсів
Руджеро Турра,

25
@BurhanKhalid: Це неправда. Зен Python не заважає вам забруднювати локальну область з тимчасовою змінною тут і там. Якщо перетворити кожне використання однієї тимчасової змінної, наприклад, на визначення вкладеної функції, яка викликається негайно, дзен Python теж не буде задоволений. Явне обмеження області дії змінної є інструментом для поліпшення читабельності, оскільки вона безпосередньо відповідає "чи використовуються ці ідентифікатори нижче?" - питання, яке може виникнути під час читання навіть найвишуканішого коду Python.
bluenote10

18
@BurhanKhalid Чудово, якщо немає функції. Але називати цей "дзен" просто огидно.
Філ

Відповіді:


81

Ні, не існує мовної підтримки для створення області блоків.

Наступні конструкції створюють область застосування:

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

38

Ідіоматичним способом у Python є короткість ваших функцій. Якщо ви вважаєте, що вам це потрібно, переробіть свій код! :)

Python створює нову область для кожного модуля, класу, функції, виразу генератора, розуміння дикту, розуміння набору, а в Python 3.x також для кожного розуміння списку. Крім них, всередині функцій немає вкладених областей.


12
"Найголовніше в програмуванні - це здатність давати імені щось. Друге найважливіше - не вимагати, щоб щось давали ім'я". Здебільшого Python вимагає, щоб області (для змінних тощо) отримували імена. У цьому відношенні змінні Python - другий за важливістю тест.
Krazy Glew

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

Я щойно помітив, що змінні також є локальними для визначення / встановлення розуміння. Я спробував Python 2.7 і 3.3, але я не впевнений, що це залежить від версії.
wjandrea

1
@wjandrea Ви маєте рацію - додано до списку. Між версіями Python для них не повинно бути різниці.
Sven Marnach

4
Я б переформулював останнє речення, оскільки ви дуже добре можете створювати функції всередині функцій. Отже, всередині функцій є вкладені області.
ThomasH

18

Ви можете зробити щось подібне до області блоку 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

Модулі (та пакети) - чудовий пітонічний спосіб розділити вашу програму на окремі простори імен, що, мабуть, є неявною метою цього питання. Дійсно, коли я вивчав основи 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)

Тоді у своєму основному файлі чи інтерактивному (наприклад, Юпітер) сеансі я це роблю

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. З’ясування цього справило революцію в моєму робочому процесі та перетворило мене із скептика на віруючого в “дзен Пітона”.

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