0

Background: I'm working with a device vendor-supplied API module which stores device login data (device hostname, session ID, etc) as a global variable; I'm trying to figure out if it's possible to have multiple instances of the module to represent logins to multiple devices.

So far I've attempted a couple strategies with test code, none of which have worked:

Test module code: statictest.py

count = 0

class Test():
  @classmethod
  def testcount(cls):
    global count
    count += 1
    return count

First attempt: import module multiple times and instantiate:

>>> import statictest as s1
>>> import statictest as s2
>>> s1.Test.testcount()
1 
>>> s1.Test.testcount()
2
>>> s2.Test.testcount()
3

Second try: import module inside class, instantiate class:

#!/usr/bin/env python2.7
class TestMod():
  s = __import__('statictest')

  def test(self):
    ts = self.s.Test()
    return ts.testcount()

t = TestMod()
u = TestMod()
print t.test()
print u.test()

That one didn't work either:

[~/]$ ./moduletest.py 
1
2

This seems like it should be obvious but is there any way to encapsulate a module such that multiple instances are available?

2
  • 1
    No. If you want multiple instances, you should be using a class, not a module-level value. Your vendor is behaving (very) badly by putting this state at the module level. Commented Jul 30, 2013 at 20:55
  • Please file a bug with your vendor. This is broken by design. Commented Jul 30, 2013 at 21:34

5 Answers 5

2

The following seems to work. It uses your statictest.py module and a combination of a few of the ideas in other answers to create a context manager which will allow easy switching and use of any of the various instances of the module:

from contextlib import contextmanager
import importlib
import random
import sys

MODULE_NAME = 'statictest'
NUM_INSTANCES = 4
instances = []

# initialize module instances
for _ in xrange(NUM_INSTANCES):
    if MODULE_NAME in sys.modules:
        del sys.modules[MODULE_NAME]

    module = importlib.import_module(MODULE_NAME)
    for _ in xrange(random.randrange(10)): # call testcount a random # of times
        module.Test.testcount()

    instances.append(sys.modules[MODULE_NAME])

@contextmanager
def statictest_inst(n):
    save = sys.modules[MODULE_NAME]
    sys.modules[MODULE_NAME] = instances[n]
    yield instances[n]
    sys.modules[MODULE_NAME] = save

def get_counts():
    counts = []
    for i in xrange(NUM_INSTANCES):
        with statictest_inst(i) as inst:
            counts.append(inst.count)
    return counts

print 'initial counts', get_counts()

choice = random.randrange(NUM_INSTANCES)
print 'calling instance[{}].testcount()'.format(choice)
with statictest_inst(choice) as inst: # use context manager
    inst.Test.testcount()

print 'counts after updating one of them', get_counts()

Sample output:

initial counts [2, 4, 4, 1]
calling instance[2].testcount()
counts after updating one of them [2, 4, 5, 1]
Sign up to request clarification or add additional context in comments.

Comments

1

I think it's not possible because Python modules pretty much behave like Singletons (in fact this is a valid method to create a Singleton in Python). Refer to this SO thread or this one for example. This design is intended to prevent multiple imports of the same module because it can be somewhat expensive. The logging module is an excellent example of this. You set up your logger once and all your code which is being run by the same interpreter and imports logging will write to the same logfile.

Comments

1

Between imports, you can delete the module from sys.modules to force it to be re-imported:

import sys
import module
del sys.modules['module']
import module as module2
print(module is module2) # prints False

Comments

0

If you make copies of the file (statictest1.py, statictest2.py) that will work:

>>> import sttest1 as s1
>>> import sttest2 as s2
>>> s1.Test.testcount()
1
>>> s1.Test.testcount()
2
>>> s1.Test.testcount()
3
>>> s2.Test.testcount()
1
>>> s2.Test.testcount()
2
>>> s2.Test.testcount()

1 Comment

Note: This works if the second file is just a link to the first one, so you could dynamically create links and then import the 'new' file. Kind of odd, but seems to work.
0

If the module's state is really well contained, you could write a context manager to swap the global state in and out of the module (i.e. monkey patch it) when you call it.

For instance:

import mymodule

class ModuleState(object):
    def __init__(self):
        self.global_state = mymodule.global_state

    def __enter__(self):
        my_module.global_state = self.global_state
        return self

    def __exit__(self, type, value, traceback):
        my_module.global_state = default_state

default_state = mymodule.global_state

# Init mymodule for state1
state1 = ModuleState()

# Init mymodule for state2
state2 = ModuleState()

# Init mymodule for state3
state3 = ModuleState()


# Do something in state2
with state2:
    mymodule.something()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.