In Python, what's the correct way to instantiate a class from a variable?

Learn in python, what's the correct way to instantiate a class from a variable? with practical examples, diagrams, and best practices. Covers python, instantiation development techniques with visua...

Dynamic Class Instantiation in Python: A Comprehensive Guide

Hero image for In Python, what's the correct way to instantiate a class from a variable?

Learn the correct and safe methods to instantiate a Python class when its name is stored in a variable, covering best practices and potential pitfalls.

In Python, you often encounter situations where you need to create an instance of a class, but the class itself is not directly referenced in your code. Instead, its name might be stored in a string variable, perhaps read from a configuration file, a database, or user input. This article explores the various techniques for dynamically instantiating classes from a variable, discussing their use cases, advantages, and security considerations.

The globals() and locals() Approach

Python provides built-in functions globals() and locals() that return dictionaries representing the current global and local symbol tables, respectively. These dictionaries map names to their corresponding objects. If your class is defined in the current scope, you can retrieve it using its name from these dictionaries.

class MyClass:
    def __init__(self, value):
        self.value = value

    def greet(self):
        return f"Hello from MyClass with value {self.value}"

class AnotherClass:
    def __init__(self, name):
        self.name = name

    def identify(self):
        return f"I am AnotherClass, named {self.name}"

class_name_str = "MyClass"

# Using globals()
if class_name_str in globals():
    MyClassRef = globals()[class_name_str]
    instance = MyClassRef(123)
    print(instance.greet())

class_name_str_2 = "AnotherClass"

# Using locals() (if defined in local scope, e.g., inside a function)
def create_instance_locally(cls_name_str, *args, **kwargs):
    if cls_name_str in locals():
        ClassRef = locals()[cls_name_str]
        return ClassRef(*args, **kwargs)
    return None

# For this example, AnotherClass is global, so locals() won't find it directly here
# but if it were defined inside create_instance_locally, this would work.
# For demonstration, we'll use globals() again.
if class_name_str_2 in globals():
    AnotherClassRef = globals()[class_name_str_2]
    instance_2 = AnotherClassRef("Dynamic")
    print(instance_2.identify())

Instantiating classes using globals() and locals().

The getattr() Approach for Module-Level Classes

A more robust and common method for instantiating classes from a variable, especially when they reside within a module (either the current one or an imported one), is to use the getattr() function. This function allows you to retrieve an attribute (which can be a class) from an object (which can be a module) by its string name.

# Assume MyClass and AnotherClass are defined in the current module (e.g., main.py)

import sys

class MyClass:
    def __init__(self, value):
        self.value = value

    def greet(self):
        return f"Hello from MyClass with value {self.value}"

class AnotherClass:
    def __init__(self, name):
        self.name = name

    def identify(self):
        return f"I am AnotherClass, named {self.name}"


def instantiate_from_module(module_name, class_name_str, *args, **kwargs):
    try:
        module = sys.modules[module_name] # Get the module object
        if hasattr(module, class_name_str):
            ClassRef = getattr(module, class_name_str)
            if isinstance(ClassRef, type): # Ensure it's actually a class
                return ClassRef(*args, **kwargs)
            else:
                print(f"'{class_name_str}' in module '{module_name}' is not a class.")
        else:
            print(f"Class '{class_name_str}' not found in module '{module_name}'.")
    except KeyError:
        print(f"Module '{module_name}' not found.")
    return None

# Example usage:
instance_1 = instantiate_from_module(__name__, "MyClass", 456)
if instance_1:
    print(instance_1.greet())

instance_2 = instantiate_from_module(__name__, "AnotherClass", "Module-Loaded")
if instance_2:
    print(instance_2.identify())

# Example of a non-existent class
non_existent = instantiate_from_module(__name__, "NonExistentClass")

Using getattr() with sys.modules for dynamic instantiation.

