Accessing a Single Variable from Another Class (Python) - python-3.x

So, I'm having trouble figuring out how to best structure my Python code. I have a short program using classes, which I've recreated in an example below:
prof1 = Professor()
prof2 = Professor()
professor_list = [prof1, prof2]
class Professor:
def __init__(self):
self.name = ""
self.department = ""
self.course = ""
self.students = []
class Student:
def __init__(self):
self.name = ""
self.activities = []
So basically, I have a list of objects which are instances of the Professor class. Each Professor object also contains a list of Student objects, each of which is an instance of the Student class. I want each Student to also be able to access the course variable from its corresponding Professor (the Professor object with that Student in its list), but I wasn't sure what the neatest way to do this would be.
I considered having Student inherit from Professor, but that seemed sloppy, since I don't actually want to inherit all of the variables and functionality from Professor. For example, Professor's department variable has nothing to do with a Student object, and I don't want Students to have a department attribute. Also, Student isn't a sub-type of Professor, which is what I would associate with inheritance.
I also thought of manually adding the course information to each Student as an attribute, but that didn't seem right either. In the structure I want, the course would be a unique attribute of the Professor, and the various students would be associated with the Professor. All the students that would need to access the course variable are in the Professor's student list - so shouldn't there be a neater way of allowing them to use it than adding it manually to each student?
Here's one more idea: I might include a reference to the corresponding Professor class in each Student, and access the variable that way. Would this be bad practice?
I'd really appreciate any help you can give me with this.

