0

I'm trying to build a class in python in which variable assignment and retrieval need to be done through a different class with its logic for set_value and get_value.

MyObj class gives the logic for set_value and get_value

Class MyObj:
    def __init__(self):
        self.value = 0

    def set_value(self, value):
        self.value = value

    def get_value(self):
        return self.value

The user creates a MyClass object and sets/gets values to these variables, but the MyObj class will be 100% abstracted from the user.

class MyClass:
    item1 = MyObj()
    item2 = MyObj()
    item3 = MyObj()

    def __setattr__(self, key, value):
        print(f"set logic :: {key}, {value}")
        # key.set_value(value)

    def __getattribute__(self, item):
        print(f"get logic :: {item}")
        # return item.get_value()

Myclass will behave like any other python class but with setter and getter logic coming from MyObj.

cls = MyClass()
cls.item1 = 10 # Issue: this should not replace variable value from class object.
print(cls.item1) # Issue: this should not return class object

Issue:

  1. Currently, this will be done through the __setattr__ and __getattribute__ methods, but I can't get the code working as the parameters are in the string.
  2. I don't want to manually type getter and setter for each variable in MyClass.
  3. User should be able to read and write variables of MyClass like standard python class variables.
  4. https://github.com/ramazanpolat/prodict this lib does something similar but not what I am looking for.
  • It seems like you are describing [Descriptors](https://docs.python.org/3/howto/descriptor.html#managed-attributes), but you're using different methods. It's not clear (to me) if you need something other that what descriptors do. – Mark Oct 12 '22 at 20:53

1 Answers1

0

While this can be done with clever use of __getattribute__ and __setattr__, it's going to be more trouble that way. What it sounds like you want is descriptors.

class MyObj:
    def __init__(self):
        self._name = ''

    def __set_name__(self, owner, name):
        self._name = '__MyObj_' + name

    def __set__(self, instance, value):
        setattr(instance, self._name, value)

    def __get__(self, instance, owner):
        return getattr(instance, self._name, 0)

class MyClass:
    item1 = MyObj()
    item2 = MyObj()
    item3 = MyObj()

Suppose we have a MyClass instance called x. When we try to access x.item1, Python notes that x.__dict__ (the dictionary of instance variables for x) does not contain item1. So instead it looks at x's class, which is MyClass. MyClass.item1 is defined, so Python is going to return that. However, we accessed it as an instance variable, so in fact what Python is going to give us is MyClass.item1.__get__(x, MyClass). So whenever we write x.item1, Python will call __get__ on MyObj. Likewise for item2 and item3. Note that this is the same technique that Python uses to bind self on function objects inside of the class. The function type itself has a __get__ which returns a bound method object, not the original function. That's why x.some_method(1) is equivalent to MyClass.some_method(x, 1). It's because it's actually equivalent to MyClass.some_method.__get__(x, MyClass)(1) which translates into the former.

Similarly, when we try to assign to x.item1 with x.item1 = new_value, Python will call MyClass.item1.__set__(x, new_value), so there's your setter. We can also write a __del__ which would work if we tried to use the del syntax with x.item1, though you didn't mention deleters, so I omit it here.

The only point of note is that, using this technique, there is only one MyObj for each field on the class, not one for each instance. So, in the example I posted above, I use __set_name__ and a clever naming convention to place the backing field in the MyClass instance's __dict__. If all you want to do is return a synthetic value and not actually have a backing field, you won't need that. And depending on your use case, property might work for you and save you some typing.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
  • One issue I see with this solution: Let's say I add a method `def ok(self): print("ok")` in `MyObj` and then try to access it from `MyClass`: `if __name__ == '__main__': cls = MyClass() cls.item1 = 10 print(cls.item1) ` now trying to access ok method from MyClass `__init__` :: `def __init__(self): members = [getattr(self, attr) for attr in dir(self) if not attr.startswith("__")] print(members[0].ok())` print line will give error as item1 is int –  Oct 13 '22 at 05:03
  • Basically, I want the user to get only int, but from Inside `MyClass`, I want to control the `MyObj` class fully. –  Oct 13 '22 at 05:09
  • My above comments requirement can be fulfilled by writing getter and setter for each variable of the class, but there are many variables I don't want to write getter and setter for all. –  Oct 13 '22 at 15:46
  • There is no "inside the class" in Python. Methods defined inside the class aren't special. They have a first argument that is conventionally called `self`, but that's not a keyword. As far as the inside of the function is concerned, `self` is the name of an ordinary variable that happens to point to an instance of a particular class. – Silvio Mayolo Oct 13 '22 at 21:37