Reputation: 2326
I can't get my head around why do name of getter/setter methods have to have the same name fo the property.
I've tried reading Aaron Hall's answer here, but I still couldn't find an explanation (or missed it) about why we have to use the respective names.
class Car(object):
def __init__(self, color='blue'):
self.color = color
self.current_model = 'Opel'
@property
def model(self, new_model):
return self.current_model
@model.setter
def model(self, new_model):
if new_model == 'Audi':
raise ValueError ('NO AUDI ALLOWED')
else:
self.current_model = new_model
# model = model.setter(models) but doing this works
@model.getter
def model(self):
return self.current_model
Edit: What I found confusing is that if I rename my method as:
@model.setter
def model_new(self, new_model):
if new_model == 'Audi':
raise ValueError ('NO AUDI ALLOWED')
else:
self.current_model = new_model
And I try running:
audi = Car()
audi.model = 'BMW' # will get AttributeError: can't set attribute
I am expecting that this does not work because getter, setter, and deleter methods create a copy of the property, and changing the name of the method is like modifying attributes of a different property. It is like doing: models = model.setter(models).
But i am not sure if I get this correctly
Upvotes: 6
Views: 4308
Reputation: 532003
The function name needs to be the same because @model.setter
doesn't update an existing property; it replaces it with a new property based on the existing one. When you use the property methods as decorators, the name of the property is the name of the function that gets decorated last.
Remember,
@model.setter
def model(self, new_model):
if new_model == 'Audi':
raise ValueError ('NO AUDI ALLOWED')
else:
self.current_model = new_model
is roughly equivalent to
def tmp(self, new_model):
if new_model == 'Audi':
raise ValueError ('NO AUDI ALLOWED')
else:
self.current_model = new_model
model = model.setter(tmp)
That is, the name bound by the def
statement (model
) is bound to the result of model.setter
instead. If you use a different name, then you've changed the name of the property.
The straightforward way is to provide the getter (and optional setter and deleter) all at the same time:
def foo_get(self):
...
def foo_set(self, v):
...
foo = property(foo_get, foo_set)
Since only the getter is necessary to create the property, you can specify it alone, then create a new property combining the old one with a setter.
foo = property(foo_get)
foo = foo.setter(foo_set)
The decorator syntax allows you to skip naming the getters and setters explicitly:
# Instead of foo = property(foo_get), define a function named foo,
# pass it to property, and rebind the name foo to the new property
@property
def foo(self):
...
# Instead of foo.setter(foo_set), define *another* function named foo,
# pass it too foo.setter, and rebind the name foo to the new, augmented property. Note that this works because Python will evaluate `@foo.setter` to
# get a reference to the bound property method
# before it evaluates the following def statement.
@foo.setter
def foo(self, v):
...
Upvotes: 5
Reputation: 3974
In short: they do not have to, but it is recommended.
If I understand your question correctly, you want to know why it is good practice to name
class Car(object):
def __init__(self, color='blue', model='Opel'):
self.color = color
self._current_model = model
@property
def model(self): #THIS FUNCTION...
return self._current_model
@model.setter
def model(self, new_model): #...LIKE THIS FUNCTION
if new_model == 'Audi':
raise ValueError ('NO AUDI ALLOWED')
else:
self._current_model = new_model
It is a good idea to do this because it avoids confusion and is consistent with the second way to define properties. Consider this snippet:
class Car:
def __init__(self, color='blue', model='Opel'):
self.color = color
self._current_model = model
def g(self):
print('Getting value')
return self._current_model
def s(self, model):
print('Setting value')
self._current_model = model
def d(self):
print('Deleting value')
del self._current_model
prop = property(g, s, d)
You define a separate function for getting, setting, deleting, etc. and use them to create an instance of the property class. You can now modify your hidden variable through these functions like
my_car = Car();
my_car.prop = 'BMW' #call the setter
print(my_car.prop) #call the getter
del my_car.prop #call the deleter
This is clean, you know the variable you are working with. As a counterexample, look at this snippet:
class Car:
def __init__(self, color='blue', model='Opel'):
self.color = color
self._current_model = model
@property
def g(self):
print('Getting value')
return self._current_model
@g.setter
def s(self, model):
print('Setting value')
self._current_model = model
@g.deleter # this could be s.deleter as well
def d(self):
print('Deleting value')
del self._current_model
As you can see, it is possible that you use different names for the setters and deleters compared to the property. If you try to do the same things as before, you end up with hard-to-understand-code:
mycar = Car();
mycar.s = 'BMW'
print(mycar.g)
del mycar.d
You seemingly use 3 attributes to modify the hidden variable. This pollutes the namespace unnecessarily and is generally not recommended. Therefore it makes sense to stick to a single name, which is consistent with the former property definition.
Upvotes: 3