flowchart TD
O[object] --> A[Animal]
A --> C[Cat]
A --> P[Person]
A --> R[Rabbit]
P --> S[Student]
EE1021 Programming for Engineers - Lecture 8
super().__init__()__add__ and __eq__@property decoratorsCoordinate exampleFraction exampleThe class definition
self to refer to some instance while defining the class, e.g. (self.x - self.y)**2self is a parameter to methods in the class definitionThis lecture’s running example — the Animal class:
Animal — class name; object — class parentself — variable that refers to an instance of the classage — the data that initializes an Animalname is a data attribute even though an instance is not initialized with it as a parametermyanimal = Animal(3) creates an instance; the 3 maps to self.age in the class definitionclass Animal(object):
def __init__(self, age):
self.age = age
self.name = None
def get_age(self): # getter
return self.age
def get_name(self): # getter
return self.name
def set_age(self, newage): # setter
self.age = newage
def set_name(self, newname=""): # setter
self.name = newname
def __str__(self):
return "animal:" + str(self.name) + ":" + str(self.age)Getters and setters should be used outside of the class to access data attributes.
The author of a class definition may change data attribute variable names:
class Animal(object):
def __init__(self, age):
self.years = age # replaced the age data
def get_age(self): # attribute by years
return self.yearsa.get_age(), not a.age
Python allows you to:
It’s not good style to do any of these!
flowchart TD
O[object] --> A[Animal]
A --> C[Cat]
A --> P[Person]
A --> R[Rabbit]
P --> S[Student]
class Animal(object):
def __init__(self, age):
self.age = age
self.name = None
def get_age(self):
return self.age
def get_name(self):
return self.name
def set_age(self, newage):
self.age = newage
def set_name(self, newname=""):
self.name = newname
def __str__(self):
return "animal:" + str(self.name) + ":" + str(self.age)object implements basic operations in Python.object is automatically inherited.class Cat(Animal):
def speak(self):
print("meow")
def __str__(self):
return "cat:" + str(self.name) + ":" + str(self.age)Cat inherits all attributes of Animal: __init__(), age, name, get_age(), get_name(), set_age(), set_name(), __str__()speak():
Cat can be called with the new methodAnimal throws an error if called with Cat’s new method__str____init__ is not missing — Cat uses the Animal version.Person Subclassclass Person(Animal): # parent class is Animal
def __init__(self, name, age):
Animal.__init__(self, age) # call Animal constructor
self.set_name(name) # call Animal's method
self.friends = [] # add a new data attribute
def get_friends(self): # new methods
return self.friends
def add_friend(self, fname):
if fname not in self.friends:
self.friends.append(fname)
def speak(self):
print("hello")
def age_diff(self, other):
diff = self.age - other.age
print(abs(diff), "year difference")
def __str__(self): # override Animal's __str__
return "person:" + str(self.name) + ":" + str(self.age)super()__init__, the parent’s __init__ is not called automatically.Student Subclassimport random
class Student(Person):
def __init__(self, name, age, major=None):
# Person.__init__(self, name, age) # the OLD way
super().__init__(name, age) # the new way
self.major = major
def change_major(self, major):
self.major = major
def speak(self):
r = random.random()
if r < 0.25:
print("i have homework")
elif 0.25 <= r < 0.5:
print("i need sleep")
elif 0.5 <= r < 0.75:
print("i should eat")
else:
print("i am watching tv")
def __str__(self):
return "student:" + str(self.name) + ":" + str(self.age) + ":" + str(self.major)Rabbit Subclassclass Rabbit(Animal):
tag = 1 # class variable
def __init__(self, age, parent1=None, parent2=None):
Animal.__init__(self, age)
self.parent1 = parent1
self.parent2 = parent2
self.rid = Rabbit.tag # instance variable
Rabbit.tag += 1tag is used to give a unique id to each new Rabbit instance.Rabbit Getter Methodsclass Rabbit(Animal):
tag = 1
def __init__(self, age, parent1=None, parent2=None):
Animal.__init__(self, age)
self.parent1 = parent1
self.parent2 = parent2
self.rid = Rabbit.tag
Rabbit.tag += 1
def get_rid(self):
return str(self.rid).zfill(3)
def get_parent1(self):
return self.parent1
def get_parent2(self):
return self.parent2zfill is a string method that pads the beginning with zeros — for example, 001 not 1.Rabbit class; get_name and get_age are inherited from Animal. def __add__(self, other):
# returning object of same type as this class
return Rabbit(0, self, other)Rabbit’s __init__(self, age, parent1=None, parent2=None).+ operator between two Rabbit instances:
r4 = r1 + r2 does, where r1 and r2 are Rabbit instancesr4 is a new Rabbit instance with age 0r4 has self as one parent and other as the other parent__init__, parent1 and parent2 are of type RabbitRabbits def __eq__(self, other):
parents_same = self.parent1.rid == other.parent1.rid \
and self.parent2.rid == other.parent2.rid
parents_opposite = self.parent2.rid == other.parent1.rid \
and self.parent1.rid == other.parent2.rid
return parents_same or parents_oppositeself.parent1 == other.parent1__eq__ method over and over until it is called on None, and gives an AttributeError when it tries to do None.parent1When to use each:
| Relationship | Use | Example |
|---|---|---|
| IS-A | Inheritance | Cat is an Animal |
| HAS-A | Composition | Car has an Engine |
class Engine:
def start(self):
print("Vroom")
class Car:
def __init__(self):
self.engine = Engine() # Car owns an Engine
def drive(self):
self.engine.start()
print("Moving")The property decorator is built into Python and allows you to use class methods as data attributes.
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
# Calculated on the fly! No self._area variable needed.
return self.width * self.height
# Usage
r = Rectangle(5, 10)
print(r.area) # 50
# r.area = 60 # AttributeError: can't set attribute__init__, call the parent’s with super().__init__(...).Rabbit.tag) are shared by all instances.