В этом уроке вы изучите возможности оператора импорта в Python и то, как они работают «под капотом».
Импорт модулей в Python
Когда вы импортируете модуль, Python делает две вещи:
- Сначала проверяет, был ли модуль загружен и кэширован в sys.modules. Если нет, он выполнит модуль и создаст ссылку на объект модуля.
- Во-вторых, добавляет имя модуля в глобальное пространство имен, ссылающееся на тот же объект модуля.
Следующая программа импортирует модуль math и распечатывает математический объект math в sys.modules и глобальном пространстве имен:
import sys
import math
print('sys.modules:', hex(id(sys.modules['math'])))
if 'math' in globals():
print('globals: ', hex(id(globals()['math'])))
Выход:
sys.modules: 0x20456766590 globals: 0x20456766590
Как видите, математическая переменная ссылается на один и тот же объект модуля.
Если вы импортируете math во второй раз, Python не выполнит модуль повторно, а получит его из кэша sys.modules.
Импорт объекта
Когда вы импортируете объект (функцию, класс и т. д.) из модуля в Python, необходимо сделать следующее:
- Сначала проверьте, был ли модуль загружен и кэширован в sys.modules. Если нет, он выполнит модуль и создаст ссылку на объект модуля.
- Во-вторых, добавьте объект импорта в глобальное пространство имен.
В этом случае Python добавляет в глобальное пространство имен не переменную, ссылающуюся на модуль, а переменную, ссылающуюся на импортируемый объект.
В следующем примере функция ceil импортируется из объекта math:
import sys
from pprint import pprint
from math import ceil
print('sys.modules:', hex(id(sys.modules['math'])))
pprint(globals())
Выход:
sys.modules: 0x11d659c2130
{'__annotations__': {},
'__builtins__': <module 'builtins'(built-in)>,
'__cached__': None,
'__doc__': None,
'__file__': 'C:/oop/app.py',
'__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000011D65A008E0>,
'__name__': '__main__',
'__package__': None,
'__spec__': None,
'ceil': <built-in function ceil>,
'pprint': <function pprint at 0x0000011D661C4040>,
'sys': <module 'sys'(built-in)>}
В этом примере Python загружает математический модуль в sys.modules. Однако он создает ссылку только на функцию ceil, а не на объект модуля math в глобальном пространстве имен.
Импорт объекта из модуля как object_alias
Когда вы загружаете объект из модуля и используете псевдоним в Python, сделайте следующее:
- Сначала проверьте, был ли модуль загружен и кэширован в sys.modules. Если нет, он выполнит модуль и создаст ссылку на объект модуля.
- Во-вторых, создайте псевдоним, ссылающийся на импортируемый объект, и добавьте его в глобальное пространство имен.
Следующий пример импортирует функцию ceil из модуля math и использует псевдоним:
import sys
from math import ceil as ceiling
print('sys.modules:', hex(id(sys.modules['math'])))
print('globals:', hex(id(globals()['ceiling'])))
Выход:
sys.modules: 0x1cc4f244ae0
{'__annotations__': {},
'__builtins__': <module 'builtins'(built-in)>,
'__cached__': None,
'__doc__': None,
'__file__': 'C:/oop/app.py',
'__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001CC4EA708E0>,
'__name__': '__main__',
'__package__': None,
'__spec__': None,
'ceiling': <built-in function ceil>,
'pprint': <function pprint at 0x000001CC4F234040>,
'sys': <module 'sys'(built-in)>}
Конструкция from module import *
Когда вы импортируете все из модуля, Python сделает следующее:
- Проверит, был ли модуль загружен и кэширован в sys.modules. Если нет, он выполнит модуль и создаст ссылку на объект модуля.
- Во-вторых, добавит все символы из модуля в глобальное пространство имен.
Например, следующий код импортирует все объекты из модуля math:
import sys
from pprint import pprint
from math import *
print('sys.modules:', hex(id(sys.modules['math'])))
pprint(globals())
Выход:
sys.modules: 0x1e1ebf24b30
{'__annotations__': {},
'__builtins__': <module 'builtins'(built-in)>,
'__cached__': None,
'__doc__': None,
'__file__': 'C:/oop/app.py',
'__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001E1EB7408E0>,
'__name__': '__main__',
'__package__': None,
'__spec__': None,
'acos': <built-in function acos>,
'acosh': <built-in function acosh>,
'asin': <built-in function asin>,
....
'tau': 6.283185307179586,
'trunc': <built-in function trunc>}
Как ясно видно из вывода, Python добавляет все функции из модуля math в глобальные пространства имен. В этом случае, если в глобальном пространстве имен существуют какие-либо символы, Python заменит их ссылки.
Это часто приводит к ошибкам, которые трудно отследить.
Заблуждение об операторе import в Python
Одним из наиболее распространенных заблуждений относительно оператора импорта является то, что многие считают следующее утверждение:
from math import ceil
более эффективен, чем:
import math
Потому что первый оператор импортирует только функцию ячейки, а второй оператор импортирует весь математический модуль.
Однако в обоих случаях Python загружает весь модуль math.
Первый оператор создает символ, ссылающийся на функцию ceil из math-модуля, а второй оператор создает математический символ, ссылающийся на объект математического модуля.
