Is this code correct - python

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...

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.

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 there any class method defined that can delete an object

i am begineer in python and i am creating a employee managment system in which class employer should have a method to delete a object but it seems impossible in python. i have tried many ways to do that but none of them worked for me. the only way it worked when i used del obj_name outside a class body which i clearly dont want
class Employee:
bonus = 1000
def __init__(self,name ,salary,lang,post):
self.name = str(name)
self.salary = int(salary)
self.lang = str(lang)
self.post = str(post)
def deleteAttr(self,attr):
delattr(self, attr)
def getInfo(self):
print(f"Name of the person is {self.name}")
print(f"Language of the person is {self.lang}")
print(f"Post of the person is {self.post}")
def getSalary(self):
print(f"The salary for {self.name} is {self.salary} ")
def inc(self,inc):
self.salary = int(inc) + self.salary
print(f"Incremented salary is {self.salary}")
def dec(self,dec):
self.salary = self.salary - int(dec)
print(f"Decremented salary is {self.salary}")
class Employer(Employee):
#staticmethod
def fired(a):
atr = a.__dict__
for i in list(atr):
delattr(a, i)
del a
def recruit(self,s,name, salary, lang, post):
s = Employee(name, salary, lang, post)
def __init__(self,name,salary,lang,post):
super().__init__(name,salary,lang,post)
def setBonus(self,incr):
self.__class__.bonus += incr
print(f"Bonus increased to {self.__class__.bonus}")
def incBonus(self):
self.bonussalary = self.salary + self.bonus
in this fired is the method i am trying to define where a is the object that i am willing to delete. i read somewhere that deleting all the attributes of an object can help in deleting it but even that is not working.please help me.Thanks in advance
This screams of an XY problem, so I'm not going to answer the question asked, and instead make notes on your design:
There is no reason for an Employer to be a subclass of Employee. Subclass relationships should follow an "is-a" relationship; unless an employer is an employee, it makes no sense to model the relationship that way (Dog can subclass Animal because a dog is an animal, but this doesn't apply to your scenario).
Deleting objects is easy. Just let the last name referring to the object go out of scope, directly or indirectly (or del it if you must), and the object goes away.
But you don't want to delete it, you want them to be fired and removed from the collection of employees from that employer. So don't model employers as employees, make them their own class that contains employees. And don't make fired a static method; that implies an employee is fired by all possible employers at once, not fired by a particular employer.
A reasonable model for Employer might be:
class Employer:
def __init__(self, bonus=1000):
self.employees = []
self.bonus = bonus # bonus argument and attribute only used if bonus is same for all employees of this employer, see below
def recruit(self, name, salary, lang, post): # Remove s, assignment won't work
self.employees.append(Employee(name, salary, lang, post))
return self.employees[-1] # Give caller reference to new employee if they need it
def fire(self, employee):
self.employees.remove(employee) # Will raise exception if employee doesn't work for this employer
def setBonus(self, employee, incr): # If this applies to one employee at a time
employee.bonus += incr
print(f"Bonus increased to {employee.bonus}")
def setBonus(self, incr): # If this applies to all employees for this employer
self.bonus += incr
print(f"Bonus increased to {self.bonus}")
def incBonus(self, employee):
# Little unclear what this was supposed to do; bonussalary wasn't an attribute for
# anything except here in original code; I'll interpret it as returning the salary
# inc(luding) bonus for the year for a given employee
return employee.salary + self.bonus # If bonus applies to all employees
return employee.salary + employee.bonus # If bonus applies per-employee
If you want to hide the Employee objects from the caller, you can have self.employees be a dict mapping a unique ID (e.g. generated via an itertools.count initialized in the constructor), and have recruit insert the new employee keyed by the unique ID, then return only the unique ID, and all other methods would accept that ID instead of the Employee object, look up the correct employee, and perform the work; this way, a given employee object is not directly exposed to the caller; when the employer fires them, they disappear. It's weird (people aren't supposed to live or die by their employment status), but you do you.
Just for completeness, a cleaned up Employee that matches the design for Employer, with the assumption that bonuses are company-wide (not per-employee):
class Employee:
def __init__(self, name, salary, lang, post):
self.name = str(name)
self.salary = int(salary)
self.lang = str(lang)
self.post = str(post)
def print_info(self): # Not named get; get implies you *return* the info; use snake_case over camelCase per Python's PEP8 style guide
print(f"Name of the person is {self.name}")
print(f"Language of the person is {self.lang}")
print(f"Post of the person is {self.post}")
def print_salary(self): # See above for name change reason
print(f"The salary for {self.name} is {self.salary}")
def give_raise(self, amount_raised): # Much more explanatory name than "inc"
self.salary += int(amount_raised) # Avoid repeating self.salary using +=
print(f"Incremented salary is {self.salary}") # Disagree with printing when this happens, but you do you I guess
def cut_pay(self, amount_lost): # Again, explanatory names are good (makes it clear value expected to be positive
self.salary -= int(dec) # Avoid repeating self.salary using -=
print(f"Decremented salary is {self.salary}")
# If not for the prints in each, I'd just implement this as:
return self.give_raise(-amount_lost)
# to reduce code duplication, or replace both functions with a single
# function, "adjust_salary" that takes a positive value for raises,
# negative for cuts, but leaving it separate due to prints contradicting

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

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)

Categories