How do getters and setters work in Python? - 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.

Related

How to initialize subclasses of an abstract class?

I'm trying to create a dark souls style text-based game. I've created an abstract class which outlines the characteristics of a dark souls class/character type. There are a couple of ways I have initialized it and I'm not sure if one is better than the other.
One important note is that in all of the subclasses, the only parameter I want is the name parameter, while all other variables will be set equal to values specific to that class/character type.
Here is one of the abstract classes I created and its subsequent subclass:
class Character(ABC):
def __init__(self, name, description, health, endurance, strength) -> None:
self.name = name
self.description = description
self.health = health
self.endurance = endurance
self.strength = strength
class Giant(Character):
def __init__(self, name) -> None:
super().__init__(name, description="giant", health=500, endurance=20, strength=100)
Here is the 2nd version:
class Character(ABC):
def __init__(self, name) -> None:
self.name = name
self.description = ''
self.health = 0
self.endurance = 0
self.strength = 0
class Giant(Character):
def __init__(self, name) -> None:
super().__init__(name)
self.description = "giant"
self.health = 500
self.endurance = 20
self.strength = 100
Is one way better than the other? Is there a totally different way which would be better? I'm pretty new to inheritance and abstract classes and I'd appreciate any help you'd be able to provide. Thanks!
I would indeed use neither approach, at least for what you've described here so far in this example.
Is the only way that different subclasses of Character are different that their stats are different? Or would they actually have different behavior?
Because if it's really only about different values, I'd instead just make Character a concrete rather than abstract class and then provide certain methods:
class Character:
def __init__(self, name, description, and so on):
set the values here
#classmethod
def create_giant(cls, name):
return cls(name=name, description="giant", health=500, and so on)
And then you'd make a giant like so:
my_giant = Character.create_giant(name="Olbrzym")
For your versions, they have slightly different semantics. In your version 1, someone calling super().__init__ will be forced to provide concrete values, whereas in version 2, they can just rely on the default values. Given that a character with health=0 probably doesn't make sense, I'd favor version 1.
You see that my version doesn't use inheritance. When would I use inheritance? When I can't easily differentiate the various character types (Giant, Dwarf, Elf?) through their health and endurance and strength values alone but actually need different behavior.
Like, if you imagine keeping with the simple approach and you end up with code that uses a lot of constructs like
if self.description == 'Giant':
do_giant_stuff()
elif self.description == 'Dwarf':
do_dwarf_stuff()
elif AND SO ON
that's a good sign you should be using inheritance instead.
EDIT:
So, to have the classes different behavior, version 1 or 2? Either would work, honestly. But there's a third way. Might be overkill but might come in handy: Hook methods.
Here's how that goes: Write things so that subclasses don't have to call super().__init__. Instead, write the init in the abstract class but have it call abstract methods to fill in the default values.
class Character(ABC):
def __init__(self, name):
self.name = name
self.description = self._get_class_description()
self.health = self._get_class_health()
...
#abstractmethod
def _get_class_description():
pass
... same for the other attributes
class Giant(Character):
def _get_class_description(self):
return "giant"
def _get_class_health(self):
return 500
...
It's called the hook method because the abstract base class describes the methods that a subclass should specify (the hooks) in order to fill in the gaps in behavior.

when to use getter and setter with property?

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.

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)

Properties in Python

whats the reason to use the variable the self._age? A similar name that doesn't link to the already used self.age?
class newprops(object):
def getage(self):
return 40
def setage(self, value):
self._age = value
age = property(getage, setage, None, None)
self.age is already occupied by the property, you need to give another name to the actual variable, which is _age here.
BTW, since Python 2.6, you could write this with decorators:
def newprops(object):
#property
def age(self):
return 40
#age.setter
def age(self, value):
self._age = value
Some object oriented languages have what is called private attributes, which cannot be accessed from outside the class methods. This is important because some attributes are not meant to be changed directly, instead, they are meant to be changed as a function of something else, or validated before they are changed. In Python you don't have private attributes, but you can implement something similar by using getters and setters to a variable which starts with underscore - Python's convention for private methods and attributes.
For instance. The hypotenuse of a rectangular triangle is given by h=sqrt(a*a+b*b), so you cannot change h directly because the relationship must hold. Also, say that a name must me in the format LASTNAME COMMA FIRSTNAME, then you have to verify that this is the case before you assign self.lastname.
The property getter allows you to get the hypotenuse, but forbids you from setting it. The property setter allows you to set a property but you can make checks before actually setting the property.
So:
class Person(object)
def __init__(self):
# The actual attribute is _name
self._name = None
#property
def name(self):
# when I ask for the name, I mean to get _name
return self._name
#name.setter
def name(self, value):
# before setting name I can ensure that it has the right format
if regex_name.match(value):
# assume you have a regular expression to check for the name
self._name = value
else:
raise ValueError('invalid name')
Another example:
class Triangle(object):
def __init__(self, a, b):
# here a and b do not need to be private because
# we can change them at will. However, you could
# make them private and ensure that they are floats
# when they are changed
self.a = a
self.b = b
#property
def h(self):
return math.sqrt(a*a+b*b)
# notice there is no h.setter - you cannot set h directly
Your example is actually nonsense because the getter returns a constant. A separate underscore-named variable in usually used in conjunction with properties, which can be
1) read-only
class C(object):
#property
def age(self):
return self._age
In this case, instance.age can only be read but not assigned to. The convention is to write to self._age internally.
2) read-write
class C(object):
#property
def age(self):
return self._age
#age.setter
def age(self, value):
assert value >= 0
self._age = value
Having getter and setter only makes sense if you have to do extra calculations or checks when assigning the value. If not, you could simply declare the variable without the underscore, as properties and instance variables are accessed in the same way (in terms of code). That's why _age and the property age of course must be named differently.
What you wrote would never be written in python. Often times, though, there are actions that need to be performed when saving a property (such as serialization), and this can be transparently cached in a property.
Since the protocol for calling a property and accessing a member are the same, the python style is to wait as long as possible to turn member variables into properties.

Categories