Пространства имён в Python

Матрешки в программировании Python

В данной статье будет исследоваться пространства имён и то, как Python использует их, чтобы помочь улучшить код.

Как и во многих языках программирования, Python изолирует код через концепцию пространств имён. Во время работы программы он отслеживает все известные пространства имён и информацию, доступную в этих пространствах имён.

Пространства имён полезны несколькими способами:

  • По мере развития программного обеспечения множественные понятия требуют одинаковых или идентичных имён.
  • Пространства имён помогают минимизировать коллизии, давая понять, к какой концепции относится каждое имя.
  • По мере роста программного обеспечения становится все сложнее узнать, какой код уже находится в кодовой базе. Пространства имён помогают вам сделать обоснованные предположения о том, где может жить код, если он существует.
  • При добавлении нового кода в большую кодовую базу существующие пространства имён могут указывать, где должен находиться новый код. Если очевидного выбора не существует, может оказаться подходящим новое пространство имён.

В данной статье будет исследоваться пространства имён и то, как Python использует их, чтобы помочь улучшить код.

Как и во многих языках программирования, Python изолирует код через концепцию пространств имён. Во время работы программы он отслеживает все известные пространства имён и информацию, доступную в этих пространствах имён.

Пространства имён полезны несколькими способами:

  • По мере развития программного обеспечения множественные понятия требуют одинаковых или идентичных имён.
  • Пространства имён помогают минимизировать коллизии, давая понять, к какой концепции относится каждое имя.
  • По мере роста программного обеспечения становится все сложнее узнать, какой код уже находится в кодовой базе. Пространства имён помогают вам сделать обоснованные предположения о том, где может жить код, если он существует.
  • При добавлении нового кода в большую кодовую базу существующие пространства имён могут указывать, где должен находиться новый код. Если очевидного выбора не существует, может оказаться подходящим новое пространство имён.

Пространства имён настолько важны, что они включены в качестве последнего строки в Zen of Python (если вы не знакомы с Zen of Python, попробуйте запустить интерпретатор Python и набрать import this).

Пространства имён — отличная штука! Будем делать их больше!

Все переменные, функции и классы, которые вы когда-либо использовали в Python, были именами в одном или другом пространстве имён. Имена подобны x, total или EssentialBusinessDomainObject, которые являются ссылками на что-либо.

Когда ваш код Python говорит, что x = 3, это означает, что «присвойте значение 3 имени x», и тогда вы можете ссылаться на x в своем коде.

Слово «переменная» используется взаимозаменяемо с именами, которые относятся к значениям, хотя имена могут ссылаться на функции, классы и многое другое в Python.

Пространства имён и оператор import

При первом открытии интерпретатора Python встроенное пространство имён заполняется компонентами, встроенными в Python. Встроенное пространство имён Python содержит встроенные функции, такие как print() и open().

Эти встроенные модули не имеют префиксов, и вам не нужно делать ничего особенного, чтобы их использовать. Python делает их доступными для вас в любом месте вашего кода. Вот почему так легко print("Hello world!") на Python.

В отличие от других языков программирования, вы не будете явно создавать пространства имён в своем Python коде, но ваша структура кода влияет на то, какие пространства имён создаются и как они взаимодействуют.

Например, создание модуля Python автоматически создает дополнительное пространство имён для этого модуля. Самый простой модуль Python – это обычный файл .py, содержащий некоторый код.

Например, файл с именем sales_tax.py будет именоваться «модулем sales_tax»:

# sales_tax.py
def add_sales_tax(total, tax_rate):
    return total * tax_rate

Каждый модуль содержит своё глобальное пространство имён, к которому у кода в модуле есть свободный доступ. Функции, классы и переменные, которые ни во что не вложены, находятся в глобальном пространстве имён модуля:

# sales_tax.py

TAX_RATES_BY_STATE = {          #1
    'MI': 1.06,
    # ...
}

def add_sales_tax(total, state):
    return total * TAX_RATES_BY_STATE[state]  #2

