Why when importing the same module name in Python, Python doesn’t show an error?

Hi everyone!

  1. So… I have an interesting question… Why when importing the same module name in Python, Python doesn’t complain, doesn’t show an error?
  2. Example:

Example 1 “With error: script.py”

import json
import json # error: module "json" has already been declared

# some JSON:
x = '{ "name":"Thomas Jacobb"}'

# parse x:
y = json.loads(x)

# the result is a Python dictionary:
print(y["name"])

Example 2 “Without error: script.py”

import json # correct

# some JSON:
x = '{ "name":"Thomas Jacobb"}'

# parse x:
y = json.loads(x)

# the result is a Python dictionary:
print(y["name"])

Why should this be a mistake?

  1. Most programming languages do not allow importing the same module with the same name.
  2. So, I would like to know if this is actually an error.

proof of concept

Image description: As you can see it is not possible to declare something that you have already declared, in Python it works anyway.

reference

This is the kind of thing that is best left to static analysis tools like flake8 or pylint.

If you run flake8 on your code in Example 1, you get

F811 redefinition of unused 'json' from line 1

And you can configure your editor to show flake8 errors while you are typing.
In VS Code I do it with "python.linting.flake8Enabled": true in the settings, and it looks like this:

1 Like

I think that you’re confusing the two (btw, I don’t use or know jsx).

In Python the import is not a ‘declaration’; it’s a ‘statement’.

btw, you’ve messed up the print function – there is no key named “age”

1 Like

@rob42 Hi Rob! How are you?

  1. I think that you’re confusing the two (btw, I don’t use or know jsx).
  • R: It’s just an example. I just compared jsx and python. jsx can recognize if you’ve repeated a statement, python can’t. it would be pretty cool if python had the feature to recognize some declaration already made.

In Python the import is not a ‘declaration’; it’s a ‘statement’.

  • R: truth.
  1. btw, you’ve messed up the print function – there is no key named “age”
  • R: I made the correction in script.py

Thanks for the tip ;D

To reiterate, import is not a declaration. It’s a statement which is
idempotent and often even desirable to repeat, for example so-called
“lazy” imports:

def report_error(error_msg):
    import myerrors # custom error handler
    myerrors.report(error_msg)

For the example above, the report_error() function imports the
myerrors module just prior to use. Imagine you have it as a part of
a larger application and want to avoid the overhead of the myerrors
module under normal conditions, it will only be imported if the
above function is called, but it’s possible you have multiple error
messages you want to report so the import may be executed once for
each of them. Luckily, Python is smart about this and silently
ignores any subsequent import statements for the same module, making
this sort of solution both possible and performant.

3 Likes

Thank you for asking; I’m as well as I can be. I trust that you are also.

In Python, you don’t need to make any ‘declarations’, unlike C, for example.

If you (and you should) put all your import statements at the top of your script, then it becomes very obvious what’s been imported.

As you can (and again, should) use descriptive names for any variables, declarations are not needed. You can declare your variables, if you want to, but if you have something like:

name = 'rob'
name = 0

… then the 2nd declaration will simply overrule the 1st one.

You can see this in action, with this:

name = 'rob'
print (type(name))
name = 0
print (type(name))

First the variable name is declared and it’s auto assigned as a ‘string’ because that’s what’s been attached to it, but then we undo that with line 3: it’s not an error as such, but I would not code things that way.

1 Like

By Thomas Jacobb via Discussions on Python.org at 14Apr2022 10:31:

import json
import json # error: module “json” has already been declared

It looks like this has sort of been alluded to, but I’ll try to make
this clear.

The import statement is not a declaration. It is effectively a type of
assignment statement. This is not an error:

x = 1
x = 2

or (more like your imports):

x = 1
x = 1

An import (a) loads that named module and keeps a reference in
sys.modules with that name and (b) binds a reference to the module
to a local name in your current scope. So:

import json

causes the json module to be loaded, and the local name json to be
bound to a reference to the module so that you can make use of it. Like:

x = 1

creates an int with value 1 and binds the name x to a reference to
that int so that you can do arithmetic with it.

In fact, you can do this:

import json
json =5

and the name json will initial be a module reference, and then be an
int reference. Legal, if a little pointless.

And while imports are normally all done at the top of your module (so
that they’re easy to see and maintain) they can be done anywhere. Th
binding is to the current scope. So:

 def jsonify(x):
     ''' Return a JSON transcription of the value `x`.
     '''
     import json
     return json.dumps(x)

sets the name json in the function’s local scope. It isn’t known
outsode the function call, just like any other local variable of the
function.

Cheers,
Cameron Simpson cs@cskk.id.au

2 Likes

Also, this possibility to import a module twice is handy for debugging. Suppose that at the top of your module you have

from pprint import pformat

It’s a large module and you want to debug a function that is near the end. You don’t want to scroll to the beginning and refactor this import. You can just insert

from pprint import *

and get access to pprint, pp, … That pformat is reimported in this process is no problem.

1 Like