Allow import ... from ... notation

Idea:
Allow

import staff from module

syntaxes in addition to usual

from module import stuff

Motivation:
I often need import stuff from a module, so I write code like this

from pocket import ring

# some code using ring

As my code grows, I often realise that I need to import more stuff from the same module, so I change my code into.

import pocket

# some code using pocket.ring, pocket.nothing, pocket.hand

As Im finishing my code, I sometime come to conclusion that I dont need any other stuff from the module, so I clean my code back into

from pocket import ring, nothing, hand

# some code using ring, nothing, hand

I make these edits back and forth multiple times during the development. Each of this edits require me to flip the order of import and pocket, which requires copy-pasting or deleting-and-rewriting the symbols. Therefore, I suggest to allow writing

import ring from pocket 

and

import ring, nothing, hand from pocket 

so the order of ring and pocket would always remain the same, and I could save a few keystrokes

Cost/Benefit analysis
I do realise that the benefits from my suggestion are extremely small, but cost should not be very hight either, right? The implementation should not break anything, does not add anything new…

My hope is that the rest of the developers will come out saying “damn, I hated the same thing all along, lets change this!”

When you see a line starting import pocket, you immediately know it’s importing a module.

If you saw a line starting import ring, nothing, hand, you’d assume that it was importing a number of modules, but then you’d see the from pocket tacked on the end and you’d realise that those weren’t modules at all, but items from a module called pocket and you’d change it to from pocket import ring, nothing, hand anyway to make it clearer.

6 Likes

My concern is that having two ways to do the same thing adds complexity and reduces consistency.

4 Likes

Have you considered adding some macros to your editor?

2 Likes

I don’t like the “from a import b, c” syntax because it doesn’t align nicely with “import a”. Plus, “from a import b, c” also binds the name “a”, which is weird from the look of it.

I’d prefer something like “import a (b, c)”, though the particular syntax needs to be considered to make it look nice.

In practice I just live with “from a import b, c” because it gets the job done, and elegance is less important.

The drawback with “import b, c from a” is that when you type it, the editor cannot “autocomplete” the b and c before you type a. That’s inconvenient.

I don’t think that’s correct.


>>> from math import pi

>>> math

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

NameError: name 'math' is not defined

1 Like

Thanks for the correction. You’re right.

That I observe “from a import b, c” binds “a” is not a result of “import” but a result of “a” being a filename (together with other conditions I have not attempted to sort out).

I’m pretty sure that’s not a thing either, at least as stated. Can you
produce an example of what you’re describing for us to try?

Cheers,
Cameron Simpson cs@cskk.id.au

Sure, please find an example below.

You need to create three files in the following structure:

work/
    demo/
        __init__.py
        _impl.py
    test_demo.py

__init__.py contains:

from ._impl import f, g
h = _impl.h  # <-- why is the _impl name available here

_impl.py contains:

def f(): return "how"
def g(): return "are"
def h(): return "you"

test_demo.py contains:

import demo
print(demo.f(), demo.g(), demo.h())

Now from the work directory, run the following:

python test_demo.py

and you should see the output

how are you

Sure, please find an example below.

Thank you, this is a great example.

__init__.py contains:

from ._impl import f, g
h = _impl.h  # <-- why is the _impl name available here

This was a surprise to me.

I think this is because this import imports _impl.py as the module
demo._impl and something in the import machinery annotations the
demo module with the name _impl to make demo._impl a working name.

Again, this is a surprise to me and I’m having trouble finding
documentation of this behaviour. I expect the import to define the
sys.modules["demo.__impl"] module entry, but I didn’t expect it to
define the name _impl in the demo module namespace.

So I think you get the name _impl because you’re in demo (i.e.
demo/__init__.py).

So the import statement is not writing _impl into whatever namespace
is running the import, it’s writing the name _impl into the demo
namespace, regardless of where the import statement was.

Here’s a test_demo2.py showing this:

 from demo import h
 print("we have h:", h)
 print("we do not have demo:", demo)

and running it:

 [~/tmp/demo]fleet2*> python3 test_demo2.py
 we have h: <function h at 0x10392c790>
 Traceback (most recent call last):
   File "/Users/cameron/tmp/demo/test_demo2.py", line 3, in <module>
     print("we do not have demo:", demo)
 NameError: name 'demo' is not defined

So the import statement not making a local demo name. You only saw
_impl in the previous example (demo/__init__py) because the demo
namespace is the local namespace when you were running that partiular
bit of code.

Cheers,
Cameron Simpson cs@cskk.id.au

That does make some sense. Consider:

import foo.bar
print(foo.bar)

One would expect the print to succeed (and print the repr of a module, not particularly useful). As such, whatever object foo is needs to be able to locate bar.

It’s a little surprising inside __init__.py itself, but I don’t think there’s any good way around that, unless import demo._impl were to be actually disallowed in some way.

1 Like

Yes. I’ve been thinking about this and you’ve put into an example what I’ve been trying to formalise .

1 Like

As someone who is using both Python and JavaScript/TypeScript, which uses import { ... } from ..., I find Python’s syntax much more convenient. With Python you can just start typing and auto completion will just work. With JS/TS you have to start writing import {} from 'whatever', then move the cursor back between the braces for auto completion to work. That’s quite annoying. Python made the better choice here.

5 Likes

Just to address the original motivation for this - I have encountered this issue myself, but my solution to it was a particularly development order for determining imports. I’ll post it here in case it might be useful to you too!

  1. Generally I always start with from module import stuff syntax, and slowly the deps may grow from here. With auto-imports this happens quite naturally. Only exceptions to this are really large modules like numpy, pandas where it is very standard to import numpy as np etc.
  2. Periodically I have a cull of the items being imported, as the type checker will identify unused imports with this syntax - e.g.:
    from pocket import ring, ring2, ring3 (if ring2 is not used it will appear in grey)
  3. Only when my package/project is complete to I do a final review of the imports. If there is a large number of items being imported, I switch to the import x strategy as you describe, but I actually find in practice I very rarely have to do this (I tend to keep most files to around 200 lines). typing is a strange one for this, as it’s quite variable how many imported items are required, and the exact imports required are highly dependent on the exact code structure - perhaps this should be treated in the “numpy” style.
1 Like