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!
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.
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.