Таким образом, компромисс производительности между наличием множества различных каталогов в вашем PYTHONPATH
и наличием глубоко вложенных структур пакетов будет виден в системных вызовах. Предполагая, что у нас есть следующие структуры каталогов:
bash-3.2$ tree a
a
└── b
└── c
└── d
└── __init__.py
bash-3.2$ tree e
e
├── __init__.py
├── __init__.pyc
└── f
├── __init__.py
├── __init__.pyc
└── g
├── __init__.py
├── __init__.pyc
└── h
├── __init__.py
└── __init__.pyc
Мы можем использовать эти структуры и программу strace
для сравнения и сопоставления системных вызовов, которые мы генерируем для следующих команд:
strace python -c 'from e.f.g import h'
PYTHONPATH="./a/b/c:$PYTHONPATH" strace python -c 'import d'
Много записей PYTHONPATH
Таким образом, компромисс здесь - это системные вызовы во время запуска по сравнению с системными вызовами во время импорта. Для каждой записи в PYTHONPATH
, python
сначала проверяет, существует ли каталог:
stat("./a/b/c", {st_mode=S_IFDIR|0776, st_size=4096, ...}) = 0
stat("./a/b/c", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
Если каталог существует (он ... обозначен 0 справа), Python будет искать несколько модулей при запуске интерпретатора. Для каждого модуля он проверяет:
stat("./a/b/c/site", 0x7ffd900baaf0) = -1 ENOENT (No such file or directory)
open("./a/b/c/site.x86_64-linux-gnu.so", O_RDONLY) = -1 ENOENT (No such file or directory)
open("./a/b/c/site.so", O_RDONLY) = -1 ENOENT (No such file or directory)
open("./a/b/c/sitemodule.so", O_RDONLY) = -1 ENOENT (No such file or directory)
open("./a/b/c/site.py", O_RDONLY) = -1 ENOENT (No such file or directory)
open("./a/b/c/site.pyc", O_RDONLY) = -1 ENOENT (No such file or directory)
Каждый из них не выполняется, и он переходит к следующей записи в пути в поисках модуля для заказа. Мой 3.5 intepretter искал 25 модулей таким образом, производя добавочные 152
системные вызовы при запуске для каждой новой записи PYTHONPATH
.
Глубокая структура упаковки
Глубокая структура пакета не платит штрафов при запуске интерпретатора, но когда мы импортируем из глубоко вложенной структуры пакета, мы видим разницу. В качестве основы, вот простой импорт d/__init__.py
из каталога a/b/c
в нашем PYTHONPATH
:
stat("/home/matt/a/b/c/d", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
stat("/home/matt/a/b/c/d/__init__.py", {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
stat("/home/matt/a/b/c/d/__init__", 0x7ffd900ba990) = -1 ENOENT (No such file or directory)
open("/home/matt/a/b/c/d/__init__.x86_64-linux-gnu.so", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/home/matt/a/b/c/d/__init__.so", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/home/matt/a/b/c/d/__init__module.so", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/home/matt/a/b/c/d/__init__.py", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
open("/home/matt/a/b/c/d/__init__.pyc", O_RDONLY) = 4
fstat(4, {st_mode=S_IFREG|0664, st_size=117, ...}) = 0
read(4, "\3\363\r\n\17\3105[c\0\0\0\0\0\0\0\0\1\0\0\0@\0\0\0s\4\0\0\0d\0"..., 4096) = 117
fstat(4, {st_mode=S_IFREG|0664, st_size=117, ...}) = 0
read(4, "", 4096) = 0
close(4) = 0
close(3) = 0
В основном то, что он делает, ищет пакет или модуль d
. Когда он находит d/__init__.py
, он открывает его, а затем открывает d/__init__.pyc
и считывает содержимое в память перед закрытием обоих файлов.
С нашей глубоко вложенной структурой пакета мы должны повторить эту операцию еще 3 раза, что хорошо для 15
системных вызовов на каталог, что в сумме дает еще 45 системных вызовов. Хотя это меньше половины количества вызовов, добавляемых путем добавления пути к нашим PYTHONPATH
, вызовы read
могут потенциально занимать больше времени, чем другие системные вызовы (или требовать больше системных вызовов), в зависимости от размера из файлов __init__.py
.
TL; DR
Учитывая все это, эти различия почти наверняка не настолько существенны, чтобы перевесить конструктивные преимущества вашего желаемого решения.
Это особенно верно, если ваши процессы работают долго (например, веб-приложение), а не недолговечно.
Мы можем уменьшить системные вызовы на:
- Удаление любых посторонних
PYTHONPATH
записей
- Предварительно скомпилируйте
.pyc
файлы, чтобы избежать необходимости их записи
- Сохраняйте структуру пакета плоской
Мы могли бы значительно повысить производительность, удалив файлы py
, чтобы они не читались для целей отладки вместе с файлами PYC ... но мне это кажется слишком большим шагом.
Надеюсь, это полезно, возможно, это гораздо более глубокое погружение, чем необходимо.