I am writing a program that will render the name and gender of people entered. The program should ask for a name and then for the gender until the user only presses Enter as the name. Only after Enter has been pressed, the text should be output for all persons. I have tried to implement some code before, but unfortunately I am not getting anywhere.
class Person():
def __init__(self):
self.name = input('Name: ')
self.gender= input('Gender: ')
def give_name(self):
return self.name
def give_gender(self):
return self.gender
def show_name(self):
return self.give_name() + ' is ' + self.give_gender()
my_person = Person()
print(my_person.show_name())
At the end should output the following:
Name: Maya Mitermacht
Gender: female
Name: Max Mustermann
Gender: male
Name:
Maya Mitermacht is female
Max Mustermann is male
Inspired by #Pac0, I'm submitting another answer, though his is already enough and works well!
The one thing that kept tickling me at the back of my head was only a matter of concept, not efficiency or effectiveness. The idea is: if we want to stop creating Persons, we should do so before creating the next one, not after its creation.
For that, I present an alternative using __new__. Perhaps not the best use of it, but it works as intended and has that 'feature' to stop the creation of objects between instances, not after the last one.
class Person:
name: str
gender: str
def __new__(cls):
cls.name = input('Name: ')
if cls.name:
cls.gender = input('Gender: ')
return super(Person, cls).__new__(cls)
else:
raise ValueError("A name is required to create a Person instance")
def __init__(self):
self.name = Person.name
self.gender = Person.gender
def give_name(self):
return self.name
def give_gender(self):
return self.gender
def show_name(self):
return self.give_name() + ' is ' + self.give_gender()
def main():
people = []
while True:
try:
new_person = Person()
except ValueError:
break
else:
people.append(new_person)
for person in people:
print(person.show_name())
if __name__ == "__main__":
main()
The idea is to use __new__ to capture input, then check if a name was provided. If it was, move on to get gender and create the new Person instance. Else, raise an exception, as a person requires a name.
The usage is simple, we can create as many people as we want (and here I'm storing them all in people list. When we are done, provide no name to the next Person, which will raise ValuError, that we catch and break from the loop. Then, print all names.
Since I'm already here, I'll also make some general suggestions to the code. First, getters and setters are not common in Python, and usually are discouraged. Therefore, a person.give_name() could simply be swapped by person.name, and even more when that's only used inside the class. Using that idea, we could simplify the methods give_name, give_gender and show_name to simply:
class Person:
name: str
gender: str
.
.
.
def show_name(self):
return self.name + ' is ' + self.gender
Next, I believe we can make good use of one more "magical" method from Python, and that is __str__. Since we are using name and gender to identify a Person instance, and we want to show that info in a particular way, we can simply define how a Person is represented as a string. We do that using __str__ (inside the class), such as:
def __str__(self):
return f"{self.name} is {self.gender}"
Note: f-strings are very neat and easy to use, but if you're not a fan, replace it with self.name + ' is ' + self.gender, originally.
That way, when we call str() on a Person instance, it will return that same string. If we directly try to print() a Person, we'll have the same result. So the loop to print the names doesn't need print(person.show_name()) anymore, it can be simply print(person) -- which seems more readable to me, and a bit shorter!
So, to wrap it all up, everything would look like this:
class Person:
name: str
gender: str
def __new__(cls):
cls.name = input('Name: ')
if cls.name:
cls.gender = input('Gender: ')
return super(Person, cls).__new__(cls)
else:
raise ValueError("A name is required to create a Person instance")
def __init__(self):
self.name = Person.name
self.gender = Person.gender
def __str__(self):
return f"{self.name} is {self.gender}"
def main():
people = []
while True:
try:
new_person = Person()
except ValueError:
break
else:
people.append(new_person)
for person in people:
print(person)
if __name__ == "__main__":
main()
Since the input will decide to create object or not, it can't be the role of the object/class itself to automatically initialize it. You must have a way to know if the input was empty or not.
Hence, you should delegate this to another class method, call it from your main program and check return value. For instance, in the code below, I chose to return True or False in my function.
class Person():
def initialize(self):
name_entered = input('Name: ')
if name_entered == '':
return False
gender_entered = input('Gender: ')
if gender_entered == '':
return False
self.name = name_entered
self.gender = gender_entered
return True
def give_name(self):
return self.name
def give_gender(self):
return self.gender
def show_name(self):
return self.give_name() + ' is ' + self.give_gender()
persons = []
while True:
my_person = Person()
isCreated = my_person.initialize()
# if it returned False, then user has entered nothing for gender or name, stop the loop
if (not isCreated):
break
#other wise, person is initialized, let's add it to the list and continue
persons.append(my_person)
for person in persons:
print(person.show_name())
Related
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.
I wrote a program with which you can manage the employees of a company. I save all my objects that I create in my list self .__ staffs = []. Yes, I know that this is a private attribute and so do everyone else in my program, but that was the default. If I now create my instances of the classes and save them in the list self .__ staffs = [] using the add_staff method, how can I define a method where I can find the name of the worker and whether he is a Worker, Secretary or Manager and delete it from my list? I've already tried it with .remove(name), but here it only removes the instance of the Staff class, without the data on whether he is a worker, .. or not. You should look for the staff object from the self .__ staffs attribute by name and then remove it.Can anyone help me? Thanks in advance
Here is my Code:
class StaffList:
def __init__(self, date: str):
self.__date = date
self.__staffs = []
def get_count(self):
t = len(self.__staffs)
for staff in self.__staffs:
t -= 0.5
return int(t)
def add_staff(self, staff):
self.__staffs.append(staff)
def remove_by_name(self, name):
self.__staffs.remove(name)
def __str__(self):
a = "Date: {}\nThere are {} Staff-Member(s):\n".format(self.__date, self.get_count())
for i in self.__staffs:
a += i.__str__()
return a
class Staff:
def __init__(self, name: str, salary: int):
self.__name = name
self.__salary = salary
def __str__(self):
return " -Members Name and Salary: {} [{}€]\n".format(self.__name, self.__salary)
class Manager(Staff):
def __init__(self, name, salary, department: str):
super().__init__(name, salary)
self.__department = department
def __str__(self):
return " This Member is a Manager and works in the {} department\n".format(self.__department)
class Secretary(Staff):
def __init__(self, name, salary, has_printer: bool):
super().__init__(name, salary)
self.__has_printer = has_printer
def __str__(self):
if self.__has_printer is True:
return " This Member is a Secretary and has a Printer\n"
else:
return " This Member is a Secretary and has no Printer\n"
class Worker(Staff):
def __init__(self, name, salary, has_driving_licence: bool):
super().__init__(name, salary)
self.__has_driving_licence = has_driving_licence
def __str__(self):
if self.__has_driving_licence is True:
return " This Member is a Worker and has a driving licence\n"
else:
return " This Member is a Wroker and has no driving licence\n"
datum = StaffList("22.11.2020")
staff1 = Staff("Josep Lanington", 2500)
datum.add_staff(staff1)
manager1 = Manager(staff1, staff1, "Elektronics")
datum.add_staff(manager1)
staff2 = Staff("Elena Kromberger", 1800)
datum.add_staff(staff2)
secretary1 = Secretary(staff2, staff2, True)
datum.add_staff(secretary1)
staff3 = Staff("Peter Angerer", 1500)
datum.add_staff(staff3)
worker1 = Worker(staff3, staff3, False)
datum.add_staff(worker1)
print(datum)
First of all, your get_count method is wrong. Should be
def get_count(self):
return len(self.__staffs)
you are doing something really strange here:
manager1 = Manager(staff1, staff1, "Elektronics")
datum.add_staff(manager1)
You created an instance of class Manager, giving another instance of class as name and salary.
I guess what you are trying to achieve is this:
staff1 = Manager("Josep Lanington", 2500, "Elektronics")
datum.add_staff(staff1)
staff2 = Secretary("Elena Kromberger", 1800, True)
datum.add_staff(staff2)
staff3 = Worker("Peter Angerer", 1500, False)
datum.add_staff(staff3)
print(datum)
Now, in order to remove an item from a list with .remove() method you should be able to compare this item to .remove() parameter.
But your class don't know how to compare parameter with a string.
You could add this method to your Staff() class.
def __eq__(self, name):
if not isinstance(name, str):
# don't attempt to compare against unrelated types
return NotImplemented
return self.__name == name
Now you can do datum.remove_by_name("Elena Kromberger").
This is not very elegant (since you define method __eq__ to work against string) but this might give you an idea how to move forward.
Another thing to consider is that if there is no Staff member with such name you would get an error
ValueError: list.remove(x): x not in list
I am learning Python using "Learn Python the Hard Way". Programming is very new to me. I'm at exercise 42. I'm asked to do the following:
Make some new relationships that are lists and dictionaries so you can also have "has-many" relationships.
I want the program to print the names of all of Frank's pets. However, I get an error saying 'AttributeError: 'list' object has no attribute 'callpet'.
I understand the problem is that I'm using a list. When I write frank.pet = satan there is no problem at all. But I want Frank to have more than one pet, and I want the program to print the names of those pets.
This is the full code I am using:
class Person(object):
def __init__(self, name):
self.name = name
self.pet = None
def callme(self):
return self.name
def petsyay(self):
namepet = self.pet.callpet()
return namepet
class Pets(object):
def __init__(self, petname):
self.petname = petname
def callpet(self):
return self.petname
frank = Person("Frank")
poekie = Pets("Poekie")
satan = Pets("Satan")
frank.pet = [poekie, satan]
print frank.petsyay()
What I understand is that I need to split the list or something. So I've tried the following:
def callpet(self):
for eachpet in petname:
return self.petname
Put that just gets the same error. I'm confused, what am I doing wrong here?
Not true after edits (do note that his return is sometimes a petname and sometime a list of petnames, this is not good): [If you call Mr. E's answer for a single Pets only you will most likely recieve an error TypeError: 'Pets' object is not iterable. His answer will only work as long as self.pets is a list and will fail when they're not.]
If you want to support singular and multiple Pets at the same time, either always make sure your self.pet is a list or make sure you check before you process them. Kepp in mind that it's important that you have a constant type output exiting your function because otherwise it makes other people's lives hard.
I recommend that you make self.pets a mandatory list in __init__
class Person(object):
def __init__(self, name):
self.name = name
self.pet = None
def callme(self):
return self.name
def petsyay(self):
if isinstance(self.pet, list):
namepet = []
for pet in self.pet:
namepet.append(pet.callpet())
elif isinstance(self.pet, Pets):
namepet = []
namepet.append(self.pet.callpet())
else:
raise ValueError("Not a correct type. Send Pets or list of Pets")
return namepet
class Pets(object):
def __init__(self, petname):
self.petname = petname
def callpet(self):
return self.petname
frank = Person("Frank")
poekie = Pets("Poekie")
satan = Pets("Satan")
frank.pet = [poekie, satan]
print frank.petsyay()
frank.pet = poekie
frank.petsyay()
print frank.petsyay()
I debugged your program and what was happening was that you did not have a setPets method in your person object so self.pets was never getting set with the list of pets. It was constantly None. I added a setPets method as well as updating the petsYay method to return a list. You could also use yield and return a generator to loop through outside of the class. Heres the updated code:
class Person(object):
def __init__(self, name):
self.name = name
self.pets = None
def setPets(self, p):
self.pets = p
def callme(self):
return self.name
def petsyay(self):
tempL = []
if self.pets is not None:
for pet in self.pets:
tempL.append(pet.callpet())
return tempL
class Pets(object):
def __init__(self, petname):
self.petname = petname
def callpet(self):
return self.petname
frank = Person("Frank")
poekie = Pets("Poekie")
satan = Pets("Satan")
frank.setPets([poekie, satan])
print frank.petsyay()
The problem is that frank.pet is a list of Pets. To do what you want you must redefine petsyay to work with a list of Pets, and return a list of names
def petsyay(self):
return [p.callpet() for p in self.pet]
If you want to work with both lists of pets or single pet, you could handle the exception
def petsyay(self):
try:
return self.pet.callpet()
except AtributeError: #Just handle the AttributeError to prevent hiding another exception
return [p.callpet() for p in self.pet]
In this problem, you will implement a class according to the specifications in the template file usresident.py. The file contains a Person class and a USResident class (a subclass of Person). Person is already implemented for you and you will have to implement two methods of USResident.
For example, the following code:
a = USResident('Tim Beaver', 'citizen')
print a.getStatus()
b = USResident('Tim Horton', 'non-resident')
will print out:
citizen
will show that a ValueError was raised at a particular line
DO NOT MODIFY THE IMPLEMENTATION OF THE Person CLASS
class Person(object):
def __init__(self, name):
#create a person with name name
self.name = name
try:
firstBlank = name.rindex(' ')
self.lastName = name[firstBlank+1:]
except:
self.lastName = name
self.age = None
def getLastName(self):
#return self's last name
return self.lastName
def setAge(self, age):
#assumes age is an int greater than 0
#sets self's age to age (in years)
self.age = age
def getAge(self):
#assumes that self's age has been set
#returns self's current age in years
if self.age == None:
raise ValueError
return self.age
def __lt__(self, other):
#return True if self's name is lexicographically less
#than other's name, and False otherwise
if self.lastName == other.lastName:
return self.name < other.name
return self.lastName < other.lastName
def __str__(self):
#return self's name
return self.name
class USResident(Person):
"""
A Person who resides in the US.
"""
def __init__(self, name, status):
"""
Initializes a Person object. A USResident object inherits
from Person and has one additional attribute:
status: a string, one of "citizen", "legal_resident", "illegal_resident"
Raises a ValueError if status is not one of those 3 strings
"""
# Write your code here
def getStatus(self):
"""
Returns the status
"""
# Write your code here`enter code here`
I would agree with the replies to this post concerning MCVE.
As for an answer to the question (to get the grader to accept your answer), remember that when inheriting the (Parent) class Person for (child) class USResident, (Parent) class Person will need to be initialized in (child) class USResident with:
Person.__init__(self, name)
So the code that gave me a correct answer was:
class USResident(Person):
"""
A Person who resides in the US.
"""
def __init__(self, name, status):
"""
Initializes a Person object. A USResident object inherits
from Person and has one additional attribute:
status: a string, one of "citizen", "legal_resident", "illegal_resident"
Raises a ValueError if status is not one of those 3 strings
"""
Person.__init__(self, name)
if status != 'citizen' and status != 'legal_resident' and \
status != 'illegal_resident':
raise ValueError()
else:
self.status = status
def getStatus(self):
"""
Returns the status
"""
return self.status
The final exam is over but you can go to Final Exam Code Graders in the sidebar of the course to check this code.
I started this course late so I just got to this question and I too was perplexed as to why I wasn't getting the "correct" output as well (for upwards of an hour!).
For those of you not in the course, here's a picture:
The course, for those who are interested, is "Introduction to Computer Science and Programming Using Python", or 6.00.1x, from edX.org .
Unfortunately, only enrolled persons can access the code grader.
Cheers!
Actually it is very simple, just to test you if you can use a constant in the class.
Just like something: STATUS = ("c", "i", "l") and then raise the ValueError if the condition failed.
I have this class Person created:
class Person(object):
def __init__(self, name, age, gender):
self._name = name
self._age = age
self._gender = gender
self._friend = None
def __eq__(self, person):
return str(self) == str(person)
def __str__(self):
if self._gender == 'M':
title = 'Mr'
elif self._gender == 'F':
title = 'Miss'
else:
title = 'Ms'
return title + ' ' + self._name + ' ' + str(self._age)
def __repr__(self):
return 'Person: ' + str(self)
def get_name(self):
return self._name
def get_age(self):
return self._age
def get_gender(self):
return self._gender
def set_friend(self, friend):
self._friend = friend
def get_friend(self):
return self._friend
I now need to have 3 functions:
Using the Person class, write a function print_friend_info(person) which accepts a single argument, of type Person, and:
prints out their name
prints out their age
if the person has any friends, prints 'Friends with {name}'
A function create_fry() which returns a Person instance representing Fry. Fry is 25 and his full name is 'Philip J. Fry'
A function make_friends(person_one, person_two) which sets each argument as the friend of the other.
And this is what I've got:
def print_friend_info(person):
person= Person
person_name=person.get_name(person)
person_age=person.get_age(person)
person_gender=person.get_gender(person)
person_friends=person.get_friend(person)
return person_name, person_age, person_gender, person_friends
"""print (person_name)
print (person_age)
print ('Friends with {'+person_friends+'}')"""
def create_fry():
fry=Person("Philip J. Fry", 23, "M")
return fry
def make_friends(person_one,person_two):
return person_one.set_friend(person_two)
And the error handler says "type object 'Person' has no attribute '_name'"
Don't understand what you try to put into print_friend_info, if the parameter "person" is an instance already
I think your problem is this line
person = Person
by doing that, you just override person which is an instance and replace it with just a definition of class Person
Try to remote that line, and see what happend
You want to remove this line
person= Person
from the print_friend_info(person): function.
That line doesn't create a new Person instance, and even if it did you wouldn't need to. You already get the person passed in as parameter, all you need to do there is retrieve his information.
Since classes are objects in python, by doing person= Person you are just saying that person is the same as Person. Then when you try to get_name on person, that doesn't work because person is not an instance of a class, it is a class object. Therefore it has no _name attribute. It will only have a _name attribute after you instantiate it with the proper parameters.
it still dosn't print the info needed
return person_name, person_age, person_gender, person_friends
"""print (person_name)
print (person_age)
print ('Friends with {'+person_friends+'}')"""
This code doesn't seem right. Instead of printing the data, it returns the data. The text between """ does nothing because you have already left the function with return
Should probably be replaced with
print (person_name)
print (person_age)
print ('Friends with {'+person_friends+'}')
return # this is not necessary, but it's better to be explicit.
Once you have a Person object, say with a label person, you can access its functions like so:
>>> person = Person('Pete', 43, 'Male')
>>> person.get_age()
43
You don't need to pass person to the member function get_age:
>>> person.get_age(person)
Traceback (most recent call last):
...
TypeError: get_age() takes exactly 1 argument (2 given)
But the definition of get_age does have a parameter, self:
def get_age(self):
return self._age
This is implicitly passed to the function when you call it on an object. So person is being passed as self. You can see in the error message above that we were passing two parameters, the implicit self person, and the (wrong, unneeded) explicit person.
self._age is the same as person._age, but is encapsulated, and hidden behind your 'getter' interface.
So, to fix your code, get rid of:
person = Person
and change calls like person.get_age(person), to person.get_age().