flowchart TD
    A[Start: Class Name as String] --> B{Determine Module Source?}
    B -- Yes, current module --> C1[Get current module object (e.g., `sys.modules[__name__]`)]
    B -- Yes, specific module --> C2[Import module (e.g., `importlib.import_module`)]
    B -- No, global/local scope --> C3[Check `globals()` or `locals()`]

    C1 --> D{Retrieve Class using `getattr(module, class_name)`}
    C2 --> D
    C3 --> D_alt{Retrieve Class using `dict[class_name]`}

    D --> E{Is retrieved object a class?}
    D_alt --> E

    E -- Yes --> F[Instantiate Class: `ClassRef(*args, **kwargs)`]
    E -- No --> G[Error: Not a class or not found]

    F --> H[Success: Class Instance]
    G --> I[Failure: Handle Error]
    H --> J[End]
    I --> J

Flowchart for dynamic class instantiation strategies.

For truly dynamic loading of classes from arbitrary modules, the importlib module is the most Pythonic and recommended solution. It provides an API for programmatically importing modules, which is crucial when the module name itself is also dynamic. This approach is cleaner and more explicit than directly manipulating sys.modules or relying on __import__.

# Assume you have a file named 'my_module.py' with the following content:
#
# # my_module.py
# class ExternalClass:
#     def __init__(self, data):
#         self.data = data
#
#     def get_data(self):
#         return f"Data from ExternalClass: {self.data}"
#
# class AnotherExternalClass:
#     def __init__(self, id):
#         self.id = id
#
#     def get_id(self):
#         return f"ID from AnotherExternalClass: {self.id}"

import importlib

def instantiate_from_path(module_path, class_name_str, *args, **kwargs):
    try:
        module = importlib.import_module(module_path)
        if hasattr(module, class_name_str):
            ClassRef = getattr(module, class_name_str)
            if isinstance(ClassRef, type):
                return ClassRef(*args, **kwargs)
            else:
                print(f"'{class_name_str}' in module '{module_path}' is not a class.")
        else:
            print(f"Class '{class_name_str}' not found in module '{module_path}'.")
    except ImportError:
        print(f"Module '{module_path}' could not be imported.")
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
    return None

# Example usage:
# Make sure 'my_module.py' is in your Python path or current directory
instance_ext_1 = instantiate_from_path("my_module", "ExternalClass", "hello")
if instance_ext_1:
    print(instance_ext_1.get_data())

instance_ext_2 = instantiate_from_path("my_module", "AnotherExternalClass", id=101)
if instance_ext_2:
    print(instance_ext_2.get_id())

# Example with a non-existent module or class
non_existent_module = instantiate_from_path("non_existent_module", "SomeClass")
non_existent_class = instantiate_from_path("my_module", "UnknownClass")

Using importlib.import_module() for dynamic class instantiation from a specified module.

Security Considerations and Best Practices

Dynamic class instantiation, while powerful, carries inherent risks if not handled carefully. The primary concern is security, especially when the class name or module path comes from external, untrusted input. Always prioritize safety and maintainability.

1. Whitelist Allowed Classes

Instead of allowing any string to be instantiated, maintain a whitelist (a dictionary or set) of allowed class names and their corresponding class objects. This provides a controlled environment.

2. Validate Inputs Rigorously

If class names or module paths are user-provided, ensure they conform to expected patterns (e.g., valid Python identifiers, no path traversal attempts). Regular expressions can be useful here.

3. Use importlib Over __import__ or eval()

importlib is the official and safest way to programmatically import modules. Avoid __import__ directly, and absolutely avoid eval() for dynamic class loading, as eval() can execute arbitrary code.

4. Handle Exceptions Gracefully

Wrap your dynamic loading logic in try-except blocks to catch ImportError, AttributeError, TypeError, and other potential issues, providing informative error messages.

5. Limit Scope and Permissions

If possible, run code that performs dynamic loading in an environment with limited permissions, especially if dealing with potentially untrusted inputs.

By following these guidelines, you can leverage the flexibility of dynamic class instantiation in Python while mitigating the associated risks, leading to more robust and secure applications.