In many large projects, even in such as Django, and in official Python documentation use list to list the "available from outside" module components in the __init__.py
file:
__all__ = [foo, bar, other]
However, the record
__all__ = (foo, bar, other)
it will also work, and in theory it does not give a significant, but an increase in code performance.
Why, then use it to list?
Maybe there is some magic PEP that I don't know about?
It's a list of public objects of that module, as interpreted by import * . It overrides the default of hiding everything that begins with an underscore.
Tuples are more memory efficient than the lists. When it comes to the time efficiency, again tuples have a slight advantage over the lists especially when lookup to a value is considered. If you have data which is not meant to be changed in the first place, you should choose tuple data type over lists.
Once a tuple is created, you cannot change its values. Tuples are unchangeable, or immutable as it also is called. But there is a workaround. You can convert the tuple into a list, change the list, and convert the list back into a tuple.
We can create a list of tuples i.e. the elements of the tuple can be enclosed in a list and thus will follow the characteristics in a similar manner as of a Python list. Since, Python Tuples utilize less amount of space, creating a list of tuples would be more useful in every aspect.
Tuples are commonly used as the equivalent of a dictionary without keys to store data. For Example, Above example contains tuples inside list which has a list of movies.
Lists are surrounded by square brackets [] and Tuples are surrounded by parenthesis (). Above, we defined a variable called list_num which hold a list of numbers from 1 to 4 .The list is surrounded by brackets []. Also, we defined a variable tup_num; which contains a tuple of number from 1 to 4. The tuple is surrounded by parenthesis ().
The key difference between the tuples and lists is that while the tuples are immutable objects the lists are mutable. This means that tuples cannot be changed while the lists can be modified. Tuples are more memory efficient than the lists.
We can clearly see that, there are so many additional functionalities associated with a list over a tuple.We can do insert and pop operation, removing and sorting elements in the list with inbuilt functions which is not available in Tuple.
There is no binding reason to use either list
or tuple
. However, list
idiomatically represents a sequence of same kind of items but tuple
represents a sequence of different kind of items. This is also encoded by type hints, which have some list[str | int]
but positional fields inside tuple[str, int, int]
.
As such, list
more accurately represents "arbitrary sequence of names".
PEP 8 repeatedly makes mention of __all__
being a list:
To better support introspection, modules should explicitly declare the names in their public API using the
__all__
attribute. Setting__all__
to an empty list indicates that the module has no public API.
"""This is the example module. This module does stuff. """ ... __all__ = ['a', 'b', 'c']
The language reference says:
The public names defined by a module are determined by checking the module’s namespace for a variable named
__all__
; if defined, it must be a sequence of strings which are names defined or imported by that module.
A sequence is something that supports iteration (for x in __all__
) and access using integer indices (__all__[i]
). So it can be a list, or a tuple.
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']
Just wanted to document a little error I ran into relevant to this post: note that you need a trailing comma to create a single element tuple. So this:
__all__ = ['foo'] # I am list with one element
Is not the same as this:
__all__ = ('foo') # I am a string
Here's an example of this going wrong. In the second case, if you try to import with the wildcard*:
from mymodule import *
You get the confusing error:
AttributeError: module 'mypackage.mymodule' has no attribute 'f'
What is 'f'
?!? Well, it is the first element of __all__
, which is pointing to the string 'foo'
, not the single-element tuple ('foo',)
.
* Using from x import *
is maybe what is more to blame here, as opposed to the tuple vs. list choice. But this still seems to be a relatively common pattern in __init__.py
files, and makes me lean towards preferring lists.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With