Что означает __all__ в Python?
Я вижу __all__
в __init__.py
файлах. Что это делает?
Переведено автоматически
Ответ 1
Ссылка на, но здесь явно не указано, это именно то, когда используется __all__
. Это список строк, определяющих, какие символы в модуле будут экспортированы, когда from <module> import *
используется в модуле.
Например, следующий код в a 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>
.
Ответ 2
Это список общедоступных объектов этого модуля, интерпретируемый import *
. Он переопределяет стандарт скрытия всего, что начинается с подчеркивания.
Ответ 3
Объясните все на 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:
A package is typically made up of modules that may import one another, but that are necessarily tied together with an __init__.py
file. That file is what makes the directory an actual Python package. For example, say you have the following files in a package:
package
├── __init__.py
├── module_1.py
└── module_2.py
Let's create these files with Python so you can follow along - you could paste the following into a Python 3 shell:
from pathlib import Path
package = Path('package')
package.mkdir()
(package / '__init__.py').write_text("""
from .module_1 import *
from .module_2 import *
""")
package_module_1 = package / 'module_1.py'
package_module_1.write_text("""
__all__ = ['foo']
imp_detail1 = imp_detail2 = imp_detail3 = None
def foo(): pass
""")
package_module_2 = package / 'module_2.py'
package_module_2.write_text("""
__all__ = ['Bar']
imp_detail1 = imp_detail2 = imp_detail3 = None
class Bar: pass
""")
And now you have presented a complete api that someone else can use when they import your package, like so:
import package
package.foo()
package.Bar()
And the package won't have all the other implementation details you used when creating your modules cluttering up the package
namespace.
__all__
in __init__.py
После дополнительной работы, возможно, вы решили, что модули слишком большие (например, многие тысячи строк?) и их нужно разделить. Итак, вы делаете следующее:
package
├── __init__.py
├── module_1
│ ├── foo_implementation.py
│ └── __init__.py
└── module_2
├── Bar_implementation.py
└── __init__.py
Сначала создайте каталоги подпакетов с теми же именами, что и у модулей:
subpackage_1 = package / 'module_1'
subpackage_1.mkdir()
subpackage_2 = package / 'module_2'
subpackage_2.mkdir()
Переместите реализации:
package_module_1.rename(subpackage_1 / 'foo_implementation.py')
package_module_2.rename(subpackage_2 / 'Bar_implementation.py')
создайте __init__.py
s для подпакетов, которые объявляют __all__
для каждого:
(subpackage_1 / '__init__.py').write_text("""
from .foo_implementation import *
__all__ = ['foo']
""")
(subpackage_2 / '__init__.py').write_text("""
from .Bar_implementation import *
__all__ = ['Bar']
""")
И теперь у вас все еще есть api, подготовленный на уровне пакета:
>>> import package
>>> package.foo()
>>> package.Bar()
<package.module_2.Bar_implementation.Bar object at 0x7f0c2349d210>
И вы можете легко добавлять в свой API элементы, которыми вы можете управлять на уровне подпакета, а не на уровне модуля подпакета. Если вы хотите добавить новое имя в API, вы просто обновляете __init__.py
, например, в module_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 экспортирует все имена, которые не начинаются с _
при импорте с помощью import *
. Как продемонстрировал здесь сеанс командной строки, import *
не вводит _us_non_public
имя из us.py
модуля:
$ cat us.py
USALLCAPS = "all caps"
us_snake_case = "snake_case"
_us_non_public = "shouldn't import"
$ python
Python 3.10.0 (default, Oct 4 2021, 17:55:55) [GCC 10.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from us import *
>>> dir()
['USALLCAPS', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'us_snake_case']
Вы, конечно, могли бы положиться на этот механизм. Некоторые пакеты в стандартной библиотеке Python, на самом деле, действительно полагаются на это, но для этого они используют псевдонимы для своего импорта, например, в ctypes/__init__.py
:
import os as _os, sys as _sys
Использование _
соглашения может быть более элегантным, потому что оно устраняет избыточность повторного присвоения имен. Но это добавляет избыточность для импорта (если у вас их много), и легко забыть делать это последовательно - и последнее, чего вы хотите, это бесконечно поддерживать то, что вы намеревались сделать всего лишь деталью реализации, только потому, что вы забыли поставить префикс _
при именовании функции.
Я лично пишу __all__
на ранних этапах своего жизненного цикла разработки для модулей, чтобы другие, кто может использовать мой код, знали, что им следует использовать, а что нет.
Большинство пакетов стандартной библиотеки также используют __all__
.
Когда имеет смысл избегать __all__
Имеет смысл придерживаться _
соглашения о префиксе вместо __all__
когда:
- Вы все еще находитесь в режиме ранней разработки, у вас нет пользователей, и вы постоянно дорабатываете свой API.
- Возможно, у вас действительно есть пользователи, но у вас есть unittests, которые охватывают 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()
И это прекрасно работает независимо от того, выполняется ли как 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
Ответ 4
Я просто добавляю это для точности:
Все остальные ответы относятся к модулям. Исходный вопрос явно упоминался __all__
в __init__.py
файлах, так что речь идет о пакетах python.
Как правило, __all__
вступает в игру только тогда, когда используется from xxx import *
вариант import
инструкции. Это относится как к пакетам, так и к модулям.
Поведение модулей объясняется в других ответах. Точное поведение пакетов подробно описано здесь.
Короче говоря, __all__
на уровне пакета выполняет примерно то же самое, что и для модулей, за исключением того, что имеет дело с модулями внутри пакета (в отличие от указания имен внутри модуля). Итак , __all__
определяет все модули, которые должны быть загружены и импортированы в текущее пространство имен при использовании from package import *
.
Большая разница в том, что когда вы опускаете объявление __all__
в пакете __init__.py
, оператор from package import *
вообще ничего не импортирует (за исключениями, описанными в документации, см. Ссылку выше).
С другой стороны, если вы опустите __all__
в модуле, "звездчатый импорт" импортирует все имена (не начинающиеся с подчеркивания), определенные в модуле.