how to use more than one function with in python class? - python

I have defined a python class that has name and age attributes. I'm trying to print the oldest and longest name of objects in this class.
the oldest works fine but to find the longest name I can't use one function with len() and max() since len() does not take more than one argument . I have to define the name length as an attribute first and then define the max. Copy of my code is below. I appreciate your help :)
class NewClass:
def __init__(self, name, age):
self.name = name
self.age = age
self.length = len(name)
def Oldest(*args):
return max(args)
# def Longets(*args): this doesn't work since len takes one argument only
# return max(len(args))
def Longest(*args):
return max(args)
person1 = NewClass('Cindy', 24)
person2 = NewClass('Amy', 28)
person3 = NewClass('fifififi', 27)
print(f'The oldest person is {Oldest(person1.age,person2.age,person3.age)} years old')
print(f'longest name has {Longest(person1.length,person2.length,person3.length)} character')

You can use a list comprehension:
def Longets(*args):
return max([len(arg) for arg in args])

Related

How to print the Name and Gender by using class and methods?

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())

Where does this (student) append come from

class Student:
def __init__(self,name,age,grade): #giving the attributes
self.name=name
self.age=age
self.grade=grade
def get_grade(self):
return self.grade
class Course:
def __init__(self,lesson,max_student):#giving attributes
self.lesson=lesson
self.max_student=max_student
self.students=[]
def add_student(self,student):
if len(self.students)<self.max_student:
self.students.append(student) #append(student) from where
#I don't get where the append(student) get the name from.
#As above code did't use student.
return True
return False
s1=Student('Tim',19,95) #Naming the student
s2=Student('bill',19,75)
s3=Student('jill',19,65)
course1=Course('Math',2)
course1.add_student(s1) #Adding the Student to a list by appending
course1.add_student(s2)
print(course1.students[0].name)
#Will give Tim but how do i print them all at once
#instead of multiple print maybe like a [0:1] but got error
The append method is part of Python. It's part of the list class, so all lists have this method (among others). Check the docs. You set self.students to an empty list in the line self.students = [].
The student variable comes from the argument to add_student, as you specified here: def add_student(self,student). So, when you call course1.add_student(s1), the s1 will be student inside the method (because for class methods, the first argument self is always the class instance itself and doesn't have to be specified in the call).

Python how to find an object by its attributes?

Is there a way to get an object by matching it up with its attributes?
For example:
class band:
def __init__(self, name, number):
self.name = name
self.number = number
rivers = band("rivers", 1)
patrick = band("pat" , 2)
brian = band("brian" , 3)
scott = band("scott", 4)
In this code, is there a way for me to use a number(lets say 1), to find the object rivers within the band class and get its name attribute?
Thank you for taking your time to read this and have a nice day.
In this code, is there a way for me to use a number(lets say 1), to find the object rivers within the band class and get its name attribute?
In this code, NO. Because you do not save the value of name to the class property.
However, if your class structure was like:
class band:
def __init__(self, name, number):
self.number = number
self.name = name # <--- Here is the difference
you could have done it. Let say rivers, patrick, brian and scott are part of the list as:
my_list = [rivers, patrick, brian, scott]
Iterate over the list and print the name of class for which value of number is 1 as:
for item in my_list:
if item.number == 1:
print item.name
You may store a dictionary mapping number to band in order to achieve that.
For example
bands = {}
class band:
def __init__(self, name, number):
self.number = number
self.name = name
bands[number] = self
def getBandFromNumber(number):
return bands[number]

Calling list of instances in a function of a class

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]

Using created classes

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

Categories