Search code examples
pythonmultiprocessing

Class attribute resets to default after passing class as an argument in multiprocessing


I am creating a class to set, hold and access data throughout the project. Below is my Data class:

class Data:
    """This class stores project-related constants as class attributes for easy access throughout the project."""
    ORG_URL: str = "https://www.myurl.com/"
    PROJECT_NAME: str = "My Project"
    root_path: str = ""

    @classmethod
    def set_root_path(cls, new_path):
        cls.root_path = new_path

Below is my Main class which sets the root when run:

from multiprocessing import Process, Manager


class Main:
    def __init__(self) -> None:
        # determine if application is a script file or frozen exe
        if getattr(sys, 'frozen', False):
            application_path = os.path.dirname(sys.executable)
        elif __file__:
            application_path = os.path.dirname(__file__)

        data = Data()
        data.set_root_path(f"{application_path}")
        

        with Manager() as manager:
            q = manager.Queue()
            args = manager.Queue()
            print(data.root_path)                          ## prints correct path
            gui = Process(target=GUI,args=(q,args,data))
            runner = Process(target=Runner,args=(q,args,data))
            gui.start()
            runner.start()
            gui.join()
            runner.join()

if __name__ == "__main__":
    Main()

Below is my Runner class where i am accessing the root_path:

class Runner:
    def __init__(self,q,args,data) -> None:
        print("Initiating Runner")
        self.q = q
        self.args = args
        self.data = data
        print(self.data.root_path)  # prints ''

I tried multiple things but could not resolve it. Please help.


Solution

  • This is because processes are spawned in Windows (and MacOS by default), not forked. As a result modules are reloaded when a new process is spawned, so module-level class statements are re-executed, and so are the initialization of class variables within.

    To transfer an object across processes you should use an instance of a class rather than a class. For readability you can create a dataclass instead so the instance attributes can be declared in as clean a way as class variables:

    from dataclasses import dataclass
    
    @dataclass
    class Data:
        ORG_URL: str = "https://www.myurl.com/"
        PROJECT_NAME: str = "My Project"
        root_path: str = ""
    
        def set_root_path(self, new_path):
            self.root_path = new_path
    

    Demo (run under Linux in explictly spawn mode): https://replit.com/@blhsing1/RequiredDisguisedCodewarrior