Import expressions

Just thinking about how nice it is in Rust that you can write

std::fs::metadata(&path)?

to reference a function in another module without a use statement to bring it into this namespace.

Edit: I described below why this is nice: you can use it in the middle of coding something without either stopping to add an import or adding a mental to-do to do so.

The use statement would look like

use std::fs::metadata;
metadata(&path)

which is similar in behaviour to Python’s import.

The :: syntax isn’t available in Python because name[start::step] already has a meaning.

What about import x.y as an expression?

for x in (import itertools).chain(xs, ys, vz):
    ...

It is almost always itertools where I want this, or contextlib or collections or some other helper.

As a bonus, this would promote the use of lazy imports which sometimes make programs start faster (I used to avoid these they would ImportError later than they could; these days I’m sad about the cost of imports that are often not used).

You can use __import__("itertools").chain if you want to, but frankly I find putting the imports at the start of the module much more readable.

… at the cost of making inner loops significantly slower (an import is not free, even if you’ve already imported the module elsewhere).

1 Like

I would have thought it’s just a lookup in sys.modules instead of lookup in globals()?

That becomes ugly for more deeply nested names:

__import__('os.path').path.join

The thing you really want is importlib.import_module() but that’s an extra import away. And it’s not really for this. I mean, I wouldn’t let it through code review. It’s for dynamic imports.

As a dunder name, I believe that __import__ is now considered to be
for use by the interpreter. I think that that recommended way to refer
to an import using an expression is to use importlib.import_module.

Unless you are playing code golf, or writing obfuscated Python, or
trying to break out of a sandbox inside eval() or exec(), I think using
__import__ is a bit wiffy.

I agree with Paul that putting the imports at the top of the module is
better, oh, 98% of the time. The other 2% of the time, it might be
useful to look at better ways to do lazy imports.

I’m not convinced that a import expression is the right way to do it,
but I’m keeping an open mind.

Thinking out loud… what if we had a module proxy object that didn’t
actually do the nuts and bolts of loading the module until the first
time you actually used it (by attribute access)?

import itertools lazily
assert isinstance(itertools, LazyModule)
# much later
...
itertools.chain  # first attribute access loads the module
assert isinstance(itertools, ModuleType)

Does that seem useful?

1 Like

Sorry, that’s not solving the same problem. That’s my fault; I didn’t articulate what I like about the capability in Rust, I just cited it as nice.

The thing I like is that when I’m deep in an algorithm:

while ready:
    head = heapq.heappop(ready)
    yield head
    for d in |

(| indicates my cursor)

Then I just want to write something valid without breaking my flow. Whether I refactor the import to the top of the file later, or not, or it drops out, is something I don’t have to worry about.

Particularly for something like itertools.chain(), I might only use it once in the whole module. If Python had an import expression I might never bother to change it.

I understand there’s a difference in semantics (laziness) in Python that isn’t in Rust, but the coding experience is nice and could work in Python.

(import x).y is quite verbose which is okay if it’s only used sparingly but can add a lot of noise if it isn’t. If you agree that best practices are best enforced by the language’s grammar, this probably isn’t the best way to approach inline imports. The problem you are describing I’d think is better solved by an IDE. Granted, IDEs tend to place imports at the top and you’ll have to go out of your way to remove them, which might happen often when prototyping (and care about unused imports), but c’est la vie.

I’m recently furious about an IDE (VS Code) having quietly inserted the line

from os import initgroups

into a module that I was working on. This was in a game jam (PyWeek) and it meant my entry crashed on Windows. For all I know this cost me the competition victory. I didn’t even type initgroups and honestly it isn’t even a function I knew about.

This is not something better solved by IDEs. IDEs can *@%! right off.

(I didn’t storm off after this experience to come here and post this idea, and I am not consciously aware that my fury about this experience directly influenced this proposal. You just brought it up and now i’m angry about it again.)

I do think it would be fine if IDEs (grr) or code formatters like isort (yay) hoisted the import expression I actually intentionally typed out to the top of the file. But for that to be a thing, first import expressions need to be a thing.

This feels harmoniously balanced to me. It’s verbose so it will be used sparingly.

If someone used it excessively their code would look dreadful and that’s their problem.

Leaving aside whether this is something we may have or not, you can have this
by creating a namespace object which takes care of the import dynamically
whenever it gets asked to return an attribute it doesn’t yet know.

Some forms which would work:

stdlib.itertools.count()
pypi.requests.get('https://www.python.org')

or simply a generic one:

package.itertools.count()
package.requests.get('https://www.python.org')

You’d still have to put:

from importtools import stdlib, pypi, package

at the top of your file, though. However, that would be a good
indication for using such dynamic imports in the code below.

2 Likes

Nice hack! :grin: You would have to return a pretty muddy proxy object in order to support

package.urllib.parse.urlparse(...)

It would have to try attribute access, fall back to importing and if the import fails with ModuleNotFoundError, re-raise the AttributeError? So it can’t clearly distinguish import errors and attribute errors. I guess it could raise a MultiException, not sure whether that has appropriate semantics.

But, it feels too magical for my taste. If someone wrote this as a PyPI package I’d think it was cute but wouldn’t use it.

It’s certainly all doable, but like I said: whether or not using such a feature is good style is a different question. I would not use something like this :wink:

This is something that happens to me quite a lot, but in a Python interpreter console, and usually I’ll recognise the need for an import a few more tab-stops in. Importing anything currently would mean I have to cancel the entire code-block, import, then do everything again.

I know this is solved currently by Jupyter, but that’s very slow to start up when I need to do a quick calculation. An inline import would solve my problem here

1 Like

What do you do if you realise you made a typo or a logic error in the

middle of your code block in the interactive interpreter?

Out of the unlimited number of errors that one can make in a code block

typed interactively, why is “oops, forgot to import a module” important

enough to require new syntax, compared to (say) "oops, I mistyped a

variable or forgot a function call six lines back"?

Coincidentally, two days ago I was doing to exploratory programming in

the REPL. I meant to write for i, t in enumerate(something()) that

iterated over an infinite sequence of 2-tuples, but I kept leaving the

call to enumerate out. After the fifth time I made the same mistake, I

typed the code out in a text editor, copied it, and pasted into the

REPL. I think I would much prefer better editing tools in the REPL,

which could solve a vast range of editing errors, rather than an

extremely narrowly focused solution for just one niche problem.

I can recommend ptpython. It is a terminal-based REPL with full editor capabilities, including syntax highlighting and vim or emacs keybindings.

Why is your tone so aggressive? I wasn’t suggesting Python learns inline importing so my problem is resolved, rather I’m giving an argument for the idea. If I wanted an existing solution, I could just use __import__ (with no concern about compatibility, as I’m using it in an interactive session), or as I say, use Jupyter.

The more I think about it, the more convinced I am that lady imports are the better solution in modules here over inline imports. Ignoring the performance impact of local imports, having them declared at the top of a follow has allowed me to easily see a module’s dependencies

Sorry, I couldn’t resist, but you know, you could do

while ready:
head = use(“heapq”).heappop(ready)
yield head
for d in …
and other stuff like inline-installation of packages etc. if you did import use first… - see GitHub - amogorkon/justuse: Just use() code from anywhere - a functional import alternative with advanced features like inline version checks, autoreload, module globals injection before import and more. :slight_smile: