Может ли кто-нибудь объяснить __all__ на Python?
-
09-06-2019 - |
Вопрос
Я использую Python все больше и больше и продолжаю видеть переменную __all__
установить в разные __init__.py
файлы.Может кто-нибудь объяснить, что это делает?
Решение
Это список общедоступных объектов этого модуля, как его интерпретирует import *
.Он отменяет настройку по умолчанию, скрывающую все, что начинается с подчеркивания.
Другие советы
С этим связан, но не упомянут здесь явно, именно тот момент, когда __all__
используется.Это список строк, определяющих, какие символы в модуле будут экспортированы при from <module> import *
используется в модуле.
Например, следующий код в foo.py
явно экспортирует символы bar
и baz
:
__all__ = ['bar', 'baz']
waz = 5
bar = 10
def baz(): return 'baz'
Затем эти символы можно импортировать следующим образом:
from foo import *
print(bar)
print(baz)
# The following will trigger an exception, as "waz" is not exported by the module
print(waz)
Если __all__
выше закомментировано, этот код будет выполнен до завершения, как поведение по умолчанию import *
заключается в импорте всех символов, которые не начинаются с подчеркивания, из данного пространства имен.
Ссылка: https://docs.python.org/tutorial/modules.html#importing-from-a-package
ПРИМЕЧАНИЕ: __all__
влияет на from <module> import *
только поведение.Члены, не упомянутые в __all__
по-прежнему доступны извне модуля и могут быть импортированы с помощью from <module> import <member>
.
Я просто добавляю это, чтобы быть точным:
Все остальные ответы относятся к модули.Исходный вопрос явно упомянут __all__
в __init__.py
файлы, так что речь идет о Python пакеты.
В целом, __all__
вступает в игру только тогда, когда from xxx import *
вариант import
используется заявление.Это относится как к пакетам, так и к модулям.
Поведение модулей объясняется в других ответах.Точное поведение пакетов описано. здесь в деталях.
Суммируя, __all__
на уровне пакета делает примерно то же самое, что и для модулей, за исключением того, что он касается модули в комплекте (в отличие от указания имена внутри модуля).Так __all__
указывает все модули, которые должны быть загружены и импортированы в текущее пространство имен при использовании from package import *
.
Большая разница в том, что когда вы пропускать декларация __all__
в упаковке __init__.py
, заявление from package import *
вообще ничего не импортирует (за исключениями, описанными в документации, см. ссылку выше).
С другой стороны, если вы опустите __all__
в модуле «помеченный импорт» импортирует все имена (не начинающиеся с подчеркивания), определенные в модуле.
Объясните __all__ на Python?
Я продолжаю видеть переменную
__all__
установить в разные__init__.py
файлы.Что это делает?
Что значит __all__
делать?
Он объявляет семантически «публичные» имена модуля.Если есть имя в __all__
, Ожидается, что пользователи будут использовать его, и они могут рассчитывать на то, что он не изменится.
Это также будет иметь программные последствия:
import *
__all__
в модуле, например. module.py
:
__all__ = ['foo', 'Bar']
означает, что когда ты import *
из модуля, только те имена в __all__
импортируются:
from module import * # imports foo and Bar
Инструменты документирования
Инструменты документирования и автодополнения кода также могут (фактически должны) проверять __all__
чтобы определить, какие имена показывать как доступные в модуле.
__init__.py
делает каталог пакетом Python
Из документы:
А
__init__.py
файлы необходимы для того, чтобы Python рассматривал каталоги как содержащие пакеты;это сделано для того, чтобы каталоги с общим именем, например string, непреднамеренно скрывали действительные модули, которые встречаются позже на пути поиска модулей.В простейшем случае
__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__
в __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()
но если они об этом не знают, другие инструменты (например, пидок) не сообщит им.
Вы можете позже изменить это, когда Baz
готов к прайм-тайму:
from module_1 import *
from module_2 import *
__all__ = ['foo', 'Bar', 'Baz']
Префикс _
против __all__
:
По умолчанию Python экспортирует все имена, которые не начинаются с буквы. _
.Вы, конечно, мог полагаться на этот механизм.Фактически, некоторые пакеты стандартной библиотеки Python делать полагаются на это, но для этого они создают псевдонимы для своего импорта, например, в ctypes/__init__.py
:
import os as _os, sys as _sys
Используя _
Соглашение может быть более элегантным, поскольку оно устраняет избыточность повторного присвоения имен.Но это добавляет избыточность импорта (если его у вас много), и это легкий забывать делать это последовательно - и последнее, чего вы хотите, - это иметь возможность бесконечно поддерживать то, что вы намеревались использовать только как деталь реализации, только потому, что вы забыли поставить префикс _
при названии функции.
Я лично пишу __all__
на ранних стадиях жизненного цикла разработки модулей, чтобы другие, кто может использовать мой код, знали, что им следует использовать, а что нет.
Большинство пакетов стандартной библиотеки также используют __all__
.
Избегая __all__
имеет смысл
Имеет смысл придерживаться _
соглашение о префиксах вместо __all__
когда:
- Вы все еще находитесь на ранней стадии разработки, у вас нет пользователей, и вы постоянно настраиваете свой API.
- Возможно, у вас есть пользователи, но есть юнит-тесты, охватывающие API, и вы все еще активно добавляете API и вносите изменения в разработку.
Ан 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
Это также меняет то, что будет показывать pydoc:
модуль1.py
a = "A"
b = "B"
c = "C"
модуль2.py
__all__ = ['a', 'b']
a = "A"
b = "B"
c = "C"
$ pydoc модуль1
Help on module module1: ИМЯ module1 ФАЙЛ module1.py ДАННЫЕ а = 'A' б = 'B' с = 'C'
$pydoc модуль2
Help on module module2: ИМЯ module2 ФАЙЛ module2.py ДАННЫЕ __все__ = ['a', 'b'] а = 'A' б = 'B'
Я заявляю __all__
во всех моих модулях, а также подчеркивают внутренние детали, они действительно помогают при использовании вещей, которые вы никогда раньше не использовали в сеансах живого устного перевода.
От (Неофициальная) Справочная вики по Python:
Публичные имена, определенные модулем, определяются путем проверки пространства имен модуля на наличие переменной с именем
__all__
;если определено, это должна быть последовательность строк, которые являются именами, определенными или импортированными этим модулем.Имена, данные в__all__
все они считаются общедоступными и обязаны существовать.Если__all__
не определен, набор общедоступных имен включает все имена, найденные в пространстве имен модуля, которые не начинаются с символа подчеркивания («_»).__all__
должен содержать весь общедоступный API.Он предназначен для предотвращения случайного экспорта элементов, которые не являются частью API (например, библиотечных модулей, которые были импортированы и использованы внутри модуля).
__all__
настраивает звездочка в from <module> import *
__all__
настраивает звездочка в from <package> import *
А модуль это .py
файл, предназначенный для импорта.
А упаковка это каталог с __init__.py
файл.Пакет обычно содержит модули.
МОДУЛИ
""" cheese.py - an example module """
__all__ = ['swiss', 'cheddar']
swiss = 4.99
cheddar = 3.99
gouda = 10.99
__all__
позволяет людям узнать «публичные» особенности модуль.[@АаронХолл] Кроме того, pydoc их распознает.[@Лонгпоук]
от модуль Импортировать *
Смотри как swiss
и cheddar
переносятся в локальное пространство имен, но не gouda
:
>>> from cheese import *
>>> swiss, cheddar
(4.99, 3.99)
>>> gouda
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'gouda' is not defined
Без __all__
, любой символ (который не начинается с подчеркивания) будет доступен.
Импорт без *
не подвержены влиянию __all__
Импортировать модуль
>>> import cheese
>>> cheese.swiss, cheese.cheddar, cheese.gouda
(4.99, 3.99, 10.99)
от модуль Импортировать имена
>>> from cheese import swiss, cheddar, gouda
>>> swiss, cheddar, gouda
(4.99, 3.99, 10.99)
Импортировать модуль как локальное имя
>>> import cheese as ch
>>> ch.swiss, ch.cheddar, ch.gouda
(4.99, 3.99, 10.99)
ПАКЕТЫ
в __init__.py
файл упаковка __all__
представляет собой список строк с именами общедоступных модулей или других объектов.Эти функции доступны для импорта с подстановочными знаками.Как и в случае с модулями, __all__
настраивает *
при импорте подстановочных знаков из пакета.[@МартинСтеттнер]
Вот отрывок из MySQL-коннектор Python __init__.py
:
__all__ = [
'MySQLConnection', 'Connect', 'custom_error_exception',
# Some useful constants
'FieldType', 'FieldFlag', 'ClientFlag', 'CharacterSet', 'RefreshOption',
'HAVE_CEXT',
# Error handling
'Error', 'Warning',
...etc...
]
Случай по умолчанию, звездочка без номера __all__
за пакет, сложен, потому что очевидное поведение будет дорогостоящим:использовать файловую систему для поиска всех модулей в пакете.Вместо этого, когда я читал документы, только объекты, определенные в __init__.py
импортируются:
Если
__all__
не определено, утверждениеfrom sound.effects import *
делает нет импортировать все подмодули из пакетаsound.effects
в текущее пространство имен;это только гарантирует, что пакетsound.effects
был импортирован (возможно, выполняется любой код инициализации в__init__.py
), а затем импортирует любые имена, определенные в пакете.Сюда входят любые имена, определенные (и явно загруженные подмодули) с помощью__init__.py
.Он также включает в себя все подмодули пакета, которые были явно загружены предыдущими операторами импорта.
Импорт подстановочных знаков...следует избегать, поскольку они [сбивают с толку] читателей и многие автоматизированные инструменты.
[ПКП 8, @ToolmakerSteve]
Короткий ответ
__all__
влияет from <module> import *
заявления.
Длинный ответ
Рассмотрим этот пример:
foo
├── bar.py
└── __init__.py
В foo/__init__.py
:
(Неявно) Если мы не определим
__all__
, затемfrom foo import *
будет импортировать только имена, определенные вfoo/__init__.py
.(Явный) Если мы определим
__all__ = []
, затемfrom foo import *
ничего не будет импортировать.(Явный) Если мы определим
__all__ = [ <name1>, ... ]
, затемfrom foo import *
будет импортировать только эти имена.
Обратите внимание, что в неявном случае Python не будет импортировать имена, начинающиеся с _
.Однако вы можете принудительно импортировать такие имена, используя __all__
.
Вы можете просмотреть документ Python здесь.
__all__
используется для документирования общедоступного API модуля Python.Хоть это и необязательно, __all__
должен быть использован.
Вот соответствующий отрывок из справочник по языку Python:
Публичные имена, определенные модулем, определяются путем проверки пространства имен модуля на наличие переменной с именем
__all__
;если определено, это должна быть последовательность строк, которые являются именами, определенными или импортированными этим модулем.Имена, данные в__all__
все они считаются общедоступными и обязаны существовать.Если__all__
не определен, набор общедоступных имен включает все имена, найденные в пространстве имен модуля, которые не начинаются с символа подчеркивания («_»).__all__
должен содержать весь общедоступный API.Он предназначен для предотвращения случайного экспорта элементов, которые не являются частью API (например, библиотечных модулей, которые были импортированы и использованы внутри модуля).
ПКП 8 использует аналогичную формулировку, хотя также ясно дает понять, что импортированные имена не являются частью общедоступного API, когда __all__
отсутствует:
Чтобы лучше поддерживать самоанализ, модули должны явно объявлять имена в своем общедоступном API, используя метод
__all__
атрибут.Параметр__all__
пустой список означает, что у модуля нет общедоступного API.[...]
Импортированные имена всегда следует рассматривать как деталь реализации.Другие модули не должны полагаться на косвенный доступ к таким импортированным именам, если только они не являются явно документированной частью API содержащего модуля, например:
os.path
или посылка__init__
модуль, который предоставляет функциональность подмодулей.
Кроме того, как указано в других ответах, __all__
используется для включения импорт подстановочных знаков для пакетов:
В операторе импорта используется следующее соглашение:если посылка
__init__.py
код определяет список с именем__all__
, это список имен модулей, которые должны быть импортированы приfrom package import *
встречается.
В дополнение к существующим ответам, __all__
не обязательно должен быть список.Согласно документации на import
заявление, если определено, __all__
должно быть последовательность строк которые представляют собой имена, определенные или импортированные модулем.Таким образом, вы также можете использовать кортеж для сохранять некоторые циклы памяти и процессора.Только не забудьте запятую, если модуль определяет одно публичное имя:
__all__ = ('some_name',)