Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - Should I alias imports with underscores?

Tags:

python

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.

like image 690
Jon Lauridsen Avatar asked Sep 28 '14 00:09

Jon Lauridsen


2 Answers

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 [email protected]: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.

like image 125
firegurafiku Avatar answered Oct 17 '22 13:10

firegurafiku


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?

like image 23
Martijn Pieters Avatar answered Oct 17 '22 13:10

Martijn Pieters