Using list instead of tuple in module __all__

Viewed 605

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?

4 Answers

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.

Related