Try this: Python Property
The sample code is:
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
print("getter of x called")
return self._x
@x.setter
def x(self, value):
print("setter of x called")
self._x = value
@x.deleter
def x(self):
print("deleter of x called")
del self._x
c = C()
c.x = 'foo' # setter called
foo = c.x # getter called
del c.x # deleter called
Answer from Grissiom on Stack OverflowTry this: Python Property
The sample code is:
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
print("getter of x called")
return self._x
@x.setter
def x(self, value):
print("setter of x called")
self._x = value
@x.deleter
def x(self):
print("deleter of x called")
del self._x
c = C()
c.x = 'foo' # setter called
foo = c.x # getter called
del c.x # deleter called
What's the pythonic way to use getters and setters?
The "Pythonic" way is not to use "getters" and "setters", but to use plain attributes, like the question demonstrates, and del for deleting (but the names are changed to protect the innocent... builtins):
value = 'something'
obj.attribute = value
value = obj.attribute
del obj.attribute
If later, you want to modify the setting and getting, you can do so without having to alter user code, by using the property decorator:
class Obj:
"""property demo"""
#
@property # first decorate the getter method
def attribute(self): # This getter method name is *the* name
return self._attribute
#
@attribute.setter # the property decorates with `.setter` now
def attribute(self, value): # name, e.g. "attribute", is the same
self._attribute = value # the "value" name isn't special
#
@attribute.deleter # decorate with `.deleter`
def attribute(self): # again, the method name is the same
del self._attribute
(Each decorator usage copies and updates the prior property object, so note that you should use the same name for each set, get, and delete function/method.)
After defining the above, the original setting, getting, and deleting code is the same:
obj = Obj()
obj.attribute = value
the_value = obj.attribute
del obj.attribute
You should avoid this:
def set_property(property,value): def get_property(property):
Firstly, the above doesn't work, because you don't provide an argument for the instance that the property would be set to (usually self), which would be:
class Obj:
def set_property(self, property, value): # don't do this
...
def get_property(self, property): # don't do this either
...
Secondly, this duplicates the purpose of two special methods, __setattr__ and __getattr__.
Thirdly, we also have the setattr and getattr builtin functions.
setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value) # default is optional
The @property decorator is for creating getters and setters.
For example, we could modify the setting behavior to place restrictions the value being set:
class Protective(object):
@property
def protected_value(self):
return self._protected_value
@protected_value.setter
def protected_value(self, value):
if acceptable(value): # e.g. type or range check
self._protected_value = value
In general, we want to avoid using property and just use direct attributes.
This is what is expected by users of Python. Following the rule of least-surprise, you should try to give your users what they expect unless you have a very compelling reason to the contrary.
Demonstration
For example, say we needed our object's protected attribute to be an integer between 0 and 100 inclusive, and prevent its deletion, with appropriate messages to inform the user of its proper usage:
class Protective(object):
"""protected property demo"""
#
def __init__(self, start_protected_value=0):
self.protected_value = start_protected_value
#
@property
def protected_value(self):
return self._protected_value
#
@protected_value.setter
def protected_value(self, value):
if value != int(value):
raise TypeError("protected_value must be an integer")
if 0 <= value <= 100:
self._protected_value = int(value)
else:
raise ValueError("protected_value must be " +
"between 0 and 100 inclusive")
#
@protected_value.deleter
def protected_value(self):
raise AttributeError("do not delete, protected_value can be set to 0")
(Note that __init__ refers to self.protected_value but the property methods refer to self._protected_value. This is so that __init__ uses the property through the public API, ensuring it is "protected".)
And usage:
>>> p1 = Protective(3)
>>> p1.protected_value
3
>>> p1 = Protective(5.0)
>>> p1.protected_value
5
>>> p2 = Protective(-5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> p1.protected_value = 7.3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 17, in protected_value
TypeError: protected_value must be an integer
>>> p1.protected_value = 101
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> del p1.protected_value
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 18, in protected_value
AttributeError: do not delete, protected_value can be set to 0
Do the names matter?
Yes they do. .setter and .deleter make copies of the original property. This allows subclasses to properly modify behavior without altering the behavior in the parent.
class Obj:
"""property demo"""
#
@property
def get_only(self):
return self._attribute
#
@get_only.setter
def get_or_set(self, value):
self._attribute = value
#
@get_or_set.deleter
def get_set_or_delete(self):
del self._attribute
Now for this to work, you have to use the respective names:
obj = Obj()
# obj.get_only = 'value' # would error
obj.get_or_set = 'value'
obj.get_set_or_delete = 'new value'
the_value = obj.get_only
del obj.get_set_or_delete
# del obj.get_or_set # would error
I'm not sure where this would be useful, but the use-case is if you want a get, set, and/or delete-only property. Probably best to stick to semantically same property having the same name.
Conclusion
Start with simple attributes.
If you later need functionality around the setting, getting, and deleting, you can add it with the property decorator.
Avoid functions named set_... and get_... - that's what properties are for.
Videos
I am trying to jump more deep into OOP and i study from two resources a
the first one is an academic course (MIT open ware) and they use getters and setters
while the tutorial i follow uses property decorator
I just want to know if one is better than the other or if there are situations where one is preferable
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.
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.