Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inserting a dictionary into a python class using a decorator

I am trying to design a wrapper that can accept the name of a class (not an object) and insert a dictionary into each instance of the class. Below is a snippet of how I achieve this when I am wrapping an existing function.

def insert_fn_into_class(cls):
    """Decorator function that consumes a function and inserts it into the cls class."""
    def decorator(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            return func(*args, **kwargs)
        setattr(cls, f'prefix_{func.__name__}', wrapper)

    return decorator

How can I use a similar template to decorate a dictionary and insert it into the cls class. Since dictionaries are not callable, how would such a wrapper be designed?

Update

Thank you for the constructive feedback on this. A fellow stack-overflow user rightly pointed out that I failed to explain WHY I would want to do this. So here goes:

  1. I am trying to build a framework that can essentially consume a bunch of user defined functions and extend an existing class. So I have a class A, and user-defined functions f_1, f_2, ..., f_n which I want to inject into class A such that an instance of this class obj_a = A() can call these functions such as obj_a.f_1(). I have managed to achieve this using a decorator function similar to the code snippet above.
  2. Now, every user defined function has some repetitive code that I think I can do away with if all instances of my base class A can have access to a user defined dictionary. My thought process for achieving this was to try and modify my existing wrapper function to add it to the class. However, I am aware that dictionaries are not callable and hence, the question.

I hope this was sufficiently elaborate.

Update 2

Looks like there was scope for some more elaboration. Here is an example of how the various components that I described earlier looks like.

module_a.py

class BaseClass():
    def __init__(self):
        ...

    def base_class_method(self):
        ...
    
    def run(self):
        getattr(self, 'prefix_user_fn_1')()

def extend(cls=BaseClass):
    def decorator(func):
        def wrapper(self, *args, **kwargs):
            return func(*args, **kwargs)
        setattr(cls, f'prefix_{func.__name__}', wrapper)
    return decorator

user_defined_functions.py

from module_a import BaseClass, extend

@extend()
def user_fn_1():
    dict_ = {'a':'b', 'c':'d'}
    ...

@extend()
def user_fn_2():
    dict_ = {'my':'dict', 'c':'d'}
    ...

main.py

from module_a import BaseClass

b = BaseClass()
b.run()

Each user function is contains a subset of a commonly used dictionary. To take advantage of this, I think it would be convenient if this can be accessed as part of BaseClass's attributes injected dynamically.

modified_user_defined_functions.py

# THIS WILL NOT WORK FOR OBVIOUS REASONS
from module_a import BaseClass, extend, common_config, insert_config_into_class

# @common_config ?? Is this even a good idea?
dict_ = {'my':'dict', 'a':'b', 'c':'d'}
insert_config_into_class(BaseClass, dict_)

@extend()
def user_fn_1(self):
    print(self.dict_)
    # do something with self.dict_
    ...

To incorporate this, I might have to change the run method on BaseClass to look like this:

modified_module_a.py

...
class BaseClass():
    ...
    
    def run(self):
        getattr(self, 'prefix_user_fn_1')(self)

Update 3 - Potential Solution

def shared_config(data, cls=BaseClass):
    setattr(cls, 'SHARED_DICT', data)

This solution works, but also somewhat answers my question for which I think I can say that maybe I was overengineering my solution by writing a decorator for this when a simple function could possibly achieve this.

However, an aspect of the original question still remains - Is this a good approach?

like image 741
cyberbeast Avatar asked Apr 19 '26 10:04

cyberbeast


2 Answers

The way you're asking questions gives me a hint that you don't quite understand how python uses object references.

Decorators aren't meant to be used with non-callable objects, so it's clarification

accept the name of a class (not an object)

is not really clear. And there's no difference in what you want to attach to class — dict or function, you'll only manipulate abstract reference anyway.

Apart from all that, take a look at this snippet:

def patcher(cls):
    def wrapper(*args, **kwargs):
        instance = cls(*args, **kwargs)
        instance.payload = {'my': 'dict'}
         return instance
    return wrapper

@patcher
class A:
    pass

Does it cover your needs?

like image 144
Slam Avatar answered Apr 22 '26 00:04

Slam


I think I can say that maybe I was overengineering my solution

Well, kind of indeed... FWIW the whole thing looks quite overcomplicated.

First thing first: if you define your dict as a global (module) name, all functions within that module can directly access it:

# simple.py

SHARED_DATA = {"foo": "bar", "answer": 42}

def func1():
    print SHARED_DATA["foo"]

def func2():
    print SHARED_DATA["bar"]

so I don't really see the point of "injecting" this in a class imported from another module just to access it from those functions.

Now with your decorator:

def extend(cls=BaseClass):
    def decorator(func):
        def wrapper(self, *args, **kwargs):
            return func(*args, **kwargs)
        setattr(cls, f'prefix_{func.__name__}', wrapper)
    return decorator

if the goal is to make the function an attribute of the class but without turning it into an instance method, you can just use staticmethod

def extend(cls=BaseClass):
    def decorator(func):
        setattr(cls, f'prefix_{func.__name__}', staticmethod(func))
        return func
    return decorator

Now if you have other (unexplained) reason to make SHARED_DICT a class attribute (of BaseClass or another class), you can indeed provide a configuration function:

# module_a
def configure(cls, data):
    cls.SHARED_DATA = data


# less_simple.py

from module_a import BaseClass, configure

SHARED_DATA = {"foo": "bar", "answer": 42}
configure(BaseClass, SHARED_DATA)

def func1():
    print SHARED_DATA["foo"]

def func2():
    print SHARED_DATA["bar"]

But note that you still don't need to go thru BaseClass nor self to access this dict from the module's functions.

You could of course pass either the class or instance to your used-defined functions but here again, no need for a bizarre contraption - either make them classmethod or just directly set them as attributes of the class, Python will take care of injecting the class or instance as first argument (here an example making them instance methods):

# module_a
def configure(cls, data):
    cls.SHARED_DATA = data

def extend(cls=BaseClass):
    def decorator(func):
        setattr(cls, f'prefix_{func.__name__}', func)
        return func
    return decorator

# rube_goldberg.py

from module_a import BaseClass, configure, extend

SHARED_DATA = {"foo": "bar", "answer": 42}
configure(BaseClass, SHARED_DATA)

@extend
def func1(self):
    print SHARED_DATA["foo"]

@extend
def func2(self):
    print SHARED_DATA["bar"]

Note that this is still totally useless from the decorated functions POV - they don't need self at all to access SHARED_DATA, but now they cannot be executed without a BaseClass instance as first argument so the user cannot test them directly.

Now maybe there are other things you didn't mention in your question, but so far it looks like you're trying really hard to make simple things complicated ;-)

like image 28
bruno desthuilliers Avatar answered Apr 22 '26 01:04

bruno desthuilliers



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!