Why do relative from-imports also add the submodule itself to the namespace?

This is a quirk of the way that packages work. Consider this from an external perspective:

import asyncio
import asyncio.base_events
from asyncio.base_events import BaseEventLoop

After the first statement, one would expect the name asyncio to exist in the current namespace. Well and good.

After the second, one would expect that (a) asyncio has not been reassigned; (b) asyncio.base_events is now valid; and (c) any future import of asyncio.base_events will use the same module (it won’t be reimported more than once).

The third statement should give you the class out of the same module that the second line imported. If you switch lines 2 and 3 around, it’s clear that the from-import MUST cache the imported module, as otherwise you’d run into problems where a class doesn’t correctly match (because it’s not the same class, it’s another thing with the same name).

So you should be 100% confident that this code will work:

import asyncio
original = id(asyncio)
from asyncio.base_events import BaseEventLoop
assert id(asyncio) == original
assert asyncio.base_events.BaseEventLoop is BaseEventLoop

From which it should be clear that asyncio.base_events is indeed guaranteed to be set after the from-import.

The one small piece left in the puzzle is: Inside __init__.py, you’re running in the package’s namespace. That means that your globals ARE the attributes of the asyncio object, and adding asyncio.base_events must also populate your globals with that - because they’re the same thing.

So it’s a little bit weird when you see it, but it’s a consequence of otherwise-logical design decisions, and it can’t really be any other way without breaking something :slight_smile:

2 Likes