Search code examples
pythonpropertiessettermetaclass

How to turn python method into setter with the help of metaclass?


I'm new to metaclasses, so I'm sorry, If my question is somehow stupid. I need to make a metaclass, that takes particular methods of a class and turns them into property methods or setters. So, if I have

class TestClass():
    def __init__(self, x: int):
        self._x = x

    def get_x(self):
        print("this is property")
        return self._x

    def set_x(self, x: int):
        print("this is setter")
        self._x = x

I want it to work like this:

class TestClass():
    def __init__(self, x: int):
        self._x = x

    @property
    def x(self):
        print("this is property")
        return self._x

    @x.setter
    def x(self, x: int):
        print("this is setter")
        self._x = x

For now I've just realized how to make it for property:

class PropertyConvert(type):
    def __new__(cls, future_class_name, future_class_parents, future_class_attr):
        new_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                if name.startswith('get_'):
                    new_attr[name[4:]] = property(val)
                if name.startswith('set_'):
                    # ???
            else:
                new_attr[name] = val
        return type.__new__(cls, future_class_name, future_class_parents, new_attr)

But I can't figure out how to do it for setters.


Solution

  • I highly recommend docs about descriptors, they are really nice written with many similar examples explained.

    But answering your question, to make a setter work you need to use the same property function but fill second arguments.

    class property(fget=None, fset=None, fdel=None, doc=None)

    So code could look like that:

    class PropertyConvert(type):
        def __new__(cls, future_class_name, future_class_parents, future_class_attr):
            new_attr = {}
            fget, fset, property_name = None, None, None
            for name, val in future_class_attr.items():
                if not name.startswith("__"):
                    if name.startswith("get_"):
                        property_name = name[4:]
                        fget = val
                    if name.startswith("set_"):
                        property_name = name[4:]
                        fset = val
                else:
                    new_attr[name] = val
            if n:
                new_attr[property_name] = property(fget, fset)
            return type.__new__(cls, future_class_name, future_class_parents, new_attr)