Add an __exclude_all__ complement to __all__
Hi, Python has an __all__ variable that can be defined in a module to restrict which members of the module should be included in a call `from foo import *`. However specifying which members of the module should be excluded is more difficult. There are three workarounds that I see: 1) Defining __all__ to specify most of the members of the module, except the few that should be excluded. 2) Naming members to be excluded with a leading underscore. e.g. as done here in ctypes: https://github.com/python/cpython/blob/master/Lib/ctypes/__init__.py#L3 3) Manually deleting members from the module. e.g. as done here in numpy: https://github.com/numpy/numpy/blob/master/numpy/core/__init__.py#L52 I think it might be cleaner to also allow an __exclude_all__ variable which will exclude some members from being imported. If both were defined I would imagine __exclude_all__ being applied second. Best, George Harding
On 3/3/21 12:55 PM, George Harding wrote:
Python has an __all__ variable that can be defined in a module to restrict which members of the module should be included in a call `from foo import *`.
The primary purpose these days for `__all__` is to codify a module's API. The *-import is just a happy accident.
However specifying which members of the module should be excluded is more difficult.
And unnecessary -- specify `__all__` so your users know which classes, functions, variables, etc., can be safely used. If it should be excluded, don't put it in `__all__`. -- ~Ethan~
On Thu, Mar 4, 2021 at 7:57 AM George Harding
I think it might be cleaner to also allow an __exclude_all__ variable which will exclude some members from being imported.
If both were defined I would imagine __exclude_all__ being applied second.
You could implement that yourself: __all__ = {n for n in globals() if not n.startswith("_")} - __exclude_all__ ChrisA
On 2021-03-03 14:06, Chris Angelico wrote:
You could implement that yourself:
__all__ = {n for n in globals() if not n.startswith("_")} - __exclude_all__
Sort of. That will only work if you define __all__ at the end of the file. But usually you want to define it at the beginning as a sort of documentation aid ("this is the public API"). I do think something like __exclude_all__ would be handy. It can be annoying to have to define __all__ just to exclude a few things. But it's not a huge issue. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown
Hi Ethan,
I'm not convinced that __all__ is responsible for codifying the api. The
contents of __all__, does not stop anyone from importing anything in the
module using `from foo import bar`. __all__ is specified in the tutorial as
being included for the `from foo import *` case:
https://docs.python.org/3/tutorial/modules.html
And similar the first result for __all__ on google, is a stack overflow
post stating that it is for from foo import *:
https://stackoverflow.com/questions/44834/can-someone-explain-all-in-python
Best,
George
On Wed, Mar 3, 2021 at 9:47 PM Ethan Furman
On 3/3/21 12:55 PM, George Harding wrote:
Python has an __all__ variable that can be defined in a module to restrict which members of the module should be included in a call `from foo import *`.
The primary purpose these days for `__all__` is to codify a module's API. The *-import is just a happy accident.
However specifying which members of the module should be excluded is more difficult.
And unnecessary -- specify `__all__` so your users know which classes, functions, variables, etc., can be safely used. If it should be excluded, don't put it in `__all__`.
-- ~Ethan~ _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/EPNXFK... Code of Conduct: http://python.org/psf/codeofconduct/
Hi Ethan, I'm sorry, I take that back, that convention was codified in PEP8. https://www.python.org/dev/peps/pep-0008/#id50 Best, George On Wed, Mar 3, 2021 at 10:18 PM George Harding < george.winton.harding@gmail.com> wrote:
Hi Ethan,
I'm not convinced that __all__ is responsible for codifying the api. The contents of __all__, does not stop anyone from importing anything in the module using `from foo import bar`. __all__ is specified in the tutorial as being included for the `from foo import *` case:
https://docs.python.org/3/tutorial/modules.html
And similar the first result for __all__ on google, is a stack overflow post stating that it is for from foo import *:
https://stackoverflow.com/questions/44834/can-someone-explain-all-in-python
Best,
George
On Wed, Mar 3, 2021 at 9:47 PM Ethan Furman
wrote: On 3/3/21 12:55 PM, George Harding wrote:
Python has an __all__ variable that can be defined in a module to restrict which members of the module should be included in a call `from foo import *`.
The primary purpose these days for `__all__` is to codify a module's API. The *-import is just a happy accident.
However specifying which members of the module should be excluded is more difficult.
And unnecessary -- specify `__all__` so your users know which classes, functions, variables, etc., can be safely used. If it should be excluded, don't put it in `__all__`.
-- ~Ethan~ _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/EPNXFK... Code of Conduct: http://python.org/psf/codeofconduct/
On Thu, Mar 4, 2021 at 9:57 AM Brendan Barnwell
On 2021-03-03 14:06, Chris Angelico wrote:
You could implement that yourself:
__all__ = {n for n in globals() if not n.startswith("_")} - __exclude_all__
Sort of. That will only work if you define __all__ at the end of the file. But usually you want to define it at the beginning as a sort of documentation aid ("this is the public API").
Given that it's meant to be program-readable, that shouldn't be too big a deal. You can always put your human-readable public API description in the docstring (where there's room for actual descriptions, not just a list of names).
I do think something like __exclude_all__ would be handy. It can be annoying to have to define __all__ just to exclude a few things. But it's not a huge issue.
We are programmers. When there is a problem, we (should!) seek a solution that involves getting the computer to do the work for us. ChrisA
On 3/3/21 2:17 PM, Brendan Barnwell wrote:
[...] usually you want to define [__all__] at the beginning as a sort of documentation aid ("this is the public API").
That is its purpose. :-)
I do think something like __exclude_all__ would be handy. It can be annoying to have to define __all__ just to exclude a few things.
Exclude a few things from what? *-imports? If you're using *-imports that often you are already courting confusion (where did that name come from? why is this name not that function?). I maintain a handful of libraries, all with `__all__`, and only one them is designed to be used with `from ... import *`. The presence or absence of `__all__` has no bearing on named imports. `from blahblah import _xyz` will work in either case. -- ~Ethan~
If you are using __all__ to define your API, then you'll end up needing `from foo import *` if you want to combine modules, e.g.: https://github.com/python/cpython/blob/master/Lib/asyncio/__init__.py So the two need to coexist. *-import does have valid uses. The majority of these cases are within a library, rather than between libraries. Best, George
On 3/3/21 3:38 PM, George Harding wrote:
If you are using __all__ to define your API, then you'll end up needing `from foo import *` if you want to combine modules, e.g.:
https://github.com/python/cpython/blob/master/Lib/asyncio/__init__.py
Absolutely!
So the two need to coexist. *-import does have valid uses.
Agreed. -- ~Ethan~
On 3/3/21 2:18 PM, George Harding wrote:
__all__ does not stop anyone from importing anything in the module using `from foo import bar`.
That is true, but that is also Python. If you import something not included in `__all__` then you do so at your own risk as it could change or vanish in the next release. -- ~Ethan~
Why not just __exclude__ or __excluding__? On Wed, Mar 3, 2021 at 2:57 PM George Harding < george.winton.harding@gmail.com> wrote:
Hi,
Python has an __all__ variable that can be defined in a module to restrict which members of the module should be included in a call `from foo import *`. However specifying which members of the module should be excluded is more difficult. There are three workarounds that I see:
1) Defining __all__ to specify most of the members of the module, except the few that should be excluded.
2) Naming members to be excluded with a leading underscore. e.g. as done here in ctypes: https://github.com/python/cpython/blob/master/Lib/ctypes/__init__.py#L3
3) Manually deleting members from the module. e.g. as done here in numpy: https://github.com/numpy/numpy/blob/master/numpy/core/__init__.py#L52
I think it might be cleaner to also allow an __exclude_all__ variable which will exclude some members from being imported.
If both were defined I would imagine __exclude_all__ being applied second.
Best,
George Harding
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/YMP7WQ... Code of Conduct: http://python.org/psf/codeofconduct/
On Wed, 3 Mar 2021 at 23:59, Brendan Barnwell
But usually you want to define it at the beginning as a sort of documentation aid ("this is the public API").
This is a little off-topic, but I'm curious, since usually, for public functions and classes, I do __all__ = (Class.__name__, func.__name__, ...) So I have to put it at the end of the module. I do this because if I change the class or function name and I forget to change it in __all__, I get an exception. Furthermore, if there's a module composed by submodules, I usually do from .a import * from .b import * __all__ = a.__all__ + b.__all__ In your opinion, these are good or bad practices?
__all__ = (Class.__name__, func.__name__, ...)
So I have to put it at the end of the module. I do this because if I change the class or function name and I forget to change it in __all__, I get an exception.
I certainly will not claim to be the arbitrator of good and bad practices but that seems reasonable enough. It is worth pointing out that it's pretty easy to unit test `__all__` ``` # module/__init__.py __all__ = 'Foo', 'Bar' class Foo: pass ``` ``` # tests/test_imports.py def test_star(): # will raise `AttributeError: module 'module' has not attribute 'Bar'` from module import * ```
from .a import * from .b import *
__all__ = a.__all__ + b.__all__
Assuming you mean:
```
from . import a
from . import b
from .a import *
from .b import *
__all__ = a.__all__ + b.__all__
```
It is fairly unnecessary if you aren't adding more names. but definitely
isn't harmful.
On Fri, Mar 5, 2021 at 12:11 PM Marco Sulla
On Wed, 3 Mar 2021 at 23:59, Brendan Barnwell
wrote: But usually you want to define it at the beginning as a sort of documentation aid ("this is the public API").
This is a little off-topic, but I'm curious, since usually, for public functions and classes, I do
__all__ = (Class.__name__, func.__name__, ...)
So I have to put it at the end of the module. I do this because if I change the class or function name and I forget to change it in __all__, I get an exception.
Furthermore, if there's a module composed by submodules, I usually do
from .a import * from .b import *
__all__ = a.__all__ + b.__all__
In your opinion, these are good or bad practices? _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/AZLCRX... Code of Conduct: http://python.org/psf/codeofconduct/
On 3/5/21 12:41 PM, Caleb Donovick wrote:
__all__ = (Class.__name__, func.__name__, ...)
So I have to put it at the end of the module. I do this because if I change the class or function name and I forget to change it in __all__, I get an exception.
I certainly will not claim to be the arbitrator of good and bad practices but that seems reasonable enough.
It is worth pointing out that it's pretty easy to unit test `__all__`
``` # module/__init__.py __all__ = 'Foo', 'Bar'
class Foo: pass ```
``` # tests/test_imports.py def test_star(): # will raise `AttributeError: module 'module' has not attribute 'Bar'` from module import * ```
from .a import * from .b import *
__all__ = a.__all__ + b.__all__
Assuming you mean: ``` from . import a from . import b from .a import * from .b import * __all__ = a.__all__ + b.__all__ ```
Actually, Marko's version works, and is the same style used by asyncio. Apparently, from .a import * also adds `a` to the module's namespace. -- ~Ethan~
On Sat, Mar 6, 2021 at 9:47 AM Ethan Furman
On 3/5/21 12:41 PM, Caleb Donovick wrote:
__all__ = (Class.__name__, func.__name__, ...)
So I have to put it at the end of the module. I do this because if I change the class or function name and I forget to change it in __all__, I get an exception.
I certainly will not claim to be the arbitrator of good and bad practices but that seems reasonable enough.
It is worth pointing out that it's pretty easy to unit test `__all__`
``` # module/__init__.py __all__ = 'Foo', 'Bar'
class Foo: pass ```
``` # tests/test_imports.py def test_star(): # will raise `AttributeError: module 'module' has not attribute 'Bar'` from module import * ```
from .a import * from .b import *
__all__ = a.__all__ + b.__all__
Assuming you mean: ``` from . import a from . import b from .a import * from .b import * __all__ = a.__all__ + b.__all__ ```
Actually, Marko's version works, and is the same style used by asyncio. Apparently,
from .a import *
also adds `a` to the module's namespace.
My guess is that that works only because the module is a package? ChrisA
participants (7)
-
Abe Dillon
-
Brendan Barnwell
-
Caleb Donovick
-
Chris Angelico
-
Ethan Furman
-
George Harding
-
Marco Sulla