octref
octref

Reputation: 6801

Why is "import" implemented this way?

>>> import math
>>> math.pi
3.141592653589793
>>> math.pi = 3
>>> math.pi
3
>>> import math
>>> math.pi
3

Initial question: Why can't I get math.pi back?

I thought import would import all the defined variables and functions to the current scope. And if a variable name already exists in current scope, then it would replace it.

Yes, it does replace it:

>>> pi = 3
>>> from math import *
>>> pi
3.141592653589793

Then I thought maybe the math.pi = 3 assignment actually changed the property in the math class(or is it math module?), which the import math imported.

I was right:

>>> import math
>>> math.pi
3.141592653589793
>>> math.pi = 3
>>> from math import *
>>> pi
3

So, it seems that:
If you do import x, then it imports x as a class-like thing. And if you make changes to x.property, the change would persist in the module so that every time you import it again, it's a modified version.

Real question:

  1. Why is import implemented this way? Why not let every import math import a fresh, unmodified copy of math? Why leave the imported math open to change?
  2. Is there any workaround to get math.pi back after doing math.pi = 3 (except math.pi = 3.141592653589793, of course)?
  3. Originally I thought import math is preferred over from math import *. But this behaviour leaves me worrying someone else might be modifying my imported module if I do it this way...How should I do the import?

Upvotes: 18

Views: 723

Answers (3)

user2357112
user2357112

Reputation: 280963

Python only creates one copy of any given module. Importing a module repeatedly reuses the original. This is because if modules A and B imported C and D, which imported E and F, etc., C and D would get loaded twice, and E and F would get loaded 4 times, etc. With any but the most trivial of dependency graphs, you'd spend a few minutes loading redundant modules before running out of memory. Also, if A imported B and B imported A, you'd get stuck in a recursive loop and, again, run out of memory without doing anything useful.

The solution: Don't screw with the contents of other modules. If you do, that's an interpreter-wide change. There are occasionally situations where you'd want to do this, so Python lets you, but it's usually a bad idea.

Upvotes: 13

theodox
theodox

Reputation: 12208

The import behavior is intended to allow modules to have state. For example, a module that runs initialization code may have all sorts of different behaviors based on what happens at init time (a good example is the os module, which transparently loads different versions of the path submodule depending on what OS you're on). The usual behavior exists to allow lots of different code to access the module without re-running the initialization over and over. Moreover, modules function sort of like static classes in other languages - they can maintain state and are often used as an alternative to global variables: eg, you might use the locale module to set local culture variables (currency format, etc) -- calling locale.setlocale in one part of your code and local.getlocale in another is a nice alternative to making a global variable.

Your example, of course, points out the weakness. One of the classic python principes is

We're all adults here

The language does not provide much of the privacy management features you'd find in, say, Java or C# which let the author lock down the contents of a module or class. You can, if you're feeling malicious (or just suicidal) do exactly the sort of thing done in your example: change pi to equal 3, or turn a function into a variable, or all sorts of other nasty stuff. The language is not designed to make that hard -- it's up to coders to be responsible.

@Josh Lee's answer shows how to use reload, which is the correct way of refreshing a module to it's disk-based state. The wisdom of using reload depends mostly on how much init code is in the module, and also on the web of other modules which import or are imported by the module in question.

Upvotes: 6

Josh Lee
Josh Lee

Reputation: 177604

A module may be imported many times. An import statement just loads the reference from sys.modules. If the import statement also reloaded the module from disk, it would be quite slow. Modifying a module like this is very unusual and is only done under rare, documented circumstances, so there’s no need to worry.

How to reload a module:

>>> import imp
>>> imp.reload(math)
<module 'math' (built-in)>
>>> math.pi
3.141592653589793

Upvotes: 10

Related Questions