5

This is a conceptual question rather than an actual problem, I wanted to ask the great big Internet crowd for feedback.

We all know imported modules end up in the namespace of that module:

# Module a:
import b
__all__ = ['f']
f = lambda: None

That allows you to do this:

import a
a.b  # <- Valid attribute

Sometimes that's great, but most imports are side effects of the feature your module provides. In the example above I don't mean to expose b as a valid interface for callers of a.

To counteract that we could do:

import b as _b

This marks the import as private. But I can't find that practice described anywhere nor does PEP8 talk about using aliasing to mark imports as private. So I take it it's not common practice. But from a certain angle I'd say it's definitely semantically clearer, because it cleans up the exposed bits of your module leaving only the relevant interfaces you actually mean to expose. Working with an IDE with autocomplete it makes the suggested list much slimmer.

My question boils down to if you've seen that pattern in use? Does it have a name? What arguments would go against using it?

I have not had success using the the __all__ functionality to hide the b import. I'm using PyCharm and do not see the autocomplete list change.

E.g. from some module I can do:

import a

And the autocomplete box show both b and f.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Jon Lauridsen
  • 2,521
  • 5
  • 31
  • 38

2 Answers2

6

While Martijn Pieters says that no one actually uses underscore-hiding module imports, that's not exactly true. The traces of this technique can be easily seen in the Python's standard library itself (see a related question). Let's check it:

$ git clone --depth 1 git@github.com:python/cpython.git
$ cd cpython/Lib
$ find -iname '*.py' | xargs grep 'as \+_' | wc -l
183
$ find -iname '*.py' | xargs grep '^import' | wc -l
4578

So, about 4% of all imports are underscore-prefixed — not a majority, but yet far from “no one”. There also are some examples in numpy and matplotlib packages.

For me, this import-underscoring is the only right way to import module without exposing it at public. Unfortunately, it totally ruins code appearance, so many developers avoid using it. But it has some advantages over the __all__ approach:

  • Library user can decide whether a name is private or not without consulting documentation by just looking at the name. Looking to just __all__ is not enough to tell private from public as some public names may be not listed there.
  • No need to maintain a refactoring-unfriendly list of code entity names.

To the conclusion, both _name and __all__ are just plain evil, but the thing which actually needs fixing is the Python's module system, designed under an impression of “simple is better than complex” mantra. Compare to, for example, the way how modules behave in Haskell.

UPD:
It looks like PEP-8 has already answered this question in its “Public and internal-interfaces” section:

Even with __all__ set appropriately, internal interfaces (packages, modules, classes, functions, attributes or other names) should still be prefixed with a single leading underscore.

Community
  • 1
  • 1
firegurafiku
  • 3,017
  • 1
  • 28
  • 37
3

No one uses that pattern, and it is not named.

That's because the proper method to use is to explicitly mark your exported names with the __all__ variable. IDEs will honour this variable, as do tools like help().

Quoting the import statement documentation:

The public names defined by a module are determined by checking the module’s namespace for a variable named __all__; if defined, it must be a sequence of strings which are names defined or imported by that module. The names given in __all__ are all considered public and are required to exist. If __all__ is not defined, the set of public names includes all names found in the module’s namespace which do not begin with an underscore character ('_'). __all__ should contain the entire public API. It is intended to avoid accidentally exporting items that are not part of the API (such as library modules which were imported and used within the module).

(Emphasis mine).

Also see Can someone explain __all__ in Python?

Community
  • 1
  • 1
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I think I am under some misunderstanding then, I thought `__all__` only relates to the `from a import *` pattern? That is the point I concluded from link, which I've also come across in other searchings through SO. In PyCharm when I make an `__all__` entry it does not change the autocomplete box when I do a normal `import a` E.g.: module a: from . import b __all__ = ['f'] f = lambda: None (module b is empty) Now from another module I can do: from . import a And the autocomplete box show both `b` and `f`. – Jon Lauridsen Sep 28 '14 at 00:44
  • 1
    @JonLauridsen: The `from modulename import *` pattern is *one* of the usecases for `__all__`. If PyCharm still offers autocompletion for names not listed in `__all__` that could be considered a bug in PyCharm. – Martijn Pieters Sep 28 '14 at 00:49
  • Ah, well that'd do it yes.. Okay I've created a defect @ http://youtrack.jetbrains.com/issue/PY-14022 and we'll see what happens! Thanks. – Jon Lauridsen Sep 28 '14 at 00:55
  • @Martijn: Are you saying that if `a.py` contains `__all__=['f']` that `import a; a.b` will raise an AttributeError? I don't think that's true... – unutbu Sep 28 '14 at 01:08
  • 1
    @unutbu: no, I'm not saying that at all. I'm saying that PyCharm should only autocomplete names that are marked as public; they are already ignoring names that start with an underscore, which is the implicit definition of 'not public', while `__all__` gives an explicit definition. – Martijn Pieters Sep 28 '14 at 01:11
  • @Martijn: `__all__` only affects `from a import *`. With `import a`, `a.b` is exposed regardless of `__all__`. I don't think it is a bug in PyCharm (or IPython) that autocomplete shows the `b` attribute exists. – unutbu Sep 28 '14 at 01:18
  • @unutbu: then *why hide `a._foo` when autocompleting*, if that attribute also exists? Why have the damn convention at all in that case? `pydoc` (the library powering `help()`) does this, why can't PyCharm and IPython? – Martijn Pieters Sep 28 '14 at 01:19
  • @Martijn: Neither PyCharm nor IPython work the way you suggest they should. Is there any IDE that works the way you expect? If one interprets `__all__` as only affecting the globals when one uses `from module import *` then I don't think there is any reason why one should expect `__all__` to affect autocompletion when `import module` is used. – unutbu Sep 28 '14 at 01:24
  • @unutbu: But that is not the correct interpretation though. See the quoted documentation and the behaviour of `pydoc`. Are we arguing in circles here now? – Martijn Pieters Sep 28 '14 at 01:27
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/62055/discussion-between-unutbu-and-martijn-pieters). – unutbu Sep 28 '14 at 01:30