[Tutor] aliasing an imported module
Steven D'Aprano
steve at pearwood.info
Sun Feb 14 01:41:41 CET 2010
On Sat, 13 Feb 2010 10:51:38 am Garry Willgoose wrote:
> I want to be able to import multiple instances of a module and call
> each by a unique name and it doesn't appear at first glance that
> either import or __import__ have what I need.
No, such a thing is not officially supported by Python.
Python treats modules as singletons -- there is only one instance of any
module at any time. It is possible to force Python to break that
promise, but that is fighting the language, and there's no guarantee
that it won't cause other breakages further along. So if you choose to
create multiple instances of a module, you're doing something the
language doesn't want you to do, and if you end up shooting yourself in
the foot you'll have no one to blame but yourself.
Having said that, the following trick should do what you want. Start
with a simple module holding state:
# mymodule.py
state = []
Here's Python's normal behaviour:
>>> import mymodule
>>> mymodule.state
[]
>>> mymodule.state.append(123)
>>> mymodule.state
[123]
>>> import mymodule as something_else
>>> something_else.state
[123]
And here's the trick:
>>> import sys
>>> del sys.modules['mymodule']
>>> import mymodule as another_name
>>> another_name.state
[]
>>> mymodule.state # Check the original.
[123]
This *should* work across all versions and implementations of Python,
but remember that it's a hack. You're fighting the language rather than
working with it.
Another problem is that there's no guarantee that the module holds all
its state inside itself: it might in turn import a second module, and
store state in there. Many packages, in particular, may do this.
The best solution is to avoid global state if you possibly can.
You also say:
> The key problem is that
> the module might locally store some partial results ready for the
> next time its called to save CPU time (typically the results for one
> timestep ready for the next timestep).
I'm going to take a wild guess as to what you're doing, and make a
suggestion for how you can do something better.
I guess you have functions something like this:
STATE = None
def func():
global STATE
if STATE is None:
# No global state recorded, so we start from scratch.
step = 1
partial = 0
else:
step, partial = STATE
step += 1
partial = do_some_calculations(step, partial)
STATE = (step, partial)
return partial
But as you point out, that means all calls to func() use the same global
state.
Here are two alternatives. Here's a rather messy one, but it tickles my
fancy: use a token to identify the caller, so each caller gets their
own state and not somebody else's.
LAST_TOKEN = 0
def get_token():
global LAST_TOKEN
LAST_TOKEN += 1
return LAST_TOKEN
STATE = {}
def func(token):
global STATE
if not STATE[token]:
# No global state recorded, so we start from scratch.
step = 1
partial = 0
else:
step, partial = STATE[token]
step += 1
partial = do_some_calculations(step, partial) # Defined elsewhere.
STATE[token] = (step, partial)
return partial
Then, before each independent use of func, the caller simply calls
get_token() and passes that to the function. I'm sure you can see
problems with this:
- the caller has to store their tokens and make sure they pass the right
one;
- the function needs to deal with invalid tokens;
- the global STATE ends up storing the result of intermediate
calculations long after they are no longer needed;
- the separation is a "gentleman's agreement" -- there is nothing
stopping one caller from guessing another valid token.
Although I'm remarkably fond of this solution in theory, in practice I
would never use it. A better solution is to write func in a more
object-oriented fashion:
class FuncCalculator(object):
"""Class that calculates func"""
def __init__(self):
# Start with initial state.
self.STATE = (1, 0.0)
def __call__(self):
step, partial = self.STATE
step += 1
partial = self.do_some_calculations(step, partial)
self.STATE = (step, partial)
return partial
def do_some_calculations(self, step, partial):
return partial + 1.0/step # or whatever
Instances of the class are callable as if they were functions. In C++
terminology, this is called a "functor". In Python, we normally just
call it a callable.
So we can create as many independent "functions" as needed, each with
their own state:
>>> f = FuncCalculator()
>>> g = FuncCalculator()
>>> h = FuncCalculator()
>>> f()
0.5
>>> f()
0.83333333333333326
>>> f()
1.0833333333333333
>>> g()
0.5
>>> g()
0.83333333333333326
>>> h()
0.5
>>> f()
1.2833333333333332
Hope this helps!
--
Steven D'Aprano
More information about the Tutor
mailing list