Search code examples
pythonclassooppython-descriptors

Properties vs. generic setter/getter and descriptor


I can't seem to find a definitive answer on the matter and I guess the reason is because it depends on the situation.

a, b and c (and d, e, f... as only 3 attributes are listed in this example for simplicity purposes, which probably tells that the approach is wrong if a class needs that many attributes) are different attributes but have some similarities, which probably means that splitting them in other classes would be the better choice.

Which one of the following options is the most "Pythonic" or efficient when creating a class with multiple "private" attributes? Or would a different approach (e.g. grouping similar attributes in separate classes) be better? How'd that look if, let's say, b and c are the same "kind" of attribute? (e.g. b being an input path and c being an output path).

Multiple property decorators and setters:

class A:  # Too many properties and setters?

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    @property
    def a(self):
        return self._a

    @a.setter
    def a(self, value):
        self._a = a

    @property
    def b(self):
        return self._b

    @b.setter
    def b(self, value):
        self._b = b

    ...  # (same for c)

or multiple setters/getters with "private" attributes:

class A:  # Name mangling issues?

    def __init__(self, a, b, c):
        self.__a = a
        self.__b = b
        self.__c = c

    def set_a(self, a):
        self.__a = a

    def get_a(self):
        return self.__a

    def set_b(self, b):
        self.__b = b

    def get_b(self):
        return self.__b

    ...  # (same for c)

or generic setters/getters:

class A:  # Less cluttered, but less intuitive that attributes are meant to be "private"?
    
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

    def __getattr__(self, name: str):
        return self.__dict__[f"_{name}"]

    def __setattr__(self, name, value):
        self.__dict__[f"_{name}"] = value

or a Python Descriptor? (How'd that'd look like if b and c were to be moved to a separate class as they share some similarities?).

Thanks!


Solution

  • The most pythonic way to create a class that has a member is as follows

    # adding type-hints is recommended
    class A:
      def __init__(self, a: int):
        self.a: int = a
    

    Now everyone uses it, but in a year you have a requirement that

    when user sets a you need to notify all observers

    Now getting and setting a needs to be done through a function. But doing that would break all users that rely on obj.a working. The solution is then to make a a property.

    class A:
      def __init__(self, a: int):
        self._a: int = a
      
      @property
      def a(self) -> int:
        return self._a
    
      @a.setter
      def a(self, value: int):
        self._a = value
        self.notify_observers(self._a)
    
    ... # observers code here
    

    Now uses of obj.a still work flawlessly.

    1. Don't add getters and setters unless you absolutely need to. private members usually don't need getters and setters.
    2. If you see a getter and setter that do nothing except get and set a member then this member should be a public member instead. And tell that java developer that python has properties, and he is not writing java code anymore. (this really happened before)

    Other uses for properties are for read-only members or data that is stored in C objects that you cannot get a reference to, or is computed lazily, or validation, etc .... The important part is that you must have a reason to use getters and setters, and when you do need them then use properties, don't sprinkle them where they are not needed.