You could go the route of the reference to the Professor, but I think the easiest way would be to create an add_student method which adds the Student to the corresponding Professor's list, as well as marks the course on the Student, so you don't have to do this manually and you can avoid a circular reference (which also is unnecessary because if you're marking the Professor reference on the Student just to get the course attribute, then you may as well just mark the course attribute directly):
class Professor:
def __init__(self):
self.name = ""
self.department = ""
self.course = ""
self.students = []
def add_student(self, student):
self.students.append(student)
student.course = self.course
class Student:
def __init__(self):
self.name = ""
self.activities = []
self.course = None
Alternatively, you could add a method to the Professor which checks if the Student belongs to it and use that to get the course:
prof1 = Professor()
prof2 = Professor()
professor_list = [prof1, prof2]
class Professor:
def __init__(self):
self.name = ""
self.department = ""
self.course = ""
self.students = []
def teaches(self, student):
return student in self.students
class Student:
def __init__(self):
self.name = ""
self.activities = []
def get_course(self, professors):
for professor in professors:
if professor.teaches(self):
return professor.course
return None

Related

How can i avoid repetitive calling of instance variables in Subclasses?

i was wondering if there is a way in Python to get rid of repetitive calling of instance variables , when creating subclasses.
for example:
class Name:
def __init__(self,first,last):
self.first = first
self.last = last
def __str__(self):
return f"Users first name is : {self.first}, Users last name is: {self.last}"
def __repr__(self):
return f"first:{self.first}, last:{self.last}"
class Cash(Name):
def __init__(self,first,last,cash):
super().__init__(first,last)
self.cash = cash
def full(self):
return f"{self.first},{self.last},{self.cash}"
c1 = Cash("Exa","Cool",200)
print(c1.full())
Is it possible to call all instance variables (self.first,self.last...) from "Name", without having to mention them in the constructor of "Cash"
something like:
class Cash(Name):
def __init__("all from Name" + new one ("cash" in this example)):
super().__init__("all from Name")
self.cash = cash
In your case, you can change the Cash class to look like this:
class Cash(Name):
def __init__(self,*inputs):
super(Cash,self).__init__(*inputs[:-1])
self.cash = inputs[-1]
def full(self):
return f"{self.first},{self.last},{self.cash}"
but for a more general solution that covers all situations take a look at this similar question.

Python understanding classes and functions

New to python and have been working on improving my skills overall, however, I struggle with understanding classes and functions.
Why can or can't I do the following code below
class Person():
name = 'Tom'
age = 31
has_job = False
Person.name = 'Tom'
Person.age = 31
Person.has_job = False
print(Person.name, Person.age, Person.has_job)
compared to this
class Person():
def __init__(self, name, age, has_job):
self.name = name
self.age = age
self.has_job = has_job
p1 = Person('Tom', 31, False)
Is this just bad practice or is it something else entirely?
I don't think that writing a class like your first example would be very usefull, because the attributes remain the same for each instance.
That means that every Person will be called by default 'Tom', will have the age: 41 and "has_job" will be set to false.
In the second example you've got a specific constructor that will initialise those variables and that's going to be more usefull. There's only one problem: you forgot to put ":" after def __init__(self, name, age, has_job) .
Also be aware of the indentation.
Your code should look like this:
class Person():
def __init__(self, name, age, has_job):
self.name = name
self.age = age
self.has_job = has_job
p1 = Person('Tom', 31, False)
print(p1.name);
Python is white space sensitive. Unless you want to change the default values in you class you do not need to redefine them.
class Person():
name = 'Tom'
age = 31
has_job = False
'''
change these will change the class values
Person.name = 'Tom'
Person.age = 31
Person.has_job = False
'''
print(Person.name, Person.age, Person.has_job)
In the first section of your code you are trying to define class attributes. These are attributes that do not change between instances of your class. On the other hand if you define variables in the def init(self) method these are parameters you must pass when creating the class and will be unique to each instance of the class you create. These are called instance attributes.
class Person():
# these are class attributes.
name = 'Tom'
age = 31
has_job = False
class Person2():
def __init__(self, name, age, has_job)
# these are instance attributes
self.name = name
self.age = age
self.has_job = has_job
In your first code snippet you did not indent the classes attributes appropriately when you created the class. Check my example above to see how that would be done.
So in your case since each person will be a new instance of your Person class, you do not want to have name, age and has_job as class attributes since those are unique to every person you create. If you had those variables as class attributes then each person you create using your Person() class will have the same name, age, and has_job values.
If you created a class with class attributes and then changed the class attributes of the class instance every time it would not be pythonic. Rather you should create instances of the class with instance attributes.
I HIGHLY recommend watching Corey Shafer OOP tutorials on youtube as they cover all this extensively: https://www.youtube.com/watch?v=ZDa-Z5JzLYM&list=PL-osiE80TeTt2d9bfVyTiXJA-UTHn6WwU&index=40

How to print class variables in a list

So I am very new to coding and started with python, I am trying to build a class in a program that puts together a DnD party by randomising their attributes. So far I can get the program to initialise instances of the party members and just give the user a prompt on how many of the hero's to choose from they would like in their party. My issue is that after setting the lists up and getting everything in place. I am unable to print any of the attributes of the individual heros. Regardless of whether I am calling them from within the lists or if I am directly trying to print them. I have tried using __str__ to create strings of the attributes but I am clearly missing something. Any help would be greatly appreciated.
import random
class Party:
def __init__(self, name="", race="", alignment="", class_=""):
self.name = name
while name == "":
name = random.choice(names)
# print(name)
self.race = race
while race == "":
race = random.choice(races)
# print(race)
self.alignment = alignment
while alignment == "":
alignment = random.choice(alignments)
# print(alignment)
self.class_ = class_
while class_ == "":
class_ = random.choice(classes)
# print(class_)
def character_stats(self):
return "{} - {} - {} - {}".format(self.name, self.race, self.class_, self.alignment)
Each attribute pulls a random value from a list. My format statement is the latest attempt to get the values of the attributes to print rather than the object/attributes instead.
I apologise if any of the terminology is wrong, very very new to this
You are not assigning anything else but the input, (in this case being an empty string "" to the attribuytes. In your minimal example you have this constructor:
class Party:
def __init__(self, name=""):
self.name = name
while name == "":
name = random.choice(names)
After you randomly assign a new name from names, you should assign it to self, otherwise the local variable just goes out of scope when the __init__ method finishes. This code snippet should work:
class Party:
def __init__(self, name=""):
while name == "":
name = random.choice(names)
# Now we assign the local variable as
# an attribute
self.name = name

Python Classes: 'int' object has no attribute in a function in a my class

I'm new to Python object oriented programming. I copied this code online to get a better sense of how OOP worked in Python. Here is the code:
class Student:
def __init__(self, name, student_number):
## Student has-a name
self.name = name
## Student has-a student number
self.student_number = student_number
## Student has-many classes
self.classes = []
def enrol(self, course_running):
## Append the list, classes with variable course_running
self.classes.append(course_running)
## Call course_running with the add_student function in a class
course_running.add_student(self)
class Department:
def __init__(self, name, department_code):
## Department has-a name
self.name = name
## Department has-a code
self.department_code = department_code
## Department has-many courses
self.courses = {}
def add_course(self, description, course_code, credits):
self.courses[course_code] = Course(self, description, course_code, credits)
return self.courses[course_code]
class Course:
def __init__(self, description, course_code, credits, department):
## Course has-a description
self.description = description
## Course has-a code
self.course_code = course_code
## Course has-many credits
self.credits = credits
## Course has-a deparmtnet
self.department = department
## Adds the aforementioned course to the necessary DPT; it is usefull because
## it automatically adds this course to the DPT given in its parameter
self.department.add_course(self)
## Course has-many runnings
self.runnings = []
def add_running(self, year):
self.runnings.append(CourseRunning(self, year))
return self.runnings[-1]
class CourseRunning:
def __init__(self, course, year):
## CourseRunning has-a year
self.course = course
## CourseRunning has-a year
self.year = year
## CourseRunning has-many students
self.students = []
def add_student(self, student):
self.students.append(student)
maths_dept = Department("Mathematics and Applied Mathemtics", "MAM")
mam1000w = maths_dept.add_course("Mathematics 1000", "MAM1000W", 1)
mam1000w_2013 = mam1000w.add_running(2013)
bob = Student("Bob", "Smith")
bob.enrol(mam1000w_2013)
However, I keep getting these errors:
Traceback (most recent call last):
File "ex42c.py", line 70, in <module>
mam1000w = maths_dept.add_course("Mathematics 1000", "MAM1000W", 1)
File "ex42c.py", line 30, in add_course
self.courses[course_code] = Course(self, description, course_code, credits)
File "ex42c.py", line 47, in __init__
self.department.add_course(self)
AttributeError: 'int' object has no attribute 'add_course'
I changed the 'int' object into a 'list' object then a 'str' object, but they produced similar errors. When I deleted the integer and all 'credit' variables, the same error occurred with the 'course_code' variables and with str "MAM1000W." Basically, I want to understand why I cannot pass these parameters to the function.
Thanks in advance.
The first issue is that you're passing the arguments to Course in the wrong order.
Instead of self.courses[course_code] = Course(self, description, course_code, credits)
You need self.courses[course_code] = Course(description, course_code, credits, self)
Because in your Course.__init__ you have them in that order: def __init__(self, description, course_code, credits, department).
But then you have a recursive loop where the add_course function is called by the Course __init__, which is called when you create a Course in add_course... and so on and so forth.
Except that wouldn't happen either, because your call to self.department.add_course(self) in the Course.__init__ is missing two required arguments for Department.add_course - course_code and credits
In short: you have a lot of problems here. We could fix most of them, but the decision about the recursive call to add_course is going to require you to decide whether you want a department to add courses, or courses to add themselves to departments.
After deleting the above ( self.department.add_course(self)) please unindent the part below :
def add_running(self, year):
self.runnings.append(CourseRunning(self, year))
return self.runnings[-1]
It works for me although I am not sure if this is the way you wanted it to work.
It previously did not work because you tried to use Department's method from within Course class. The department was just an argument of Integer type instead of the Department object you probably wanted.
The same method is already there in Department class so why not use it only?
The second part was unindenting the add_running() function as it was typed within the init block of Course.
I hope that helps.

Comparing string to list of objects with repr

If I have a class called Person, and this class contains the name, age, etc. of each person, if I fill a list with the Person class I want to check if a name is in the list without creating a new list of just the names. If I use repr and return repr(self.name), I can print the list and have it return a list of the names of each person in the list. If I then check if "steve" is in the list it returns False. I am guessing this is because it is comparing "steve" to each class, not the class repr. Is there a way to do this without creating a new list?
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return repr(self.name)
people = [Person('steve', 25), Person('dan', 30),Person('heather', 19)]
print(people)
print('steve' in people)
Use any():
any(p.name == 'steve' for p in people)
#True
...and a fail test:
any(p.name == 'bob' for p in people)
#False
However, to be able to use in, you need to define __eq__, not __repr__:
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, s):
return s == self.name
and now it works as intended:
'steve' in people
#True
'bob' in people
#False
The easiest way probably is to just do this manually:
steve_in_people = any('steve' == repr(person) for person in people)
You could also use a bloom filter to quickly determine if 'steve' isn't in your list, but there's no way to know for sure without checking the list. You could also use something like a B-Tree to perform this check very quickly (worst case would be something like O(m) where m is the length of the string you're looking for).
If you don't want fancy data structures and would prefer to be pythonic... well then, use a set, which will give you very high-performance in checks (virtually constant-time):
names = {repr(person) for person in people}
steve_in_people = 'steve' in names
#JoeIddon makes a good point about modifying the class to easily support this behavior. I'll suggest one further improvement:
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return self.name
def __eq__(self, s):
return s == self.name
# This will allow you to quickly find this object in a set using its name
def __hash__(self):
return hash(self.name)
Now you can just create a set of Person objects and search into it using a name string:
person_set = set(all_person_objects)
steve_in_people = 'steve' in person_set

Resources