__all__ attribute: bug and proposal

Steven D'Aprano steve at pearwood.info
Mon Jun 27 21:56:29 EDT 2016

On Tue, 28 Jun 2016 06:56 am, Pavel S wrote:

> Hi,
> I today uncovered subtle bug and would like to share it with you.
> By a mistake, I forgot to put comma into '__all__' tuple of some module.
> Notice missing comma after 'B'.
> # module foo.py
> __all__ = (
>     'A',
>     'B'
>     'C',
> )

Right. That's a language feature: implicit concatenation of string literals.
If you have two string literals separated by nothing but whitespace, the
compiler will concatenate them:

s = 'He said, "Hello, did you see where' " they're" ' going?"'

is equivalent to:

s = 'He said, "Hello, did you see where they\'re going?"'

> If you try to import * from the module, it will raise an error, because
> 'B' and 'C' will be concatenated into 'BC'.

Correct. Exactly the same as if you had written:

__all__ = ['A', 'BC', 'D']

> The bug won't be found until someone imports *.

I always write a unit-test to check that everything in __all__ exists.

> In order to identify problems as soon as possible, here's the proposal.
> Porposal: allow putting objects into __all__ directly, so possible
> problems will be found earlier:
> # module foo.py
> class A: pass
> class B: pass
> class C: pass
> __all__ = (A, B, C)

There are two problems with this idea.

Suppose you have this:

prefs_file = "filename"
__all__ = (prefs_file, A, B, C)  # suppose A, B and C are all defined

Obviously, the intention is that the caller can say:

from themodule import *

and "filename" will be printed. But there's two problems:

(1) the __all__ tuple doesn't know anything about the name "prefs_file". It
only knows about "filename", the object (value) of prefs_file. So there is
no way for the import system to know what name to use for that value:

????? = "filename"

(2) For backwards-compatibility, we still need to support the old way of
writing __all__, using names given as strings:

__all__ = ('prefs_file', 'A', 'B', 'C')

But now you have an ambiguity. If __all__ looks like this:

__all__ = (prefs_file, A, B, C)

does the first entry mean "import the value 'filename'" (new behaviour), or
does it mean "import the value with the name 'filename'" (old behaviour)?

“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.

More information about the Python-list mailing list