Search code examples
pythonimportscoping

Accessing a class instance in a library from two separate scripts in a project


I searched all over and could not come up with a reasonable search query to produce helpful results. I'll try to explain this with a simple example (that is tested).

Suppose I have some small custom Python library that contains just the following private class and public instance of it:

#!/usr/bin/env python

class _MyClass(object):
    def __init__(self):
        self.val = "Default"

my_instance = _MyClass()

Now, I also have two other python files ('file_a' and 'file_b') that will end up importing this instance from my library as seen below.

The full code in 'file_a':

#!/usr/bin/env python

from my_lib import my_instance

my_instance.val = "File A was here!"
import file_b
file_b.check_val()

The full code in 'file_b':

#!/usr/bin/env python

from my_lib import my_instance

def check_val():
    print "From 'file_b', my_instance.val is: {}".format(my_instance.val)

The resulting output, if I only execute 'file_a' within a directory that also contains 'file_b' and 'my_lib', is this:

From 'file_b', my_instance.val is: File A was here!

Can someone explain to me how 'file_b' is able to access the same exact instance as 'file_a' in my example? Does this have to do with how the value being set in 'file_a' is global?

By the way, I do know I can just make 'MyClass' public again and instantiate it whenever a unique instance is needed in either 'file_a' or 'file_b', but the main reason I am posting this question is to wrap my head around this specific concept.


Solution

  • There are two things you need to understand here:

    1. Module caching

    Python caches module imports to improve performance, this happens even when you do from foo import bar. The module object gets stored in sys.modules.

    Hence, in your case both file_a and file_b are accessing same module object my_lib and same instance my_instance.

    2. References

    In Python variable assignment is basically adding a new reference to the same object, this is true for imports as well.

    from my_lib import my_instance
    

    is basically

    import my_lib
    my_instance = my_lib.my_instance
    del my_lib
    

    Now as we modify this instance in file_a, we have basically modified the instance in my_lib, and file_b will also see this change.


    You can modify file_a and file_b to verify this.

    file_a:

    #!/usr/bin/env python
    
    from my_lib import my_instance
    
    my_instance.val = "File A was here!"
    
    print "Inside file_a"
    import sys
    print id(sys.modules['my_lib']), sys.modules['my_lib'].my_instance, my_instance
    
    import file_b
    file_b.check_val()
    

    file_b:

    #!/usr/bin/env python
    
    from my_lib import my_instance
    
    print "Inside file_b"
    import sys
    print id(sys.modules['my_lib']), sys.modules['my_lib'].my_instance, my_instance
    
    
    def check_val():
        print "From 'file_b', my_instance.val is: {}".format(my_instance.val)
    

    Output(check the object IDs):

    >>> %run file_a.py
    
    Inside file_a
    4396461816 <my_lib._MyClass object at 0x106158ad0> <my_lib._MyClass object at 0x106158ad0>
    Inside file_b
    4396461816 <my_lib._MyClass object at 0x106158ad0> <my_lib._MyClass object at 0x106158ad0>
    From 'file_b', my_instance.val is: File A was here!