__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