-1

I want my_module to export __all__ as empty list, i.e.

from my_module import *
assert '__all__' in dir() and __all__ == []

I can export __all__ like this (in 'my_module.py'):

__all__ = ['__all__']

However it predictably binds __all__ to itself , so that

from my_module import *
assert '__all__' in dir() and __all__ == ['__all__']

How can I export __all__ as an empty list? Failing that, how can I hook into import process to put __all__ into importing module's __dict__ on every top level import my_module statement, circumventing module caching.

yuri kilochek
  • 12,709
  • 2
  • 32
  • 59
  • **Why in heavens name** would you want to do that? `__all__` is metadata about `my_module`, not about the module into which you are importing this. – Martijn Pieters Sep 05 '14 at 14:10
  • Really, this sounds like a terrible idea. What's wrong with putting `__all__ = ()` into your modules instead? – Martijn Pieters Sep 05 '14 at 14:11
  • @MartijnPieters, I want to impelment a decorator for explicitly exporting entities like [this](http://stackoverflow.com/questions/6206089/is-it-a-good-practice-to-add-names-to-all-using-a-decorator). The guy there sets `__all__` on first decorator application, so not explicitly exporting anything causes it to implicitly export everything. I am trying to improve on this design: if the decorator is imported, then export nothing by default, regardless of it's usage. – yuri kilochek Sep 05 '14 at 14:20
  • 1
    You cannot do what you want, not with hacking `__all__` in any case. I'd go as far as saying that such side effects are quite un-pythonic; *explicit is better than implicit* here. If you want to make everything private, the module *author* should set `__all__ = []` explicitly. – Martijn Pieters Sep 05 '14 at 14:24
  • This is all quite apart from other objections that apply here: using `from module import *` goes against the PEP 8 style guide and use of the decorator in that post makes using static code analysers (such as auto-completion tools) at best a lot more difficult. – Martijn Pieters Sep 05 '14 at 14:36

2 Answers2

3

I'll start with saying this is, in my mind, a terrible idea. You really should not implicitly alter what is exported from a module, this goes counter to the Zen of Python: Explicit is better than implicit..

I also agree with the highest-voted answer on the question you cite; Python already has a mechanism to mark functions 'private', by convention we use a leading underscore to indicate a function should not be considered part of the module API. This approach works with existing tools, vs. the decorator dynamically setting __all__ which certainly breaks static code analysers.

That out of the way, here is a shotgun pointing at your foot. Use it with care.


What you want here is a way to detect when names are imported. You cannot normally do this; there are no hooks for import statements. Once a module has been imported from source, a module object is added to sys.modules and re-used for subsequent imports, but that object is not notified of imports.

What you can do is hook into attribute access. Not with the default module object, but you can stuff any object into sys.modules and it'll be treated as a module. You could just subclass the module type even, then add a __getattribute__ method to that. It'll be called when importing any name with from module import name, for all names listed in __all__ when using from module import *, and in Python 3, __spec__ is accessed for all import forms, even when doing just import module.

You can then use this to hack your way into the calling frame globals, via sys._getframe():

import sys
import types

class AttributeAccessHookModule(types.ModuleType):
    def __getattribute__(self, name):
        if name == '__all__':
            # assume we are being imported with from module import *
            g = sys._getframe(1).f_globals
            if '__all__' not in g:
                g['__all__'] = []
        return super(AttributeAccessHook, self).__getattribute__(name)

# replace *this* module with our hacked-up version
# this part goes at the *end* of your module.
replacement = sys.module[__name__] = AttributeAccessHook(__name__, __doc__)
for name, obj in globals().items():
    setattr(replacement, name, obj)
Community
  • 1
  • 1
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • imo, using `sys._getframe` makes it more like pointing a rocket launcher at your foot… – poke Sep 06 '14 at 00:53
0

The guy there sets __all__ on first decorator application, so not explicitly exporting anything causes it to implicitly export everything. I am trying to improve on this design: if the decorator is imported, then export nothing my default, regardless of it's usage.

Just set __all__ to an empty list at the start of your module, e.g.:

# this is my_module.py
from utilitymodule import public

__all__ = []

# and now you could use your @public decorator to optionally add module to it
poke
  • 369,085
  • 72
  • 557
  • 602
  • Thanks for stating the obvious, this is exactly what I am trying to prevent. – yuri kilochek Sep 05 '14 at 14:24
  • @yurikilochek Well, know that it’s not possibly nor recommended to automatically have `__all__` preinitialized empty. The whole *point* of `__all__` is so that you can *configure* the current module in a clear way. If you were to obfuscate this and move it into a cryptic process, you would absolutely harm its use case. And btw. you shouldn’t be relying on all-imports anyway. – poke Sep 05 '14 at 14:32