__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 *
print(prefs_file)

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)?




-- 
Steven
“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