Module alias does not count in imports

>>> import datetime as dt
>>> from dt import datetime
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'dt'
>>>

Is this by design?

I believe its by design and a good design at that.

The imported module must be in the sys.modules dictionary to be accessible.

The alias is not put in the sys.modules. Its just defined with the module that you do the as in.

The import datetime as dt puts datetime in sys.modules.
the from dt ... cannot find a module named dt and reports the problem to you.

Why is it a good design? Becuase it prevents all sort of hard to debug errors from having aliases mess up code that sound work by would not.

For example this will cause confusion your code.

In one module you do this…

import datetime as sys

And somewhere else you do this:

import sys # gets datetime not the sys module
sys.stdin.write("This no longer works as expected")

Well, but the problem is in the root: you already caused confusion by naming a builtin module as another builtin module. This lead to confusion even in the current state of art.

I don’t think it’s a bad design.

Yes. Aliases aren’t “special”; they’re just binding a name in the current namespace. But the module cache used the module’s actual name to store the cached module. The import statement doesn’t care about your program’s variable names, just like it doesn’t care about filenames; it cares about symbolic names that are used to locate the module. After all, you can do from datetime import datetime even if there was not anything called datetime in the program already.

The first line is roughly equivalent to

if 'datetime' not in sys.modules:
    importlib.import_module('datetime') # this will add the entry to sys.modules
dt = sys.modules['datetime']

And the second to

if 'dt' not in sys.modules:
    importlib.import_module('dt')
datetime = sys.modules['datetime'].datetime

Except, of course, that you’d already have to import sys and import importlib to get that functionality :wink:

Are you agreeing with my reasoning that this is why alas must not go into sys.modules? I want to create an example to make point.

In your opinion, it will be wrong to add also the alias to the module cache? And why?

Yes.

First, because the cache is truly global, unlike “global” variables that only apply to the current file. If aliases were cached, then you could do e.g.

import cmath as math

(both of those are names of standard library modules) and it would change the behaviour of import math in a different file.

Second, because the as syntax is only one way to alias the module, and if you did it a different way:

import cmath
math = cmath

then there is no way that the import system could know about it.

Third, it would probably get very confusing if you aliased a package, and then also imported modules from that package.

1 Like

Thank you for the explaining.

If you’re curious as to why this is important, imagine what would happen if something like the random module were not globally shared. You’d get different sequences of random numbers, even after explicitly seeding the generator, depending on what module you’re in! There are other, less obvious, effects too, such as the way that exception handling is done, so it’s actually vitally important to have a single copy of every module.

You could apply the same seed in each script.

(OT: I prefer random.Random)

That would, at best, just result in different modules seeing the same sequence but at different points. At worst, it means that every module that uses random numbers has to provide an API equivalent to the random module itself. And the same goes for decimal contexts, logging configs, warnings, and everything else that has any sort of global state. Plus, it still doesn’t solve the problem of exceptions, but that could be done with some other form of global state I guess.

You can simply use a random.Random singleton.

But that’s exactly the problem: everything that uses random numbers has to provide an API that allows you to select a singleton to use. And, as mentioned, this also applies to myriad other small pieces of global state.

And that goes all the way up and down the chain. If you have a module X that imports module Y, which imports Z, which uses random numbers, you in module X have to provide a way to affect Z’s random numbers. With properly-cached imports, if I import X but want to change Z’s random numbers, I can simply import Z myself and either use its API, or inject something into the module namespace. You, as author of X, don’t have to do anything at all. That’s why the module cache NEEDS to be completely global - some state truly does need to be application-wide.