Вопрос-Ответ

What does __all__ mean in Python?

Что означает __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__.pys для подпакетов, которые объявляют __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__ в модуле, "звездчатый импорт" импортирует все имена (не начинающиеся с подчеркивания), определенные в модуле.

2023-08-17 23:33 python