Your actual problem here is pretty simple. And I'll explain how to fix it. But really, you should not be doing this. Whatever you're trying to do, you're doing it wrong.
It's not that there's no reason anyone could ever possibly want to do such a thing. But I'm pretty confident that anyone who uses exec
because he doesn't know you can pass functions around as first-class values, calls __import__
with a literal module name, etc. does not have such a reason. If you explain what you're actually trying to do, we can explain it.
But meanwhile:
When you do the first import
, it's creating a mycode.pyc
file, and the reload
is just reloading that file.
You can verify this by removing mycode.pyc
, then running python -B reloadtest.py
instead of python reloadtest.py
. And that obviously gives you one possible fix.
Alternatively, you can explicitly os.unlink('mycode.pyc')
right before calling reload
. I think most people would consider this horribly unpythonic, but I don't think it's any more so than what you're trying to do in the first place.
Finally, if you created a new mycode.py
file instead of rewriting the existing file in place, I think that would solve the problem.
Meanwhile, your code is exactly equivalent to the following. (To address your confusion in the comments: Exactly identical means it will have the exact same problem, and the exact same solutions will solve it.) It's also a lot simpler, more pythonic, and easier to debug, because it only involves one huge antipattern (using reload anywhere but the interactive interpreter) instead of four:
str1 = '''
def run():
return 10
'''
with open('mycode.py', 'w') as f:
f.write(str1)
import mycode as mymodule
print "number", mymodule.run()
str2 = '''
def run():
return 99
'''
with open('mycode.py', 'w') as f:
f.write(str2)
reload(mymodule)
print "number", mymodule.run()
In your edit, you explain that you're using exec
because you want to pass a dynamically-variable number of parameters:
It can be run(x,y) or run(larry, moe, curly, hickory, dickory, dock). How do I dynamically pass multiple parameters to run()?
Simple. Just put x, y
or larry, moe, curly, hickory, dickory, dock
into a tuple
, list
, or other sequence, and use run(*seq)
.
For example, I assume you have code something like this:
es = 'number = mymodule.run({})'.format(','.join(params))
exec(es)
You can replace that with:
number = mymodule.run(*params)
In fact, almost any case you can imagine where you think you need exec
, there's a better way to do it. If you can't figure it out, you can do a quick search on Google or StackOverflow and, failing that, just ask how to do it. Using exec
for dynamic code generation is idiomatic for tcl, and used to be for PHP and JavaScript years ago, but it's almost never the right thing to do in Python.
And, as you can probably guess by now, the same is true for dynamically creating modules to import
/reload
/execfile
/etc.
For example, your real code is probably trying to do something like this:
def make_new_module(n):
str = '''
def run():
return {}
'''.format(n)
f = open('mycode.py','w')
f.write(str1)
f.close()
return __import__('mycode')
But you can just define the function on the fly:
def make_new_function(n):
def run():
return n
return run
If you really need it to be named mymodule.run
, you can easily create a namespace like a class, class instance, namedtuple
, etc. to stick it in.
You can even create a new module on the fly, if you really need to (although I'm willing to bet you don't need to):
import types
def make_new_module(n):
def run():
return n
mymodule = types.ModuleType('mymodule')
mymodule.run = run
return mymodule
And rebinding a function in an existing module is even easier:
def change_mymodule_run(n):
def run():
return n
mymodule.run = run
And yes, modules are first-class values, just like functions, so you can do it just as easily if module
is a dynamic parameter instead of a static value:
def change_module_run(module, n):
def run():
return n
module.run = run