linluk linluk - 5 months ago 13
Python Question

Is it possible to modify a class with a decorator

I am writing a class in python for some settings wich looks like this:

class _CanvasSettings:
def __init__(self, **kwargs):
super().__init__()
self._size_x = _int(kwargs, 'size_x', 320)
self._size_y = _int(kwargs, 'size_y', 240)
self._lock_ratio = _bool(kwargs'lock_ratio', True)

def get_size_x_var(self):
return self._size_x
def _get_size_x(self):
return self._size_x.get()
def _set_size_x(self, value):
self._size_x.set(value)
size_x = property(_get_size_x, _set_size_x)

def get_size_y_var(self):
return self._size_y
def _get_size_y(self):
return self._size_y.get()
def _set_size_y(self, value):
self._size_y.set(value)
size_y = property(_get_size_y, _set_size_y)

def get_lock_ratio_var(self):
return self._lock_ratio
def _get_lock_ratio(self):
return self._lock_ratio.get()
def _set_lock_ratio(self, value):
self._lock_ratio.set(value)
lock_ratio = property(_get_lock_ratio, _set_lock_ratio)


as you can see I add the block:

def get_something_var(self):
return self._something
def _get_something(self):
return self._something.get()
def _set_something(self, value):
self._something.set(value)
something = property(_get_something, _set_something)


For every single setting.

Is it possible to automate this task with a
decorator
?

I would like to do it like this (pseudocode):

def my_settings_class(cls):
result = cls
for field in cls:
result.add_getter_setter_and_property( field )
return result

@my_settings_class
class _CanvasSettings:
def __init__(self, **kwargs):
super().__init__()
self._size_x = _int(kwargs, 'size_x', 320)
self._size_y = _int(kwargs, 'size_y', 240)
self._lock_ratio = _bool(kwargs'lock_ratio', True)

# Done !


Is this possible?

If yes, how?

How to implement the
add_getter_setter_and_property()
method?




Edit:

There is a pretty similar question here: Python Class Decorator

from the answers there I suspect that it is possible to achive something like I have asked, but can you give me a clue on how I could implement the
add_getter_setter_and_property()
function/method?




Note:

the
_int()
,
_bool()
functions just return a tkinter Int/Bool-var eighter from the kwargs if the string (f.e. 'size_x') exist or from the default value (f.e. 320).

Answer

It's certainly possible to do what you want, using setattr to bind the functions and property as attributes of the class object:

def add_getter_setter_property(cls, attrib_name):
    escaped_name = "_" + attrib_name
    setattr(cls, "get_{}_var".format(attrib_name),
            lambda self: return self.getattr(escaped_name))
    setattr(cls, attrib_name,
            property(lambda self: getattr(self, escaped_name).get()
                     lambda self, value: getattr(self, escaped_name).set(value)))

Here I'm skipping giving names to the getter and setter methods used by the property. You could add them to the class if you really want to, but I think it's probably unnecessary.

The tricky bit may actually be finding which attribute names you need to apply this to. Unlike in your example, you can't iterate over a class object to get its attributes.

The easiest solution (from the implementation standpoint) would be to require the class to specify the names in a class variable:

def my_settings_class(cls):
    for field in cls._settings_vars:
        add_getter_setter_and_property(cls, field)
    return cls

@my_settings_class
class _CanvasSettings:
    _settings_vars = ["size_x", "size_y", "lock_ratio"]
    def __init__(self, **kwargs):
        super().__init__()
        self._size_x = _int(kwargs, 'size_x', 320)
        self._size_y = _int(kwargs, 'size_y', 240)
        self._lock_ratio = _bool(kwargs, 'lock_ratio', True)

A more user-friendly approach might use dir or vars to examine the classes variables and pick out the ones that need to be wrapped automatically. You could use isinstance to check if the value has a specific type, or look for a specific pattern in the attribute name. I don't know what is best for your specific use, so I'll leave this up to you.

Comments