Module reloads and resets without explicit instruction to do so

Viewed 31

TLDR: I want to know in general terms, what can cause a module to reload and reset back to original state without me explicitly trying to get it to.

I have come to the conclusion that my main module is reloading and in the process all variables are re-initialized to their initial state.

I cannot produce a minimal reproducible example. After googling I cannot find anything else on the subject except that python3.x should not reload modules unless explicitly told to.

Using pygame 1.9.6.

I want to ask what usually causes this behavior. Example of my main module with my added debugging prints:

import pygame
from sys import exit

class Engine:
    def __init__(self):
        self.init = False
        self.running = False

    def start(self):
        print("engine start called")
        print("self.init state:", self.init)        

        from graphics import render
        render.init()

        from initializer import initializer
        initializer.init_sprites()

        if not self.init:
            self.init = True
            self.running = True

        print("new self.init state:", self.init)

        self.main_loop()

    def update_input(self):
        pass

    def update_events(self):
        pass

    def update_graphics(self):
        pass

    def self.main_loop() # it's at the very end of the real Engine() aswell
        while True:
            self.update_input()
            self.update_events()
            self.update_graphics()

        pygame.quit()
        exit()


engine = Engine()

print("above engine start reached")

app_init = False # I added this purely for debugging reasons

if not app_init:
    app_init = True
    engine.start()

The app isn't crashing or throwing exceptions, because all other modules keep running as they should. For example, sprites retain their states and keep moving about as they should. Camera is still scrollable et c.

However, console gives:

above engine.start() reached
engine start called
self.init state: False
render.init called
new self.init state: True
#stuff happening in background in other modules, no methods changing self.running or explicitly reloading main module. However imports of engine object do happen
above engine.start() reached
engine start called
self.init state: False
render.init called
new self.init state: True

BUT:

1) main_loop() is never returned to

2) The prints proves that the end of the main module is reached somehow even though the While loop should be active, and the app should exit() if it moves past the while loop in main_loop()

3) The self.init and global app_init state is reset to hard coded value and engine.start() is called like it was the first time the module was initialized

Is this an importing issue?

Update:

Not solved. But I have removed all side-effects by preventing all other modules from being re-initialized. However, this seems like a dirty hack and I am afraid that the reloading of my main module will lead to consequences sooner or later.

1 Answers

It's mostly a wild guess, but the only explanation I can imagine with the infos provided is that some of your "other modules" imports your file. If that's the case, you should restructure your code to avoid circular dependencies. A simple way to check this is to add a guard preventing everything under your class definition to be executed on import:

class Engine:
    # ...


if __name__ == "__main__":

    engine = Engine()

    print("above engine start reached")

    app_init = False # I added this purely for debugging reasons

    if not app_init:
        app_init = True
        engine.start()

Also, please move your imports (the ones in Engine.__init__) at the module's top-level.

EDIT:

An import was the problem (I retrieved deltatime from the engine in another module).

Design issue then.

After removing that import, engine is no longer re-initialized. I am now using if name == "main". (...) I had no idea the engine = Engine() at the bottom would re-initialize engine when module was imported.

Python is a runtime language - except for byte-code compilation, everything happens at runtime. When a Python module is loaded for the first time (in a given process), all code at the module's top-level is executed - this is how the functions and classes are created (def and class are executable statements) etc - then it's cached in the sys.modules dict (under the module name) for other imports (so it's only loaded once).

Now when a module is used as a script, the same thing happens, except that the module is imported (and cached) under the name __main__. So when your other module tries to import your main script, the loader looks for the script name, doesn't find it (because it's been cached as 'main', not as "youscriptname"), and so reloads it. This explain the double loading, but it also means that those two modules are different instances, the classes are different classes and the 'engine' instances are different instances.

IOW, while the if __name__ == '__main__' guard prevents your script's "main" code to be executed twice, you've only masked the symptom, not fixed the root issue. To make a long story short, none of your modules should try to access anything defined in the main script.

If there are functions or classes defined in your main script that you want to reuse elsewhere, you should extract them to another module. But if something in another module needs to access the engine instance from the main script, then you'll have to rethink your design to explicitely pass the engine instance (or whatever is needed from it).

Related