10

Lets assume I have a string for a class name (or part of a class name), and I have implementation somewhere. All I want, is to be able to get an instance of a class with the name in the variable.

For example,

dynamic_class_name = "Circle"  # not a constant, comes from somewhere
corresponding_class = some_magic(dynamic_class_name)
corresponding_instance = corresponding_class()

I can think of several approaches from using __import__() function, module attribute lookups, adding register to classes, other ways of using namespaces, making auto-registering via metaclassing, all the way to using some heavy component architecture and interfaces...

The question is, what would be the simplest but elegant (understandable, intuitive, regular, not hackish) way to replace ugly code like?

if dynamic_class_name == "Circle":
  inst = Circle()
elif dynamic_class_name == "Square":
  inst = Square()
...

The bonus would be to have minimal impact on IDE's (like PyCharm) ability to infer types and instances.

Also a plus is that there is no need to have a special list with the classes in one place, so new classes can be drop in.

Roman Susi
  • 1,803

5 Answers5

14

One idea off the top of my head would be to create a dictionary holding your classes. This would mean that you can have the instantiation and error handling on very few lines.

>>> class Circle():
...     pass
... 
>>> class Square():
...     pass
... 
>>> d = { "square": Square, "circle": Circle}
>>> d["square"]()
<__main__.Square instance at 0x7ffbb1408e60>

I can't really say that it's very clear what's happening, but good naming and a few helpful comments would likely alleviate that; such as calling your dictionary instantiators or something similar. You will also need to collect the classes in some manner.

Here are some other suggestions:

You could potentially use one of the methods mentioned in those answers and wrap it up in a function with a descriptive name.

Having written some dynamic code of similar style in other languages I find that you typically, unsurprisingly, end up with code that is harder to read and much more complex in general. For all its inelegance the if else solution you have is understandable for everyone who knows even a little bit of basic programming.

Robzor
  • 898
6

I find putting them in a module that you import is cleaner and doesn't pollute the global namespace. Then you can use getattr to dynamically instantiate them.

import shapes
shape_class = getattr(shapes, dynamic_class_name.capitalize())
inst = shape_class()
Paul H.
  • 61
4

Dynamically instantiating objects from a class name makes sense in some specialized cases, typically in cases where a constructor function or method needs to "route" an object to the most suitable class for the purpose. Storing the class references as strings can make sense, especially to avoid issues of references in the source code to yet undeclared classes (or subclasses). At execution time, there will be no such issues, since "whatever is already there, is there".

For a general and simple approach:

1. If the class is defined or explictly imported in the source code:

see also: https://stackoverflow.com/questions/2226330/instantiate-a-python-class-from-a-name

The class is an attribute of globals.

dynamic_class_name = "Circle" 
dynamic_class = globals[dynamic_class_name]
newobject = dynamic_class(....)

or shorthand:

newobject = globals[dynamic_class_name](...)

2. If the class is in some module you have only imported (not the content)

see: https://stackoverflow.com/a/4821120/

The class is an attribute of the module:

newobject = gettatr(module, dynamic_class_name)(...) 

3. If you want to refer to a module in any package

See: https://stackoverflow.com/a/55968374

You might use locate(...):

from pydoc import locate
my_class = locate("module.submodule.myclass")
instance = my_class()

A word of caution: with power comes responsibility. If one goes out of the beaten path, one should not count on the Python interpreter, IDEs or other tools to come to the rescue with meaningful flags, or help in code refactoring. The recommendation is to use this approach in specialized cases, where there exists a well-defined set of candidate classes. These could be listed in a list variable, or localized in a specific module, etc. Also, it would be wise to make your own handling of exception/corner cases, particularly if the class cannot be found, and some type of control on which classes are compatible to your own use case (by duck typing or using issubclass()).

fralau
  • 181
2

I ended up using the following arrangement:

class MyClass(object):
    variety = None
    variety_versions = {}

    @classmethod
    def register(cls):
        MyClass.variety_versions[cls.variety] = cls

    @classmethod
    def for_variety(cls, variety_id):
        return MyClass.variety_versions.get(variety_id, MyClassDefaultVariety)

...

class MyClassSomething(MyClass):
    variety = 'MyClassSomething'
    ...
MyClassSomething.register()

...
# Usage:
inst = MyClass.for_variety(dynamic_class_variety)(...)

That is, class attribute is used to hold a registry of classes. The register class method is used to add class to the registry. Another class method for_variety is used to get the class. It is trivial to add duplicated variety check, generalize solution for more than one variety attribute, etc. The .register() can be replaced with class decorator.

Roman Susi
  • 1,803
0

Depending on the number of different classes you need to instantiate and the complexity of the logic, this could be a good use case for the Factory design pattern. A factory class encapsulates the creation logic for a group of classes which all share a common interface. This has the benefit of being more extensible if it later turns out you need more logic to determine which class should be instantiated. For example:

class MyClass(object):
    pass

class AClass(MyClass):
    pass

class BClass(MyClass):
   pass

class ClassFactory(object):

    def create(self, type):
        if type == "a":
            return AClass()
        elif type == "b":
            return BClass()

factory = ClassFactory()
object = factory.create("a")
isinstance(object, MyClass) # True
isinstance(object, AClass) # True