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?
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:
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.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.
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)
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?
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?
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 ;-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With