#1 XTAX_RATES_BY_STATE находится в глобальном пространстве имён модуля.

#2 Код в модуле может использовать TAX_RATES_BY_STATE без каких-либо дополнительных действий.

Функции и классы в модуле также имеют локальное пространство имён, к которому только они имеют доступ:

# sales_tax.py

TAX_RATES_BY_STATE = {
    'MI': 1.06,
    ...
}

def add_sales_tax(total, state):
    tax_rate = TAX_RATES_BY_STATE[state]  #1
    return total * tax_rate               #2

#1 tax_rate находится только в локальной области видимости для add_sales_tax().

#2 Код в add_sales_tax() может использовать tax_rate без каких-либо дополнительных действий.

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

# receipt.py

from sales_tax import add_sales_tax  #1


def print_receipt():
    total = ...
    state = ...
    print(f'TOTAL: {total}')
    print(f'AFTER TAX: {add_sales_tax(total, state)}')  #2

#1 Функция add_sales_tax добавлена в глобальное пространство имён receipt.

#2 add_sales_tax все еще знает о TAX_RATES_BY_STATE и tax_rate из своего собственного пространства имён.

Чтобы ссылаться на переменную, функцию или класс в Python, одно из следующих должно быть истинным:

  1. Имя находится во встроенном пространстве имён Python.
  2. Имя - это глобальное пространство имён текущего модуля.
  3. Имя находится в текущей строке локального пространства имён кода.

Приоритет для конфликтующих имён работает в обратном порядке; локальное имя переопределяет глобальное имя, которое переопределяет встроенное имя.

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

Алгоритм поиска переменной в различных пространствах имён

Возможно, вы видели NameError: name 'my_var' is not defined в ваших приключениях с Python. Это означает, что имя my_var не было найдено ни в одном из пространств имён, известных этому коду.

Обычно это означает, что вы никогда не присваивали my_var значение или присваивали его где-то еще, и вам необходимо его импортировать.

Модули - отличный способ начать разделение кода; если у вас есть один длинный script.py с кучей несвязанных функций, рассмотрите возможность разбиения этих функций на модули.

Множественный импорт

Синтаксис для импорта в Python поначалу кажется простым, но есть несколько способов сделать это, и каждый способ приводит к незначительным различиям в информации, вносимой в пространство имён.

Ранее вы импортировали функцию add_sales_tax() из модуля sales_tax в модуль receipt:

# receipt.py

from sales_tax import add_sales_tax

Это добавляет функцию add_sales_tax() в глобальное пространство имён модуля receipt.

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

# receipt.py

from sales_tax import add_sales_tax, add_state_tax, add_city_tax, add_local_millage_tax, ...

Это альтернативный синтаксис, который немного улучшает код:

# receipt.py

from sales_tax import (
    add_sales_tax,
    add_state_tax,
    add_city_tax,
    add_local_millage_tax,
    ...
)

Это все ещё не здорово. Когда вам нужен набор функций из другого модуля, вы можете импортировать этот модуль полностью вместо этого:

# receipt.py

import sales_tax

Это добавляет весь модуль sales_tax к текущее пространство имён, и на его функции можно ссылаться с помощью sales_tax префикса:

# receipt.py

import sales_tax

def print_receipt():

   ...

   total = ...

   locale = ...

   ...

   print(f'AFTER MILLAGE: {sales_tax.add_local_millage_tax(total, locale)}')

Это позволяет избежать длинных операторов импорта, а префикс помогает избежать коллизий пространства имён.

Импорты со звёздочкой

Python позволяет импортировать все имена из модуля в сокращенном виде, используя from themodule import *. Соблазнительно использовать это вместо префикса этих имён с themodule, поэтому по всему коду повторять его не надо!

Импорт со звёздочкой может вызвать больше конфликтов имён, потому что вы не можете видеть импортируемые явные имена, и это затрудняет отладку. Придерживайтесь явного импорта!