python / cpython

The Python programming language
https://www.python.org
Other
63.35k stars 30.32k forks source link

improvements when documenting __all__ #106556

Open calestyo opened 1 year ago

calestyo commented 1 year ago

Documentation

1) Forgive me if I'm wrong (still being a Python noob ^^), but testing seems to show that the following is only half the truth: https://github.com/python/cpython/blob/da98ed0aa040791ef08b24befab697038c8c9fd5/Doc/tutorial/modules.rst?plain=1#L501-L504

This says __all__ would be module names only (well it doesn't explicitly say so, but neither does it mention anything else), but in fact (or at least from some poor man testing I've did), it first seems to take any "items" (functions, vars, etc.) defined inside the package itself (i.e. within __init__.py and only if that doesn't exist (for a name listed in __all__), it seems to try importing a module/subpackage thereof.

Especially when considering: https://github.com/python/cpython/blob/da98ed0aa040791ef08b24befab697038c8c9fd5/Doc/tutorial/modules.rst?plain=1#L474-L478 mentioned just above, where it's explicitly told that with from bla import * takes first the in-package items and only then modules/subpackages... it may seem to the reader as if __all__ would be different, and really just take modules (as it says right now).

2) Another thing, which could perhaps be improved is the case, when importing from a package, which contains a module/subpackage of the same name:
Consider e.g.: foo/__init__.py:

bar = 0
__all__ = ["foo"]

foo/foo.py:

bar = 1

and then using:

from foo import *

AFAIU, the "local" foo where foo.bar is 0 would be overwritten by foo.foo, thus foo.bar would be 1.

Now one might argue: package author's fault, if he writes stupid package/module names and __all__ but the above, I think, would even happen with e.g. __all__ = ["somethingElse"] when doing:

import foo.foo
from foo import *

as explained here: https://github.com/python/cpython/blob/da98ed0aa040791ef08b24befab697038c8c9fd5/Doc/tutorial/modules.rst?plain=1#L520-L531

Thanks, Chris.

terryjreedy commented 1 year ago

'module names' means names in the module, at the top level, as opposed to names in classes and functions in the module. In 'from module import submod' will import 'submod' even though 'submod' is not already a module name (name in the module). Unless others think the existing doc unclear, this should be closed.

calestyo commented 1 year ago

I guess one can read it like that... but I'd expect most people will understand "module names" as "the names of modules" and not "names at the top-level within modules". I personally would even understand a phrase like "a module's names" (as if the module itself could have more than one name) like that.

IMO that's especially emphasised as the example given along: https://github.com/python/cpython/blob/da98ed0aa040791ef08b24befab697038c8c9fd5/Doc/tutorial/modules.rst?plain=1#L510 uses only names that are names of modules.

calestyo commented 1 year ago

Just for the records... I found numerous stackoverflow Q&As, which seem to imply just the wrong understanding of __all__, i.e. that within __init__.py it would only refer to modules (not any names within __init__.py).

Take e.g. this one: https://stackoverflow.com/a/67143260 which even refers to the section of the tutorial I ways also referring to.

There was also another on which even had considerable upvotes... but I cannot find it anymore (had it on the smartphone and st**id Firefox there doesn't have a history).

terryjreedy commented 1 year ago

I agree now that the tutorial section is confusing.

calestyo commented 1 year ago

Maybe a way to improve it (i.e. explaining what happens when from foo import someName generally) is to rather follow the "The from form uses a slightly more complex process:" in: https://docs.python.org/3/reference/simple_stmts.html#import

That is by itself a bit confusing (at least for noobs ^^), but I'd interpret is as follows: 1st load foo 2nd look for the name someName inside foo 3rd if not found, then "from within" foo, try to import someName 4th repeat 2nd and if still nothing, raise 5th otherwise use whatever was found (first a name defined inside foo, which could again be a module, then the "automatically" imported module

AFAICS, this generally applies, regardless of whether its a module or a package.

Not sure how to best write this in simple words... maybe that from first tries to import an item (module, package, class, func, etc.) actually defined (includes imported) inside foo, and only if that yielded nothing, there's an auto import.