6

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.

6
  • Not knowledgeable in Python, but in Java there was a way to create an instance from only its class name. Is there no such way in Python? – Neil Jun 22 '17 at 6:58
  • 2
    It depends. If classes are in the same module, globals()[class_name] will do the trick. The question is rather how to do it less hackish... – Roman Susi Jun 22 '17 at 8:10
  • One more idea, which came after the first answer appeared, is to confine lookup to subclasses, eg, Shape: and use technique like in the following answer stackoverflow.com/questions/3862310/… and eg add a classmethod to Shape str_to_class, what do you think? – Roman Susi Jun 22 '17 at 8:16
  • Can it be absolutely any class, or only some subset of a base class (eg: a subclass of Plugin or Driver)? – Bryan Oakley Jul 1 '17 at 12:41
  • Subclass or a set of subclasses is also fine. – Roman Susi Jul 1 '17 at 18:35
11

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.

1
  • "if else solution you have is understandable" - good point. – Roman Susi Jun 22 '17 at 8:15
4

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()
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.

2
  • I guess it should be return MyClass.variety_versions.get(variety_id, cls) – Jeril Nov 16 '20 at 2:36
  • This depends on the case. Namely, whether you want to choose default variety this way. In my cases default variaty was None – Roman Susi Nov 16 '20 at 4:51
1

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()).

2
  • This is fragile. If I rename Circle to Oval I will need to know to also change "Circle" to "Oval". So basically, I can't leverage IDE support fro automatic refactorings. Same for having "pathes to module" in the code: someone moves the module and forgets to change the path in some other part of code. So while technically this would work, this solution's developer ergonomics is low. – Roman Susi Jul 26 '19 at 10:36
  • Agreed: this is for specialized applications under a controlled environment (such as a 'class dispatcher' in an object factory). "With power comes responsibility:" if one wants general IDE support, the best recommendation is to be simple and to stick to conventional references to classes. – fralau Jul 26 '19 at 13:29
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
4
  • How this is different from what is in the question already? – Roman Susi Jul 7 '17 at 3:55
  • It encapsulates the "ugly code" in a class, which removes the unnecessary duplication and allows for the code to be easily refactored, e.g., into a dictionary lookup as in another answer if there are many different options for classes, or to add logic to, e.g., pull initialization parameters from somewhere else. As I stated, this is more useful in situations where the creation logic becomes more complex. – Evan Wise Jul 12 '17 at 20:30
  • Ah, ok. My bad: I omitted class and method def around the if in the question for brevity. (but I wonder whoever downvoted your answer can explain?) – Roman Susi Jul 14 '17 at 5:09
  • how is this different, or better than, the approach using a dictionary to hold type -> class mapping? – Sajuuk May 29 '19 at 3:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.