From a practical standpoint, it's somewhat common to add elements to __all__
semi-dynamically. It happens when you want to expose functions defined deep in the package structure at the top level. This is much easier to do with a list.
A couple of examples of modules that do this are numpy and pyserial. I strongly suspect that Django does this too in places, but am not familiar enough with it to know for sure.
The idiom looks something like this in __init__.py
:
__all__ = [] # or might have some initial names
from .subpackage import (name1, name2, name3)
__all__.extend(['name1', 'name2', 'name3']) # or append 1-by-1 or +=
I've even seen a slightly sloppier approach, although arguably more maintainable under certain circumstances, that looks like this:
__all__ = []
from .subpackage import *
from .subpackage import __all__ as _all
__all__.extend(_all)
del _all
Clearly this is greatly simplified by having a mutable __all__
. There is no substantial benefit to turning it into a tuple after the fact or "appending" to a tuple using +=
.
Another way a mutable __all__
is useful is when your API depends on optional external packages. It's much easier to enable or disable names in a list than a tuple.
Here is an example of a module that enables additional functionality if a library called optional_dependency
is installed:
# Core API
__all__ = ['name', 'name2', 'name3']
from .sub1 import name1
from .sub2 import name2, name3
try:
import optional_dependency
except ImportError:
# Let it be, it maybe issue a warning
pass
else:
from .opt_support import name4, name5
__all__ += ['name4', 'name5']
__all__
is practically insignificant for program runtime. It is only relevant for*
-imports to begin with, which are discouraged via PEP 8. – Beauforttuple
for any extremely slight performance increase. Tuples are used for "record-like" data – Permanencyset
cannot be used for__all__
. – Beaufort__all__
dynamically a lot of the time. – Hereunder