Summary
In this specific case, the itsdangerous
library implements an alias, so that these two import
lines do the same thing. The alias from itsdangerous import URLSafeTimedSerializer
is meant for convenience; the module is actually defined in the itsdangerous.url_safe
package.
Many real-world libraries use this technique so that users can choose whether to write the shorter line or to be explicit about the package structure. But by using the from ... import
syntax, the class will be called URLSafeTimedSerializer
(without any prefix) in the code anyway.
Some other real-world libraries use this technique with "internal" modules, which have names prefixed with _
. The idea is that the user is not intended to import those modules (or sub-packages) directly, but their contents are still available directly from the package. Instead of writing one large module, making this sort of package allows for splitting up the implementation across multiple files.
In general, from X import Z
means to take Z
from X
and use it. This can only work if X
actually has Z
in it. from X.Y import Z
means to take Z
from X.Y
and use it. This can only work if X.Y
has Z
in it. Even if both sources contain a Z
, it isn't necessarily the same Z
. However, a library author can arrange so that X
directly contains the same Z
that was defined inside X.Y
.
How from ... import
works
from X import Y
can work in three ways:
X
is a package, and Y
is a module. The package will be loaded if needed, then the module is loaded if needed. Then the module is assigned to Y
in your code.
X
is a package, and Y
is a class. The package will be loaded if needed. Assuming there is no error, Y
is already an attribute of X
; that will be looked up, and assigned to Y
in your code.
X
is a module, and Y
is a class. If X
is inside a package (this depends on the syntax used for X
, not on the folder structure), that package (and any parent packages) will be loaded if needed. Assuming there is no error, the Y
class is found inside the X
module, and is assigned to the name Y
in your code.
The above is a bit imprecise because, from Python's point of view, a package is a kind of module - so everything above should say "non-package module" rather than just "module".
Loading a package does not necessarily load any modules (including sub-packages) that it contains, but the package's __init__.py
(if present) can explicitly import
these things to load them. Loading a module that is part of a package, does necessarily attach it as an attribute of its package. (It also necessarily loads the package; otherwise, there would be nothing to attach to.)
Everything that is loaded is cached by name; trying to load it again with the same name will give the cached module
object back.
How do classes become part of packages and other modules?
Notice that only packages and modules are "loaded" (i.e. imported), not classes. A module
object is something that represents all the global variables in a module file's source code, after all its top-level code has run.
For ordinary modules, this is straightforward. For packages, the "top-level code" may be contained in a special file named __init__.py
.
How can the top-level package alias a class defined in one of its modules?
Simple: it just explicitly import
s the module using the same from ... import
syntax. Remember, the imports are cached, so this doesn't cause a conflict or waste time; and it assigns the class name as a global variable within the package's code - which means that, when the package is loaded, it will be an attribute of the package.
Again, loading a package doesn't automatically load its contained modules; but loading them explicitly (using __init__.py
) allows the package to alias the contents of its modules after loading them.
We can see this in the source code:
from .url_safe import URLSafeTimedSerializer as URLSafeTimedSerializer
(The use of as
here is redundant since the class is not actually renamed. However, sometimes these aliases will rename something in order to avoid a naming conflict.)
Following along: when the itsdangerous
package (which, being a package, is a module
object) is loaded, it will explicitly load its contained url_safe
module. It takes the URLSafeTimedSerializer
attribute from url_safe
(which is also a module
), renames it URLSafeTimedSerializer
, and then that is a global variable within the code of itsdangerous/__init__.py
. Because it's a global there, when the itsdangerous
object is created (and stored in the module cache), it will have a URLSafeTimedSerializer
attribute, which is the class. That, in turn, allows the user's code to write from itsdangerous import URLSafeTimedSerializer
, even though URLSafeTimedSerializer
is not defined there.