TLDR: You are only loading the module, not the contained class.
Note that packages and modules are mostly just arbitrary namespaces to Python. There is no strong mapping of a class to its contained module. Having a module dog_input
implement the class DogInput
does not make one the alias of another - DogInput
is a regular member of dog_input
, and the later may contain arbitrary other classes and values.
When you know where the class is located, the straightforward approach is to import the module, then fetch the class from it:
module = importlib.import_module("bigpackage.animals.dog_input")
class_ = getattr(module, "DogInput")
print(class_)
If you have only the class name but a consistent naming scheme, you can extract the module name from the class name. See this question on converting CamelCase to lower-case-with-underscores.
submodule_name = convert("DogInput")
module = importlib.import_module("bigpackage.animals.%s" % submodule_name)
class_ = getattr(module, "DogInput")
print(class_)
Note that this is generally frowned upon in Python. You implicitly rely on every maintainer knowing your naming convention - this can break easily, to say the least.
You can also have people provide a qualified name - dog_input.DogInput
instead of just DogInput
. Depending on how much nesting you allow (modules and/or content) this reaches from simple to very complex.
# relative module.class
qname = "dog_input.DogInput"
# add the module part to the import
module = importlib.import_module("bigpackage.animals.%s" % qname('.')[0])
# fetch the class part from the module
class_ = getattr(module, qname('.')[1])
print(class_)
This is a very powerful but also vulnerable approach. Use it if input comes from trusted, experienced users that need a lot of flexibility. Since it may allow executing arbitrary code, do not use it for public services.
If there is a fixed/known set of allowed classes, it is easiest to explicitly index them. This gives you a implementation flexibility as well as safety.
# bigpackage.animals
from dog_input import DogImport
# mapping from identifier to class
ANIMALS = {'DogImport': DogImport}
# bigpackage.server
from bigpackage.animals import ANIMALS
class_ = ANIMALS['DogImport']
print(class_)
This gives the most flexibility to you, but limits what users can do. It is ideal if code changes are required in the future, and users are external. Note that you may build the mapping dynamically, e.g. registering classes via decorators or entry_points.