i am new to python and i'm trying to understand the use of the 'getter'. it's use case is not obvious to me.
if i use a property decorator on a method and im able to return a certain value, what exactly would i use 'getter' for.
class Person:
def __init__(self,name, age):
self._name = name
self._age = age
#property
def age(self):
return self._age
#age.setter
def age(self,new_age):
if isinstance(new_age,int) and 18 < new_age < 120:
self._age = new_age
The #property decorator adds a default getter on a given field in a Python class
that triggers a function call upon accessing a property.
The #property decorator turns the age() method into a “getter” for a read-only attribute with the same name. If want a “setter” then add #age.setter as you did in your question.
p = Person("John", 22)
print(p.age)
Output:
22
The property type can take up to 4 separate arguments (a getter, a setter, a deleter, and a doc string) when instantiating it. The first argument is a function that will be used as a getter. The second is a function that will be used as a setter. You could have written your class as
class Person:
def __init__(self,name, age):
self._name = name
self._age = age
def _age_getter(self):
return self._age
def _age_setter(self, new_age):
...
age = property(_age_getter, _age_setter)
This is cumbersome to write, so property objects have a number of methods for building the property up piece by piece. Using property as a simple decorator creates a read-only property with the decorated function as the getter.
# Equivalent to
# def age(self):
# return self._age
# age = property(age)
#property
def age(self):
return self._age
age.setter is a method of the property instance which creates a new property that is essentially a copy of age, but with its argument used to replace whatever setter the original property had. (Decorator syntax is why all the methods involved have to have the same name, so that we are constantly replacing the original property with the new, augmented property, rather than defining multiple similar properties with different names instead.)
#age.setter
def age(self, new_age):
...
Desugaring this requires the use of a temporary variable, to avoid losing the old value of age prematurely.
old_property = age
def age(self, new_age):
...
age = old_property.setter(age)
A silly, but legal, way of defining the property takes advantage of the fact that property can be defined with no arguments, and that there is a getter method that can be used as a decorator as well. (I don't think I've ever seen it used in the wild, though.)
class Person:
def __init__(self, name, age):
...
age = property()
#age.getter
def age(self):
...
#age.setter
def age(self, new_age):
...
Note that the order in which we use getter, setter, (and deleter) methods doesn't matter. The conventional order comes from the order in which property expects its positional arguments to be supplied, as well as the fact that new properties are virtually always defined by decorating the getter directly, rather than adding a getter to a property after it is created.
Related
I'm trying to create a Class with a list of fields. See code below.
class Character:
# Private Fields:
__age = 18
__weight = 200
__height = 72
def __init__(self, name):
self.__name = name
#property
def get_age(self):
return self.__age
#property
def get_name(self):
return self.__name
#property
def get_weight(self):
return self.__weight
#property
def get_height(self):
return self.__height
person = Character("someone")
print("name =", person.get_name,",", "age =", person.get_age)
Is there a way to avoid writing the #property for every private field you want to access? For instance is there a way to pass an attribute into a more general getter function like:
def get_attr(self,attr):
#set attr to __attr
#return self.attr
I tried using the join function, but it didn't work
Thanks for any help
To answer the question as asked, the simple solution is to compensate for the name mangling that is done with private members. e.g. to get the __age attribute you'd use: person._Character__age
But, this would be a terrible idea and I wouldn't recommend it. If you need them to be easily accessible, just remove the underscores. If they really need to be private, they shouldn't be easily accessible from outside the class anyway, so putting in a way to make them accessible defeats the purpose.
I was trying to set some property to a class via decorator but its not working as expected. How can I get the age via property decorator.
class Person:
def __init__(self):
self.name = ""
self.age = ""
self.dob = ""
#property
def name(self):
return self._name
#name.setter
def name(self, value):
self._name = value
#property
def age(self):
return self._age
#age.setter
def age(self, value):
self._age = value
#property
def dob(self):
return self._dob
#dob.setter
def dob(self, value):
self._dob = value
self._age = 20 #Utility.getAge(value)
if __name__ == '__main__':
p = Person()
p.name = "Andrew"
p.dob = "10-10-1980"
print p.name
print p.dob
print p.age
Output:
John
10-10-1980
#20 <-missing
I am not getting the age. Am I missing something?
Ok, this took me a while to actually find out why the above code was not working in python 2.7.
If you look at the property documentation for python2.7, you would find that the class that has the property decorators used is actually inheriting object class and your code doesn't.
Now, when you don't inherit, the property decorator actually doesn't work and setting or getting properties don't work either
(Put a print statements in getter or setter functions and they wont be printed since they were never invoked while setting p.name or getting p.name).
Question : So how come get/set for p.name and p.dob works?
Since, you are not inheriting object class in your class, the property decorators are useless, they are not being invoked but have created those property on the Person object.
But, when you use below code, you are explicitly setting those value (without the use of setters), hence thy are printed and p.age never got assigned any value.
p.name = "Andrew"
p.dob = "10-10-1980"
Code Fix : Update your class declaration to -->
class Person(object):
and setters/getters would work (check using print statements) and self.age would also work.
Bonus : Python3 onwards, all classes, by default, inherit object class.
I want to create a Pojo like class for User in Python. Each of the property will involve some validations. For example: I don't want someone to create a User with a negative value of age. To acheive that I will end up writing a class something like below.
class User:
def __init__(self, age):
self.age = age
#property
def age(self):
return self._age
#age.setter
def age(self, value):
print("Called")
if value >= 0:
self._age = 0
else:
raise ValueError("Age can't be negative")
I am bit scared after seeing the class above. Reasons:
My User class will have somewhere around 50'ish properties. I am bit scared to imagine how big this class will look in that case.
Is this a correct way of creating such classes in Python?
I don't see what's clumpsy about this - most languages out there do not have built-in getter/setter validation so you would end up writing something similar to this in any language you choose - if you want to turn all your properties into getter/setter pairs with validation, you'll have to write them separately.
Now, Python is a dynamic language of a very flexible nature so there are several ways which you can use to reduce the verbosity (but not the complexity) of this process. For example, you can create your own validator decorator to be clamped on top of your setters, e.g.:
def validator(cmp, exc):
def decorator(setter): # a decorator for the setter
def wrapper(self, value): # the wrapper around your setter
if cmp(value): # if cmp function returns True, raise the passed exception
raise exc
setter(self, value) # otherwise just call the setter to do its job
return wrapper
return decorator
And now you can define your getter/setter pairs with validation included as:
class User:
def __init__(self, age):
self.age = age
#property
def age(self):
return self._age
#age.setter
#validator(lambda x: x < 0, ValueError("Age can't be negative"))
def age(self, value):
self._age = value
However, if you're only ever going to just do the validation and no other processing in your setters (and getters), you can just define your own validating property and save a lot on verbosity, something like:
class ValidatingProperty(object):
def __init__(self, prop, cmp, exc):
self.prop = prop
self.cmp = cmp
self.exc = exc
def __get__(self, instance, owner=None):
if instance is None:
return self
return getattr(instance, self.prop, None)
def __set__(self, instance, value):
if self.cmp(value):
raise self.exc
setattr(instance, self.prop, value)
def __delete__(self, instance):
delattr(instance, self.prop)
And now you can build your class getters/setters as simple as:
class User:
age = ValidatingProperty("_age", lambda x: x < 0, ValueError("Age can't be negative"))
def __init__(self, age):
self.age = age
And if you ever need to access the raw property (assuming it was set), without wrappers around it, you can still access it with self._age (or whatever 'real' property that you've passed as the first argument to ValidatingProperty). You can even build your validators separately so you don't rely on lambdas (e.g. create an IntegerValidator class which lets you pass the ranges for validation and then reuse where needed).
The other option is to treat the users of your classes as adults and explain the valid values in the documentation and if they go outside of that - there be dragons. If the class is intended to be populated with data from end-users, the validation should be performed on the side that collects the end-user data (so that the end users can get a meaningful error with them in mind), not necessarily in the model itself.
class human(object):
def __init__(self, name=''):
self.name = name
#property
def name(self):
return self._name
#name.setter
def name(self, value):
self._name = value
class superhuman(human):
#property
def name(self):
return 'super ' + name
s = superhuman('john')
print s.name
# Doesn't work :( "AttributeError: can't set attribute"
s.name = 'jack'
print s.name
I want to be able to override the property but be able to use the super parent's setter without having to override the setter in the child class.
Is that pythonicaly possible?
Use just the .getter decorator of the original property:
class superhuman(human):
#human.name.getter
def name(self):
return 'super ' + self._name
Note that you have to use the full name to reach the original property descriptor on the parent class.
Demonstration:
>>> class superhuman(human):
... #human.name.getter
... def name(self):
... return 'super ' + self._name
...
>>> s = superhuman('john')
>>> print s.name
super john
>>> s.name = 'jack'
>>> print s.name
super jack
The property descriptor object is just one object, even though it can have multiple methods associated with it (the getter, setter and deleter). The .getter, .setter and .deleter decorator functions provided by an existing property descriptor return a copy of the descriptor itself, with that one specific method replaced.
So in your human base class what happens is that you first create the descriptor with the #property decorator, then replace that descriptor with one that has both a getter and a setter with the #name.setter syntax. That works because python decorators replace the original decorated function with the same name, it basically executes name = name.setter(name). See How does the #property decorator work? for the details on how that all works.
In your subclass you simply use that trick to create a new copy of the descriptor with just the getter replaced.
I am trying to print a string variable returned by name() function, which in this case should print "Jim, but Python is printing
`<bound method Human.name of <__main__.Human object at 0x7f9a18e2aed0>>`
Below is the code.
class Human:
def __init__(self):
name = None
def setName(self, _name):
name = _name
def name(self):
return self.name
jim = Human()
jim.setName("Jim")
print(jim.name())
UPDATE:
After reading the answers, i updated the code as shown below, but, now i am getting a new error TypeError: 'str' object is not callable
class Human:
def __init__(self):
self.name = None
def setName(self, _name):
self.name = _name
def name(self):
return self.name
jim = Human()
jim.setName("Jim")
print(jim.name())
self.name is the method itself. You have no attributes storing the name. Nowhere do you actually set the name as an attribute. The following works:
class Human:
def __init__(self):
self.name = None
def setName(self, _name):
self.name = _name
# NOTE: There is no more name method here!
Now you have an actual attribute, and you don't need to call the method here:
jim = Human()
jim.setName("Jim")
print(jim.name) # directly using the attribute
You could even just set the attribute directly:
jim = Human()
jim.name = "Jim"
print(jim.name)
Alternatively, use self._name to store the name on the instance:
class Human:
_name = None
def setName(self, _name):
self._name = _name
def name(self):
return self._name
Here we used a class attribute Human._name as a default, and only set self._name on the instance in the Human.setName() method.
The problem is that name is the name of the internal variable in your object and also the name of the method.
The namespace for variables and methods is the same. Change the name of your method to something other than name. This will fix your getter. On first glance I thought that that would be all you have to do, but the recommendation in Martijn's answer also applies -- you need to assign to self.name and not just name in order to get your setter to work as well.
As an aside, this getter/setter pattern is not usually appropriate for Python. You should ask yourself why you want to use a getter/setter pattern over simply accessing the object's variable directly. See the section on getters and setters in this article for more detail.
You can use setter and getter properties instead of your custom defined methods.
class Human():
def __init__(self):
self._name = None
#property
def name(self):
return self._name
#name.setter
def name(self, name):
self._name = name
And then, use them:
jim = Human()
jim.name = "Jim"
print(jim.name)