when to use getter and setter with property? - python

When should you use a property with getters/setters? It is not pythonic or wrong to not use a property with getters and setters? Should or shouldn't I write it with a property?
Examples:
class Person:
def __init__(self, firstname, lastname, age):
self.firstname = firstname
self.lastname = lastname
self.age = age
def say_hi(self):
print(f"""Hi i'm {self.firstname} {self.lastname} and i'm {self.age}""")
#property
def age(self):
return self._age
#age.setter
def age(self, newage):
if not isinstance(newage, int):
raise TypeError("Expect an Integer")
self._age = newage
versus
class Person2:
def __init__(self, firstname, lastname, age):
self.firstname = firstname
self.lastname = lastname
self.age = age
def say_hi(self):
print(f"""Hi i'm {self.firstname} {self.lastname} and i'm {self.age}""")
def get_age(self):
return self.age
def set_age(self, newage):
if not isinstance(newage, int):
raise TypeError("Expect an Integer")
self.age = newage

You should generally prefer to use "protected" variables (such as those starting with _) with properties (not separate functions that users need to call, that's just clunky), as it confers some advantages. This encapsulation is very handy as it:
lets you control the internal data completely, such as preventing people entering ages like -42 (which they will do if they can); and
lets you change the underlying implementation in any manner you want, without affecting clients.
For example on that last point, you may want to maintain a separate structure of all names and simply store references to those names in your Person class. This can allow you to store many more names, as the surname "Von Grimmelshausen" would be stored once (in the separate structure) and as much smaller indexes in all the Person objects that use it.
You can then totally change the naive getter from:
#property
def surname(self):
return self._surname
to:
#property
def surname(self):
return self._surname_db[self._surname_index]
without any changes to clients.

The pythonic way would be not to use setters and getters at all; just have an attribute:
class Person:
def __init__(self, firstname, lastname, age):
self.firstname = firstname
self.lastname = lastname
self.age = age
def say_hi(self):
print(f"Hi i'm {self.firstname} {self.lastname} and i'm {self.age}")
If you want to check types, use type annotations and a checker like mypy:
class Person:
def __init__(self, firstname, lastname, age):
self.firstname: str = firstname
self.lastname: str = lastname
self.age: int = age
def say_hi(self):
print(f"Hi i'm {self.firstname} {self.lastname} and i'm {self.age}")
If it later turns out that you do need to do something more complex, you can always turn it into a property later with no change of interface.

"Pythonic" is a holy struggle.
I personally prefer the Class under full control.
In your case:
class Person:
def __init__(self, firstname, lastname, age):
self.firstname = firstname
self.lastname = lastname
self.age = age
def say_hi(self):
print(f"Hi i'm {self.firstname} {self.lastname} and i'm {self.age}")
def test_str(self, cosi):
return self.test(cosi, str)
#staticmethod
def test(cosi, neco):
assert isinstance(cosi, neco), f"Bad value! {cosi} is not instance" \
f" from {neco.__name__}"
return cosi
#staticmethod
def test_positiv_int(num):
assert 0 < int(num), f"Expect an positiv integer" # negative value protect
return int(num) # if int is like string this returned int
def __setattr__(self, key, value):
# important!!!:
whitedict = dict(firstname=self.test_str,
lastname=self.test_str,
age=self.test_positiv_int
)
# Call fn from whitedict with parameter
self.__dict__[key] = whitedict[key](value)

The second version of your code (referring to class2) utilizes two instance methods i.e get_age and
set_age which are not serving much of a purpose because you can retrieve the age attribute of an instance without calling the get_age method, also you can set the age attribute to literally anything without invoking your set_age method. Also if you want user to retrieve or set the age attribute using your given instance methods, the user who was using your class previously will have to make changes in their code which is something we do not want.
Now, the first version of your code (referring to class1) is very helpful because you can pose restrictions on the age attribute by using property decorators. You can make the age attribute read only or both read and write and you can just retrieve or set the age attribute of an instance normally without having to call any methods.
Also, as you explicitly need to call the set_age method on an instance in second version of your code, for this
piece of logic :
if not isinstance(newage, int):
raise TypeError("Expect an Integer")
self._age = newage
to execute so the user cannot put any arbitrary value into the age attribute, on the other hand it happens implicitly whenever you try to set the age attribute when you use properties.

Related

How do getters and setters work in Python?

I am learning getters and setters , what I understand is that they are used so that no one could change the object's attributes directly. In the example
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
def get_age(self):
return self._age
def set_age(self, new_age):
if isinstance(new_age, int) & new_age>0 & new_age<120:
self._age = new_age
def get_name(self):
return self._name
def __str__(self):
return 'Person[' + self._name + '] is ' + str(self._age)
p1 = Person("Sandeep", 49)
I created an object p1 where I set the age 49. As I have made a set_age function so I expect we can change the age of p1 through set_age only, not through the routine way. But it is not happening, I am able to change the age of p1 through , for example, p1._age = 35 as well. Then, what is the advantage to make set_age function, if I am still able to access the attributes directly?
I think, I am missing something, please help.
You need to tell python how to associate the getter and setter with the actual variable name. To do this you can use the builtin property function like so:
class Person
def __init__(self, name, age):
self._name = name
self._age = age
def get_age(self):
return self._age
def set_age(self, new_age):
if isinstance(new_age, int) & new_age>0 & new_age<120:
self._age = new_age
def get_name(self):
return self._name
name = property(get_name)
age = property(get_age, set_age)
def __str__(self):
return 'Person[' + self.name + '] is ' + str(self.age)
p1 = Person("Sandeep", 49)
Then instead of referring to _name and _age use name and age
The reason to use a getter and setter, is if you want to do something more complex than just set and attribute with foo.bar. In your case, set_age has an
isinstance(new_age, int) & new_age>0 & new_age<120
check, which is not possible to do with a raw attribute. (Side-note: you should use and instead of &.)
Yes, someone can still do p1._age = -1, and then their code won't work, but why would they? It just makes their code not work.
Your get_name function is less useful than the age one. It basically makes name read-only, which might or might not be useful.
When creating setters and getters in Python, it is usual to use the #property decorator. This means the functions can be called as if they were attributes, so instead of p1.get_name() you can just do p1.name. Similarly p1.set_age(3) becomes p1.age = 3.
You probably want to use the age setter in __init__, because then the age of the Person is validated when it is created.
Here is a version that makes these changes (and a couple of other readability improvements).
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 0 < new_age < 120:
self._age = new_age
#property
def name(self):
return self._name
def __str__(self):
return f"Person[{self.name}] is {self.age}"
p1 = Person("Sandeep", 49)
what I understand is that they are used so that no one could change the object's attributes directly.
Your understanding is wrong. There is no magic rule that tells the python interpreter oh, this class has a setter, so direct access to the fields aren't allowed any more.
In other words: this is merely "works by convention". When you see a python class that has setter methods, then you know you should not access the fields directly, although you still can do that.
And note: python is mostly rather lenient on such things. A lot of things are "by convention" only. It is just "the nature" of python: you can do a lot of things that other languages, especially statically typed ones like Java do not allow.
Now I realise that I asked a silly question. The reason is obvious of using setters, creating functions for setting a value is a very convenient way to apply the constraints on it. In the example above, the constraint is that the age of the person should be positive and less than 120. Implementation of such constraints is not possible without setters.
Generally speaking you do not code getters and setters in python unless you specifically need them, right now. (But what if you need them later?? I'll explain that in a moment.)
Most of the time you should just define the instance variables, and then in the code that needs to use those variables, just access them directly, i.e.:
p1 = Person("Sandeep",49)
print("%s is %d years old." % (p1._name, p1._age))
# prints "Sandeep is 49 years old."
p1._age = 50
print("Now %s is %d years old." % (p1._name, p1._age))
# prints "Now Sandeep is 50 years old."
You only add getter/setter methods later, when you actually need them.
When you add the getter/setter methods later, you do not need to change any of the existing code that directly accesses the instance variables. Instead, use the #property decorator, shown in LeopardShark's answer above (https://stackoverflow.com/a/71080050/1813403).
The #property decorator does some magic code generation that makes your new getter/setter methods intercept when the existing code tries to directly access the instance variables, and send the request through your getter/setter methods instead. So the existing code will keep working but will call your new getter/setter methods instead of just messing directly with the attribute.

How can I make the OOP code print my classes objects name?

I've tried to make an OOP based program in python. I gave it an object to work with and tried to make it print the name, but its not working.
class human:
def __init__(self, name):
print("this is a human")
def name(self, name):
print("this is {}".format(bob.name))
bob = human("bob")
Anyone know what the problem could be?
Beyond the answers you already received (which solve your problem), I'd suggest not having a method that prints the name. Rather, you should have a __str___ dunder method that defines the object's behavior when an instance is printed.
class human:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
person = human("bob")
print(person)
'bob'
You can also define the object's behavior when the instance name is entered in the console, for instance just running the line
>>> person
You can do it with __repr__:
def __repr__(self):
return f'when entering the instance name in the console: {self.name}'
This will print:
when entering the instance name in the console: bob
This appears more pythonic to me than having a method that simply prints the name.
You're never storing the name on the instance, where would it get the name from? Your __init__ needs to do something along the lines of self.name = name
the name method and attribute are going to conflict, the latter will shadow (hide) the former, and it should look up whatever attribute its using on self
You never assigned the passed name to the object. Try:
class human:
def __init__(self, name):
print("this is a human")
self.name = name
def print_name(self):
print("this is {}".format(self.name))
bob = human("bob")
bob.print_name()
there are couple of things to update in the code:
bob is an instance which is not defined at human class
notice that init, name functions expect external param but you never use it in the function. (in self. = name)
in order to use it:
define a var in the class named 'name' and update you function to:
class human:
_name = ""
def __init__(self, name):
print("this is a human")
self._name = name
def name(self):
print("this is "+ self._name)
bob = human("bob")
bob.name()
bob = human("bob") only init function and you should call bob.name() in order to call the print-name function

Is this code correct

I have a question to ask, please. Given the code below, can you please let me know why in manager (or in the worker) class why
self.FirstName
gives the same result as
self._firstName
I would have thought that self._firstName would not be accessible in either of the classes (Manager/Worker) since it local to the Employee class and should not be accessible outside it, no ?
Please suggest.
import gc
class Employee(object):
"""Employee Base Class"""
def __init__(self, FirstName, LastName,Age, Role):
super(Employee, self).__init__()
self._firstName = FirstName
self._lastName = LastName
self._age = Age
self._role = Role
#property
def FirstName(self):
return self._firstName
#property
def Age(self):
return self._age
#property
def Role(self):
return self._role
#FirstName.setter
def FirstName(self, value):
self._firstName = value;
pass
#Role.setter
def Role(self, value):
self._role = value;
pass
class Manager(Employee):
"""Manager class"""
def __init__(self, FirstName,LastName,Age):
Employee.__init__(self,FirstName, LastName, Age, 'Manager')
# super(Manager, self).__init__()
def getParents(self):
"""Get parents of the class"""
print(gc.get_referrers(self))
pass
def ManagerInfo(self):
print("FirstName : " + self.FirstName)
print("Role : " + self.Role)
print("Age : " + str(self.Age))
class Worker(Employee):
"""docstring for Worker"""
def __init__(self, FirstName, LastName, Age):
Employee.__init__(self,FirstName, LastName, Age, 'employee')
def getParents(self):
"""Get parents of the class"""
print(gc.get_referrers(self))
pass
def WorkerInfo(self):
print("FirstName : " + self.FirstName)
print("Role : " + self.Role)
print("Age : " + str(self.Age))
pass
# manager = Employee('John','Doe' , 40, 'Manager')
# print("{0}'s age is {1} years.".format(manager.FirstName, manager.Age))
anEmp = Worker('WorkerName', 'LastName', 20)
aManager = Manager('John', 'Doe', 40)
print(anEmp.WorkerInfo())
print(anEmp.getParents())
print("----------------------------")
print(aManager.ManagerInfo())
print(aManager.getParents())
Thanks
why self.FirstName gives the same result as self._firstName
Because you defined FirstName as a property returning self._firstname. What did you expect actually ?
I would have thought that self._firstName would not be accessible in either of the classes (Manager/Worker) since it local to the Employee class
It's not 'local to the Employee class', it's an attribute of Employee instances (it doesn't exist in the Employee class itself).
and should not be accessible outside it, no ?
While prefixing a name with a single underscore denotes an implementation attribute (IOW something that is NOT part of the public API - the equivalent of 'protected' in most mainstream languages), it doesn't prevent access to the attribute. Actually there's absolutely NO enforcement of access restriction in Python, it's all convention (and eventually name mangling for __pseudoprivates names).
Python's philosophy is that we are all consenting adults and are wise enough to not do stupid things like messing with what is clearly labelled as an implementation attribute without accepting full responsability for breaking encapsulation.
can you please let me know what I should be doing in order to make sure that the user can only set the value using the setters and not by doing self._firstName
Nothing more than you already did actually. Re-read the above paragraphs, I already mentionned that Python did NOT enforced access restriction of any kind. self._firstname is prefixed with a single leading underscore, which is the way to tell "this is an implemention detail and not part of the API, you should not be messing with this attribute, you should not even know it exists, so if you break something by messing with it well too bad for you dude, but you're on your own".
so if in case, I have some arbitrary logic that manipulates the value in the setter before setting it, the updated value will not be available if the user just does self._firstName instead of self.FirstName
The chances this would happen are rather low actually (and that's an understatement) but theoritically yes this could happen. But this is totally unrelated since you'd have the very same problem if the user used self.FirstName instead since it would still return the stale value...

print instance of object in python

I am a beginning python learner. Why is this code not printing 100?
class Pet(object):
def __init__(self):
self.name = ''
self.age = 0
def getName(self, newname):
self.name = newname
return self.name
def getAge(self, aging):
self.age += aging
return self.age
polly = Pet()
polly.getAge(100)
print polly.getAge
Step through it.
polly = Pet() # initialize a new Pet
polly.getAge(100) # add 100 to polly.age
print polly.getAge # print the getAge method bound to polly
Did you maybe mean to do
polly = Pet()
polly.getAge(100)
print polly.age
Or:
polly = Pet()
print polly.getAge(100)
?
No matter what, you should be nice and not mutate values with a getX method. It's definitely unexpected behavior.
1) You meant print polly.getAge() instead of print polly.getAge.
2) However you then have a second issue (getAge() requires aging to be mandatory, which it totally shouldn't). You can kludge that with print polly.getAge(0), or fix it properly, see below.
Back to 1) The former actually calls the function/method (well it tries to call it, except for the issue of 2))
The latter is wrong, it only prints information about the actual function/method, but doesn't actually call it.
You can explore more by seeing that type(polly.getAge()) (or type(polly.getAge(0))) is an int, different to type(polly.getAge), which will be some type for function/method.
2) Your second issue here is getAge(...) is pretending to be a getter function but it also has a mandatory parameter aging and is implementing the aging, this is considered "method abuse" and violates separation of concerns, or at least don't name it getX which is really reserved for getters. Your alternatives are a) give it a default getAge(self, aging=0) (or use #property decorator), now it behaves like a getter when called getAge(). Or b) refactor out the two separate pieces of functionality into separate methods ("separation of concerns"):
class Pet(object):
...
def getAge(self, aging):
return self.age
def doAging(self, aging):
self.age += aging
# Note that doAging() has no return value, and that is correct behavior (it's a method, not a getter)
In general that is a better and more maintainable design, we frown on "magical getters/setters" with side-effects, for tons of reasons (one of which is they f### up unit-testing). As you start adding more methods, you'll see why very quickly...
Really you should have a method for setting and getting the age.
class Pet(object):
def __init__(self):
self.name = ''
self.age = 0
def getName(self, newname):
self.name = newname
return self.name
def getAge(self):
return self.age
def setAge(self, newage):
self.age = newage
return None
polly = Pet()
polly.setAge(100)
print (polly.getAge())
Here is the modified code. I hope this will help you.
class Pet:
## def __init__(self):
## self.name = ''
## self.age = 0
def setAge(self, age):
self.age = age
#return self.age
def getAge(self):
## self.age = aging
## return self.age
print self.age
polly = Pet()
polly.setAge(100)
polly.getAge()
#print (polly.getAge)

Python, creating objects

I'm trying to learn python and I now I am trying to get the hang of classes and how to manipulate them with instances.
I can't seem to understand this practice problem:
Create and return a student object whose name, age, and major are
the same as those given as input
def make_student(name, age, major)
I just don't get what it means by object, do they mean I should create an array inside the function that holds these values? or create a class and let this function be inside it, and assign instances? (before this question i was asked to set up a student class with name, age, and major inside)
class Student:
name = "Unknown name"
age = 0
major = "Unknown major"
class Student(object):
name = ""
age = 0
major = ""
# The class "constructor" - It's actually an initializer
def __init__(self, name, age, major):
self.name = name
self.age = age
self.major = major
def make_student(name, age, major):
student = Student(name, age, major)
return student
Note that even though one of the principles in Python's philosophy is "there should be one—and preferably only one—obvious way to do it", there are still multiple ways to do this. You can also use the two following snippets of code to take advantage of Python's dynamic capabilities:
class Student(object):
name = ""
age = 0
major = ""
def make_student(name, age, major):
student = Student()
student.name = name
student.age = age
student.major = major
# Note: I didn't need to create a variable in the class definition before doing this.
student.gpa = float(4.0)
return student
I prefer the former, but there are instances where the latter can be useful – one being when working with document databases like MongoDB.
Create a class and give it an __init__ method:
class Student:
def __init__(self, name, age, major):
self.name = name
self.age = age
self.major = major
def is_old(self):
return self.age > 100
Now, you can initialize an instance of the Student class:
>>> s = Student('John', 88, None)
>>> s.name
'John'
>>> s.age
88
Although I'm not sure why you need a make_student student function if it does the same thing as Student.__init__.
Objects are instances of classes. Classes are just the blueprints for objects. So given your class definition -
# Note the added (object) - this is the preferred way of creating new classes
class Student(object):
name = "Unknown name"
age = 0
major = "Unknown major"
You can create a make_student function by explicitly assigning the attributes to a new instance of Student -
def make_student(name, age, major):
student = Student()
student.name = name
student.age = age
student.major = major
return student
But it probably makes more sense to do this in a constructor (__init__) -
class Student(object):
def __init__(self, name="Unknown name", age=0, major="Unknown major"):
self.name = name
self.age = age
self.major = major
The constructor is called when you use Student(). It will take the arguments defined in the __init__ method. The constructor signature would now essentially be Student(name, age, major).
If you use that, then a make_student function is trivial (and superfluous) -
def make_student(name, age, major):
return Student(name, age, major)
For fun, here is an example of how to create a make_student function without defining a class. Please do not try this at home.
def make_student(name, age, major):
return type('Student', (object,),
{'name': name, 'age': age, 'major': major})()
when you create an object using predefine class, at first you want to create a variable for storing that object. Then you can create object and store variable that you created.
class Student:
def __init__(self):
# creating an object....
student1=Student()
Actually this init method is the constructor of class.you can initialize that method using some attributes.. In that point , when you creating an object , you will have to pass some values for particular attributes..
class Student:
def __init__(self,name,age):
self.name=value
self.age=value
# creating an object.......
student2=Student("smith",25)

Categories