[Python-ideas] Allow multiple imports from a package while preserving its namespace

Eric Snow ericsnowcurrently at gmail.com
Fri Apr 27 12:10:15 EDT 2018


On Thu, Apr 26, 2018 at 7:51 AM, Nick Coghlan <ncoghlan at gmail.com> wrote:
> On 26 April 2018 at 23:37, Paul Moore <p.f.moore at gmail.com> wrote:
>> What are the benefits of this over a simple "import <lib>"?
>
> Forcing submodule imports would be the main thing, as at the moment,
> you have to choose between repeating the base name multiple times
> (once per submodule) or losing the hierarchical namespace.
>
> So where:
>
>     from pkg import mod1, mod2, mod3
>
> bind "mod1", "mod2", and "mod3" in the current namespace, you might
> instead write:
>
>     from pkg import .mod1, .mod2, .mod3
>
> to only bind "pkg" locally, but still make sure "pkg.mod1", "pkg.mod2"
> and "pkg.mod3" all resolve at import time.

I'm not exactly sure what this thread is about. :)  I'm pretty sure
it's one of the following (in order of confidence):

* explicit relative imports that bind to a fully qualified module name
* combining absolute import statements, for submodules of the same
package, into a single import statement

Either way I don't think there's much point.  For the relative import
case it kind of defeats the point of relative imports.  For the
absolute import case there isn't much value added.  I'll elaborate in
the rest of this message.


Combining Absolute Import Statements
==============================

First, I'll get combining-absolute-import-statements case out of the way.

Given the following example:

    # in cafe.spam
    from . import eggs, ham, bacon
    # or
    # from . import eggs
    # from . import ham
    # from . import bacon
    ...
    # use eggs
    # use ham
    # use bacon

The equivalent absolute imports would be:

   import cafe.eggs
   import cafe.ham
   import cafe.bacon
    ...
    # use cafe.eggs
    # use cafe.ham
    # use cafe.bacon

Under the proposed syntax it would be:

    # OP
    # This matches the layout of the existing absolute import
    # statement syntax.
    import cafe.{eggs, ham, bacon}

    # Nick
    # This matches the layout of the existing relative import
    # statement syntax (albeit without the dot).
    from cafe import .eggs, .ham, .bacon

    ...
    # use cafe.eggs
    # use cafe.ham
    # use cafe.bacon

For one thing, having one import per line improves the programming
experience, IMHO.  (Then again, I'm a fan of "do at most one thing per
line of code".)  For readers one-import-per-line makes it easier to
quickly identify the imported modules, and especially to match a
module name in the code to its corresponding import statement.  I do
that often enough that I worry a combined syntax would obscure the
names and lead to more effort, as a reader, to match them.  Also, it's
not like a module is going to import so many other modules (in the
normal case) that one module per line is going to result in too many
import lines at the top of the file. [1]


Qualifying Relative Imports
=====================

Second, I'll address the case of possibly fully qualifying relative
imports.  That seems reasonable enough at first glance.  However, the
point of relative imports is that you don't have to tie a module's
code to the overall package hierarchy in your project (app/library).
This helps you:

* avoid mass refactoring when you choose to move a directory to
somewhere else in the tree (or even to a different/new library)
* make it clear to readers that the imported modules are part of the
same library/app
* make it clear to readers how the current module relates to the imported ones
* (sometimes) keep your import statements shorter, benefiting you and
readers alike; this is particularly relevant if you happen to have a
deep directory structure (not a great situation, but sometimes
appropriate) [2]

Again, relative imports mean that your code does not become tied to
your project's directory layout; and you only have to know the base
name of the imported module and how it relates (in the directory
structure) to the current module.  Suppose we add a syntax like the
following (equivalent to the above examples):

   # This matches the relative import syntax.
   from '.' import eggs, ham, bacon  # Note the quotation marks around the dot.
   # This matches the absolute import syntax.
   import .eggs, .ham, .bacon
    ...
    # use cafe.eggs
    # use cafe.ham
    # use cafe.bacon

In that case the code is tied to the directory layout, so why did we
use relative imports in the first place?

-eric


[1] Just in case, I'll preemptively response to one possible concern:
that each of the three import statements above is going to import the
"pkg" module.  However, at most the first one will actually import it.
For the others the import machinery will short-circuit when it finds
the module in sys.modules.  [Nick is well aware of this, but I'd be
surprised if the majority of Python programmers know this.]  So the
cost is not high enough to worry about it.
[2] "Consenting adults" aside, explicit relative imports will
typically be to sibling modules in a package.  The more indirect the
import, the more dependent the module is on a larger tree of packages
which leads to more complexity for readers and more likelihood of
breakage when packages move around.


More information about the Python-ideas mailing list