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?

caw
  • 429
  • 1
  • 3
  • 11
  • 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. – Charles Duffy Jul 30 '13 at 20:55
  • Please file a bug with your vendor. This is broken by design. – Keith Jul 30 '13 at 21:34

5 Answers5

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]
martineau
  • 99,260
  • 22
  • 139
  • 249
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.

Community
  • 1
  • 1
Johannes P
  • 862
  • 8
  • 14
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
nneonneo
  • 154,210
  • 32
  • 267
  • 343
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()
Jiminion
  • 4,984
  • 1
  • 22
  • 50
  • 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. – Jiminion Jul 31 '13 at 13:14
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()
David K. Hess
  • 14,018
  • 2
  • 42
  • 63