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!
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!
Share Improve this question edited Feb 1 at 15:53 joanis 12.3k23 gold badges37 silver badges48 bronze badges asked Feb 1 at 13:34 YoRHa_A2YoRHa_A2 371 silver badge6 bronze badges 12 | Show 7 more comments3 Answers
Reset to default 6The 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.
- Don't add getters and setters unless you absolutely need to. private members usually don't need getters and setters.
- 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.
First of all: nothing is private in Python; if you try hard enough you can change the literal number 2
to mean three.
However, you can make editing attributes harder and clearly signal that they are not meant to be edited. There are several ways to achieve this. Below is one of my favorites. It's simple enough to implement for production code and gets the point across.
class MyClass:
def __init__(self, secretval):
object.__setattr__(self, "secret", secretval) # bypass __setattr__
# Custom __setattr__ to prevent setting private attributes
def __setattr__(self, name, value):
if name in ["secret"]: # Check if it's a private attribute
raise AttributeError("Can't touch this")
else:
object.__setattr__(self, name, value)
# Custom __getattr__ to prevent reading private attributes
def __getattribute__(self, name):
if name in ["secret"]: # Check if it's a private attribute
raise AttributeError("Can't touch this")
else:
return object.__getattribute__(self, name)
myobject = MyClass("secret value") # valid usage
print(myobject.secret) # raises error
myobject.secret = "Bob" # raises error
print(object.__getattribute__(myobject, "secret")) # this bypasses the check
You can also use other tools like name mangling or mapping proxies or even C level code to protect attributes but all of these can be bypassed given enough dedication.
I'll try to supplement the technical answers with a few thoughts from an OOP perspective:
- Getters and setters are somewhat inconsistent with the OOP paradigm. Because your objects start to represent data structures. Some thoughts can be read in this article.
- The correct way to encapsulate objects is to use a constructor (
__init__
). - Our task is how to decompose classes so that each class has a few fields. The cohesion indicator can be used to check whether we are going in the right direction. To calculate it, we can use the LCOM metrics.
__init__
should use them instead of assigning directly to the private attribute. – chepner Commented Feb 1 at 14:12