Python: Dynamic "from" import
Question:
So i’m trying to turn a bunch of “from x import x” statements, that look like this:
from class_foo import class_foo
into something dynamic. I’m trying to pass a path to a directory and have it import all of the modules therein.
def dynamicImport(dirPath):
filez = os.listdir(dirPath)
for file in filez:
if "class" in file:
oname = file[:-3] #cut off the file extension, trivial
imp_statement = "from " + oname + " import " + oname
#when I print imp_statement, I can verify it's being concatenated correctly
exec(imp_statement)
When I run this function and pass it a path, the statement strings are being created properly and it produces no errors, yet then later I’ll try to access one of the imported objects, and this happens:
foo = class_foo()
NameError: name 'class_foo' is not defined
Clearly I’m doing something wrong. Any help would be appreciated.
Answers:
Take a look at the __import__ function
It’s been a long time since I’ve worked with Python. But I think your problem might lay in the fact that “oname” is a string. The line from class_foo import class_foo
is not a string. One painstaking option would be to have your code make a whole new .py file that would have all of the imports. So you would write all of your current file plus the new imports to, basically, a text file ending in .py
You’re exec
ing your import statement in your function’s local namespace, so that’s where the names are defined. This namespace goes away when the function ends, leaving you with nothing. What you probably want is something like exec imp_statement in globals()
.
Why not just use __import__()
instead of string-munging? Then you get a reference to your module. You can then fish out the class reference using getattr()
on the module object and insert that into globals()
(or just pass a dictionary back to the caller, who can then do globals().update()
with it).
import sys, os
def getClasses(directory):
classes = {}
oldcwd = os.getcwd()
os.chdir(directory) # change working directory so we know import will work
for filename in os.listdir(directory):
if filename.endswith(".py"):
modname = filename[:-3]
classes[modname] = getattr(__import__(modname), modname)
os.setcwd(oldcwd)
return classes
globals().update(getClasses(r"C:plugin_classes"))
Something like that. Or rather than updating globals()
with your modules, which could clobber a global variable you care about, just leave the classes in the dictionary and reference them from there:
classes = getClasess(r"C:plugin_classes")
for clas in classes.itervalues():
instance = clas(1, 2, 3) # instantiate
instance.dosomething_cool(42) # call method
Python >= 2.7 has importlib (you can pip install importlib to use importlib in earlier versions of python)
module = importlib.import_module("path.to.module")
MyClass = module.MyClass
Suppose your directory structure looks like this:
./ <- you are here
- main.py
- my_package/
- __init__.py
- my_module.py
and you want to dynamically import my_module.py
, to use some of its functions, classes, whatever. Then, using importlib
, you can use the following code in main.py
:
import importlib
pack = "my_package"
mod = "my_module"
module = importlib.import_module("." + mod, pack)
# or, alternatively
module = importlib.import_module(".".join(pack, mod))
module.func("hello") # if my_package/my_module.py defines function "func"
obj = module.MyClass("world") # if my_package/my_module.py defines class "MyClass"
So i’m trying to turn a bunch of “from x import x” statements, that look like this:
from class_foo import class_foo
into something dynamic. I’m trying to pass a path to a directory and have it import all of the modules therein.
def dynamicImport(dirPath):
filez = os.listdir(dirPath)
for file in filez:
if "class" in file:
oname = file[:-3] #cut off the file extension, trivial
imp_statement = "from " + oname + " import " + oname
#when I print imp_statement, I can verify it's being concatenated correctly
exec(imp_statement)
When I run this function and pass it a path, the statement strings are being created properly and it produces no errors, yet then later I’ll try to access one of the imported objects, and this happens:
foo = class_foo()
NameError: name 'class_foo' is not defined
Clearly I’m doing something wrong. Any help would be appreciated.
Take a look at the __import__ function
It’s been a long time since I’ve worked with Python. But I think your problem might lay in the fact that “oname” is a string. The line from class_foo import class_foo
is not a string. One painstaking option would be to have your code make a whole new .py file that would have all of the imports. So you would write all of your current file plus the new imports to, basically, a text file ending in .py
You’re exec
ing your import statement in your function’s local namespace, so that’s where the names are defined. This namespace goes away when the function ends, leaving you with nothing. What you probably want is something like exec imp_statement in globals()
.
Why not just use __import__()
instead of string-munging? Then you get a reference to your module. You can then fish out the class reference using getattr()
on the module object and insert that into globals()
(or just pass a dictionary back to the caller, who can then do globals().update()
with it).
import sys, os
def getClasses(directory):
classes = {}
oldcwd = os.getcwd()
os.chdir(directory) # change working directory so we know import will work
for filename in os.listdir(directory):
if filename.endswith(".py"):
modname = filename[:-3]
classes[modname] = getattr(__import__(modname), modname)
os.setcwd(oldcwd)
return classes
globals().update(getClasses(r"C:plugin_classes"))
Something like that. Or rather than updating globals()
with your modules, which could clobber a global variable you care about, just leave the classes in the dictionary and reference them from there:
classes = getClasess(r"C:plugin_classes")
for clas in classes.itervalues():
instance = clas(1, 2, 3) # instantiate
instance.dosomething_cool(42) # call method
Python >= 2.7 has importlib (you can pip install importlib to use importlib in earlier versions of python)
module = importlib.import_module("path.to.module")
MyClass = module.MyClass
Suppose your directory structure looks like this:
./ <- you are here
- main.py
- my_package/
- __init__.py
- my_module.py
and you want to dynamically import my_module.py
, to use some of its functions, classes, whatever. Then, using importlib
, you can use the following code in main.py
:
import importlib
pack = "my_package"
mod = "my_module"
module = importlib.import_module("." + mod, pack)
# or, alternatively
module = importlib.import_module(".".join(pack, mod))
module.func("hello") # if my_package/my_module.py defines function "func"
obj = module.MyClass("world") # if my_package/my_module.py defines class "MyClass"