PythonNewbie
PythonNewbie

Reputation: 1163

How to circular import using Python 3

I have came up with a simple code where I cannot figure out why and how it can be solved with circular imports.

test.py

from test2 import specific_ID

store_name = "testing"
STORE_ID = specific_ID()
print(STORE_ID)

test2.py

from test import store_name

print(store_name)

def specific_ID():
    print("Yay works")
    print(f"Store name: {store_name}")
    return True

the idea is that when I have the variable store_name in test.py, I would like it to be a sort of "constant" value where I can re-use the value in test2.py - However it does not seems to like where it throws err:

from test2 import specific_ID
ImportError: cannot import name 'specific_ID' from partially initialized module 'test2' (most likely due to a circular import)

My question is, how is it possible to re-use a variable from test.py to test.py?

Upvotes: 2

Views: 202

Answers (3)

Mad Physicist
Mad Physicist

Reputation: 114588

The other two answers provide good advice on how to structure your code and why the error occurs. You should listen to them, because they show you the right way to do things. This answer is not about that. Instead, let's figure out the minimal set of changes that will let you make the import actually work.

V1

test2 will import fine as long as test defines store_name. You can ensure that this happens by moving the import in test down one line:

store_name = "testing"

from test2 import specific_ID

STORE_ID = specific_ID()
print(STORE_ID)

V2

The opposite works too: the import of test will work fine as long as test2 defines specific_ID. Two changes will allow this. One is to move the import after the function definition, since the names used in a function do not need to exist until you run it:

def specific_ID():
    print("Yay works")
    print(f"Store name: {store_name}")
    return True

from test import store_name

print(store_name)

Placing imports at the end is a "common" technique for dealing with circular imports. Common is in quotes because the problem is not actually all that common and is code smell to begin with. You shouldn't be fixing it like that to begin with.

V2b

The second option for test2 is to perform the import inside the function. This is a more significant change because you also have to get rid of the print statement. Given how imports work, the import statement won't actually do anything besides create a local reference to the already-loaded module, so you don't have to worry about an expensive operation every time you run the function:

def specific_ID():
    from test import store_name

    print("Yay works")
    print(f"Store name: {store_name}")
    return True

Upvotes: 2

KuroiKuro
KuroiKuro

Reputation: 131

TLDR: Since in test2.py you only need to reference the store_name variable, removing the import of specific_id and the code that references specific_id and the error should go away.

Long answer: A circular import error is caused in test.py by the line:

from test2 import specific_ID

When python encounters this line, it will pause the execution of test.py, and attempt to process test2.py, so that it can get the value of the item that is being imported, which in this case is the specific_id function.

However, when we take a closer look at test2.py, we can see that at the top of the file, it is attempting to import from test.py:

from test import store_name

Now, since we reached this point by Python pausing execution on test.py, it means that the variable store_name has not been processed by Python at all, and therefore test2.py is unable to import it from test.py, thus leading to a circular import error. In my opinion, it will be best for the files to look like this to remove the error:

test.py:

store_name = "testing"

test2.py:

from test import store_name

print(store_name)

def specific_ID():
    print("Yay works")
    print(f"Store name: {store_name}")
    return True

STORE_ID = specific_ID()
print(STORE_ID)

Upvotes: 3

Chris Wesseling
Chris Wesseling

Reputation: 6388

Circular imports should be avoided, just put both test.py and test2.py in a package and put their shared globals in the main namespace:

__init__.py

STORE_NAME = 'testing'

test.py and test2.py

from . import STORE_NAME

But you can circumvent the global variables all together:

test2.py


def specific_ID(store_name: str) -> int:
    "Return the ID for a store"
    print("Yay works")
    print(f"Store name: {store_name}")
    # lookup id
    return id

test.py

from test2 import specific_ID
    
store_name = "testing"
STORE_ID = specific_ID(store_name)
print(STORE_ID)

When your functions are pure functions, separate from global state, that makes them much easier to reason about and refactor.

Upvotes: 4

Related Questions