Using both `import foo` AND `from foo import bar`

While this topic happens to reference a specific file, it is not so much about that file as about the pattern.

While scrolling through the source for mock.py, I noticed something that struck me as odd.

import inspect
...
from inspect import iscoroutinefunction

And

import threading
...
from threading import RLock

For at least the threading bits, RLock was added first to fix a bug, and later on threading was used to support a new feature. Different authors, months apart.

As a casual observer, it seemed odd to me to use both styles of imports when the explicit identifier is also available from the module using dotted notation.

That is, without explicit testing, I would expect that replacing both with the appropriate foo.bar style would work for both cases.

Is there anything I’m missing on why RLock would be preferred over threading.RLock? I understand that avoiding the dots can be faster. But I’m not sure if that is an issue here, or simply a case “no one noticed” or “it wasn’t broke so didn’t fix it”.

Again, not picking on mock or anything. Just happened to be where I noticed it and it got me to wondering.

What from a import b does is essentially the same as

import a
b = b.a

So if you have both styles of imports in the same module you’re basically importing something twice and then assinging some names from it to variables. But imports are cached, so the second import isn’t actually executing the other module again, it’s just looking up the existing module in sys.modules. The time loss for that unessecary lookup is pretty marginal.

Accessing the dotted name and the imported name (threading.RLock vs RLock) also is essentially the same. The latter just is the former but using an implicit assignment at the top of the file. The name lookup on the module object again does technically cost some amount of time, but it’s not enough to really be concerned about. There also is an actual semantic difference, but only if the object in the other module is changed while the current one is executing. E.g.

import other_module
from other_module import something

print(other_module.something) # prints 123
print(something) # prints 123

other_module.something = 456

print(other_module.something) # prints 456
print(something) # prints 123

This kinda thing can happen even in more subtle ways since some function you call might modify the other module without you knowing. But since you’re usually importing classes, functions, or some kind of constant values, this doesn’t happen often in practice.

Ultimately, the difference between the two styles of imports is maily aesthetic. Some people prefer one over the other, but generally try to keep everything consistent. My guess is that the imports in your examples really just are two different personal preferences being different and no one catching it cause it doesn’t really make any difference.

2 Likes

I think …

import a
b = a.b
del a

… but you knew that.

I recognise the dilemma the OP @nexushoratio identifies. I mix the two styles but often wonder which I should use in a given case. Lately I have decided that readability is the key consideration. If you write:

from somepackage import f1, f2, f3

then you can use f2 unqualified, but when you encounder it in code, can you remember where f2 comes from? So at the moment, in modules of more than a page (say) I’m reserving import from for the familiar and frequently used objects:

from typing import Generator, Iterable, Tuple  # good
from torch import range, linspace, complex # bad
1 Like

The import happens once only. The from version just sets up the name.

If you mean that the module execution happens only once, then yes, that is precisely what I wrote in the sentence after the one you quoted. If you mean that no part of the import machinery runs the second time, that isn’t actually true. As a test, you can run the following code and will see that it does print “importing functools” twice.

import builtins

__MARKER__ = True
real_import = builtins.__import__
def new_import(name, globals, *args, **kwargs):
    if "__MARKER__" in globals:
        print(f"importing {name}")
    return real_import(name, globals, *args, **kwargs)
builtins.__import__ = new_import

import functools
from functools import lru_cache

I’ve written code where I wrote

import foo
from foo import bar

myself.

Just opening a random file I find at the top

import sympy as sp
from sympy import Piecewise, nan

I like working this way because it feels more ergonomic.

So I don’t think this is a problem if it happens due to different authors interacting either.

I might do it for random. For example, I prefer randrange(n) over the redundant random.randrange(n), but I prefer random.choice(a) over the less clear choice(a). So I might from-import randrange and do import random additionally.

1 Like