Here's a more thorough example of why you would want to have a private attribute: data type validation (as pointed out by the other answers).
Let's say that you've been using the Dog class for a few weeks, and a new bug has come up that happens when the name attribute is (for whatever reason) set to a value that's not a string. You now need to change the code in order to enforce that a Dog's name is indeed a string, by raising an informative Exception. How do you do that?
Your first idea may be to simply check the name's data type after creating a new Dog (using the isinstance builtin function):
my_dog = Dog(name='Max', age=5)
if not isinstance(my_dog.name, str):
raise ValueError("The Dog's name is not a string")
This works, but there's a clear issue here: you need to 1) change every line of code where a Dog is created, and 2) you need to tell anyone who ever uses your Dog class that, by the way, remember to check for the name's data type. This is clearly a lot of work, and not future-proof by any means - what if you or another user forgets to do this check? What if no one ever reads your documentation on the Dog class?
You may then think to perform this check in the __init__ method:
def class Dog:
def __init__(self, name, age):
if not isinstance(name, str):
raise ValueError("The Dog's name is not a string")
self.__name = name
self.age = age
This cuts down on the work, but it doesn't stop someone from modifying the Dog's name after it was instantiated:
my_dog = Dog(name='Max', age=5)
my_dog.name = 10 # oops, wrong value!
What we want to do is to actually move this type-checking functionality to a method that runs every time the name attribute gets set - which is exactly what the setter method (the one decorated with the @name.setter decorator) does:
def class Dog:
def __init__(self, name, age):
self.__name = name
self.age = age
@property
def name(self):
return self.__name
@name.setter
def name(self, new_name)
if not isinstance(new_name, str):
raise ValueError("The Dog's name is not a string")
self.__name = new_name
Now the name's data type is always checked for, both during instantiation (in the __init__ method) and afterwards.
Data type validation is simply an example. The point of using private attributes and their getters/setters is to combine attribute access with some custom functionality. If you desire no functionality whatsoever when accessing/modifying the attribute, you're correct in your observation that you don't need to bother making it private and writing the getter/setter methods in the first place.
Answer from jfaccioni on Stack ExchangeHere's a more thorough example of why you would want to have a private attribute: data type validation (as pointed out by the other answers).
Let's say that you've been using the Dog class for a few weeks, and a new bug has come up that happens when the name attribute is (for whatever reason) set to a value that's not a string. You now need to change the code in order to enforce that a Dog's name is indeed a string, by raising an informative Exception. How do you do that?
Your first idea may be to simply check the name's data type after creating a new Dog (using the isinstance builtin function):
my_dog = Dog(name='Max', age=5)
if not isinstance(my_dog.name, str):
raise ValueError("The Dog's name is not a string")
This works, but there's a clear issue here: you need to 1) change every line of code where a Dog is created, and 2) you need to tell anyone who ever uses your Dog class that, by the way, remember to check for the name's data type. This is clearly a lot of work, and not future-proof by any means - what if you or another user forgets to do this check? What if no one ever reads your documentation on the Dog class?
You may then think to perform this check in the __init__ method:
def class Dog:
def __init__(self, name, age):
if not isinstance(name, str):
raise ValueError("The Dog's name is not a string")
self.__name = name
self.age = age
This cuts down on the work, but it doesn't stop someone from modifying the Dog's name after it was instantiated:
my_dog = Dog(name='Max', age=5)
my_dog.name = 10 # oops, wrong value!
What we want to do is to actually move this type-checking functionality to a method that runs every time the name attribute gets set - which is exactly what the setter method (the one decorated with the @name.setter decorator) does:
def class Dog:
def __init__(self, name, age):
self.__name = name
self.age = age
@property
def name(self):
return self.__name
@name.setter
def name(self, new_name)
if not isinstance(new_name, str):
raise ValueError("The Dog's name is not a string")
self.__name = new_name
Now the name's data type is always checked for, both during instantiation (in the __init__ method) and afterwards.
Data type validation is simply an example. The point of using private attributes and their getters/setters is to combine attribute access with some custom functionality. If you desire no functionality whatsoever when accessing/modifying the attribute, you're correct in your observation that you don't need to bother making it private and writing the getter/setter methods in the first place.
The advantage of properties is that from the viewpoint of the user of your class, it's syntactically the same as plain attribute access. So you can add logic (validation etc.) at any time without changing the class's interface and breaking the code that uses it. You can even swap in different versions of the class (e.g. with properties that do logging during development, without properties for production) and the caller won't even notice.
Contrast this to a language like Java where best practice is to always write getter/setter methods even if you don't need them, because you might need them later, but can't add them later without changing the API and thereby breaking code that uses the class.
BTW, there are no truly private attributes in Python. The leading __ is intended to keep names from conflicting among subclasses of the base class, where that might be a problem.
properties - How does the @property decorator work in Python? - Stack Overflow
When not to use @property decorator?
Cached @property decorator that is memory friendly
Decorator to treat method as a property or callable.
Videos
The property() function returns a special descriptor object:
>>> property()
<property object at 0x10ff07940>
It is this object that has extra methods:
>>> property().getter
<built-in method getter of property object at 0x10ff07998>
>>> property().setter
<built-in method setter of property object at 0x10ff07940>
>>> property().deleter
<built-in method deleter of property object at 0x10ff07998>
These act as decorators too. They return a new property object:
>>> property().getter(None)
<property object at 0x10ff079f0>
that is a copy of the old object, but with one of the functions replaced.
Remember, that the @decorator syntax is just syntactic sugar; the syntax:
@property
def foo(self): return self._foo
really means the same thing as
def foo(self): return self._foo
foo = property(foo)
so foo the function is replaced by property(foo), which we saw above is a special object. Then when you use @foo.setter(), what you are doing is call that property().setter method I showed you above, which returns a new copy of the property, but this time with the setter function replaced with the decorated method.
The following sequence also creates a full-on property, by using those decorator methods.
First we create some functions:
>>> def getter(self): print('Get!')
...
>>> def setter(self, value): print('Set to {!r}!'.format(value))
...
>>> def deleter(self): print('Delete!')
...
Then, we create a property object with only a getter:
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True
Next we use the .setter() method to add a setter:
>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True
Last we add a deleter with the .deleter() method:
>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True
Last but not least, the property object acts as a descriptor object, so it has .__get__(), .__set__() and .__delete__() methods to hook into instance attribute getting, setting and deleting:
>>> class Foo: pass
...
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!
The Descriptor Howto includes a pure Python sample implementation of the property() type:
class Property: "Emulate PyProperty_Type() in Objects/descrobject.c" def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel if doc is None and fget is not None: doc = fget.__doc__ self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError("unreadable attribute") return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError("can't set attribute") self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError("can't delete attribute") self.fdel(obj) def getter(self, fget): return type(self)(fget, self.fset, self.fdel, self.__doc__) def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.__doc__) def deleter(self, fdel): return type(self)(self.fget, self.fset, fdel, self.__doc__)
The documentation says it's just a shortcut for creating read-only properties. So
@property
def x(self):
return self._x
is equivalent to
def getx(self):
return self._x
x = property(getx)
The @property decorator comes with a whole system with getters and setters.
However I have been using it only to provide a cleaner access to a method which doesn't take any arguments and not for anything else.
If the method has to accept arguments in future, I remove the decorator and update the usages.
Are there any downsides to this? What are some other scenarios to not use @property?