Объясните __all__ в Python?
Я вижу переменную __all__
, установленную в разных __init__.py
файлах.
Что это делает?
Что делает __all__
? 1014 *
Он объявляет семантически «публичные» имена из модуля. Если в __all__
есть имя, ожидается, что пользователи будут его использовать, и они могут ожидать, что оно не изменится.
Это также будет иметь программные эффекты:
import *
__all__
в модуле, например module.py
__all__ = ['foo', 'Bar']
означает, что когда вы import *
из модуля, импортируются только те имена в __all__
:
from module import * # imports foo and Bar
Документация инструментов
Инструменты документирования и автозаполнения кода могут (на самом деле, должны) также проверять __all__
, чтобы определить, какие имена показывать как доступные из модуля.
__init__.py
делает каталог пакетом Python
Из документов :
Файлы __init__.py
необходимы для того, чтобы Python рассматривал каталоги как содержащие пакеты; это сделано для предотвращения непреднамеренного скрытия действительными модулями каталогов с общим именем, например, строки, которые встречаются позже в пути поиска модулей.
В простейшем случае __init__.py
может быть просто пустым файлом, но он также может выполнить код инициализации для пакета или установить переменную __all__
.
Таким образом, __init__.py
может объявить __all__
для пакета .
Управление API:
Пакет обычно состоит из модулей, которые могут импортировать друг друга, но которые обязательно связаны вместе с файлом __init__.py
. Этот файл делает каталог настоящим пакетом Python. Например, скажем, у вас есть следующее:
package/
|-__init__.py # makes directory a Python package
|-module_1.py
|-module_2.py
в __init__.py
вы пишете:
from module_1 import *
from module_2 import *
и в module_1
у вас есть:
__all__ = ['foo',]
и в module_2
у вас есть:
__all__ = ['Bar',]
И теперь вы представили полный API, который кто-то другой может использовать при импорте вашего пакета, например:
import package
package.foo()
package.Bar()
И у них не будет всех других имен, которые вы использовали при создании ваших модулей, загромождающих пространство имен package
.
__all__
in __init__.py
После дополнительной работы, возможно, вы решили, что модули слишком велики и их нужно разделить. Итак, вы делаете следующее:
package/
|-__init__.py
|-module_1/
| |-__init__.py
| |-foo_implementation.py
|-module_2/
|-__init__.py
|-Bar_implementation.py
И в каждом __init__.py
вы объявляете __all__
, например, в модуле_1:
from foo_implementation import *
__all__ = ['foo']
И модуль_2 __init__.py
:
from Bar_implementation import *
__all__ = ['Bar']
И вы можете легко добавлять вещи в свой API, которыми вы можете управлять на уровне подпакета, а не на уровне модуля подпакета. Если вы хотите добавить новое имя в API, просто обновите __init__.py
, например, в модуле_2:
from Bar_implementation import *
from Baz_implementation import *
__all__ = ['Bar', 'Baz']
А если вы не готовы опубликовать Baz
в API верхнего уровня, на верхнем уровне __init__.py
вы можете получить:
from module_1 import * # also constrained by __all__'s
from module_2 import * # in the __init__.py's
__all__ = ['foo', 'Bar'] # further constraining the names advertised
и если ваши пользователи знают о доступности Baz
, они могут использовать его:
import package
package.Baz()
но если они не знают об этом, другие инструменты (например, pydoc ) не сообщат им.
Позже вы можете изменить это, когда Baz
готов к прайм-тайму:
from module_1 import *
from module_2 import *
__all__ = ['foo', 'Bar', 'Baz']
Префикс _
против __all__
:
По умолчанию Python экспортирует все имена, которые не начинаются с _
. Вы, конечно, могли бы полагаться на этот механизм. Некоторые пакеты в стандартной библиотеке Python, фактически, do полагаются на это, но для этого они псевдонимы импортируют, например, в ctypes/__init__.py
:
import os as _os, sys as _sys
Использование соглашения _
может быть более изящным, поскольку оно устраняет избыточность именования имен снова. Но это добавляет избыточность для импорта (если у вас их много), и легко забыть сделать это последовательно - и последнее, что вы хотите, - это иметь неограниченную поддержку того, что вы намеревались только быть подробностью реализации только потому, что вы забыли поставить префикс _
при именовании функции.
Я лично пишу __all__
в начале своего жизненного цикла разработки для модулей, чтобы другие, кто мог использовать мой код, знали, что они должны использовать, а не использовать.
Большинство пакетов в стандартной библиотеке также используют __all__
.
При избегании __all__
имеет смысл
Имеет смысл придерживаться соглашения префикса _
вместо __all__
, когда:
- Вы все еще находитесь в режиме ранней разработки, у вас нет пользователей, и вы постоянно настраиваете свой API.
- Возможно, у вас есть пользователи, но у вас есть юнит-тесты, которые охватывают API, и вы все еще активно добавляете API и вносите изменения в разработку.
An export
Декоратор
Недостаток использования __all__
заключается в том, что вам нужно писать имена функций и классов, которые экспортируются дважды, а информация хранится отдельно от определений. Мы могли бы использовать декоратор для решения этой проблемы.
Идея такого экспортного декоратора пришла из выступления Дэвида Бизли об упаковке. Эта реализация хорошо работает в традиционном импортере CPython. Если у вас есть специальный обработчик импорта или система, я не гарантирую это, но если вы примете его, отказаться от него довольно просто - вам просто нужно вручную добавить имена обратно в __all__
Так, например, в служебной библиотеке вы можете определить декоратор:
import sys
def export(fn):
mod = sys.modules[fn.__module__]
if hasattr(mod, '__all__'):
mod.__all__.append(fn.__name__)
else:
mod.__all__ = [fn.__name__]
return fn
и затем, где вы определяете __all__
, вы делаете это:
$ cat > main.py
from lib import export
__all__ = [] # optional - we create a list if __all__ is not there.
@export
def foo(): pass
@export
def bar():
'bar'
def main():
print('main')
if __name__ == '__main__':
main()
И это прекрасно работает независимо от того, запускается ли оно как основное или импортируется другой функцией.
$ cat > run.py
import main
main.main()
$ python run.py
main
И подготовка API с import *
тоже будет работать:
$ cat > run.py
from main import *
foo()
bar()
main() # expected to error here, not exported
$ python run.py
Traceback (most recent call last):
File "run.py", line 4, in <module>
main() # expected to error here, not exported
NameError: name 'main' is not defined