Download presentation
Presentation is loading. Please wait.
1
COMPSCI 107 Computer Science Fundamentals
Lecture 08 – Classes
2
Learning outcomes At the end of this lecture, students should be able to: Define a new class Store state information about instances of the class Define new methods of the class Override the default behaviour for standard operations COMPSCI Computer Science Fundamentals
3
Why classes? Managing Complexity
4
Motivation Separates high-level constructs from implementation details
Reduces complexity
5
Motivation Separates high-level constructs from implementation details
Reduces complexity client code class definition p = Polynomial() p.add_term(5, 2) p.add_term(1, 1) p.add_term(7, 0) q = Polynomial() q.add_term(10, -2) result = p.add(q) print(result) print(result.evaluate(2)) class Polynomial: ....
6
Motivation Separates high-level constructs from implementation details
Modular code client code class definition p = Polynomial() p.add_term(5, 2) p.add_term(1, 1) p.add_term(7, 0) q = Polynomial() q.add_term(10, -2) result = p.add(q) print(result) print(result.evaluate(2)) class Polynomial: .... contract / interface
7
Motivation Separates high-level constructs from implementation details
Modular code client code class definition p = Polynomial() p.add_term(5, 2) p.add_term(1, 1) p.add_term(7, 0) q = Polynomial() q.add_term(10, -2) result = p.add(q) print(result) print(result.evaluate(2)) class Polynomial: .... contract / interface
8
Motivation Separates high-level constructs from implementation details
Modular code client code class definition p = Polynomial() p.add_term(5, 2) p.add_term(1, 1) p.add_term(7, 0) q = Polynomial() q.add_term(10, -2) result = p.add(q) print(result) print(result.evaluate(2)) class Polynomial: .... contract / interface
9
Object-oriented programming
Here are two objects (from the “real world”): They are both the same kind of thing (car) class So they both have the same kinds of attributes state They can also “do” the same kinds of actions behaviour
10
Object-oriented programming
And here are two objects of different kinds: In object-oriented programming, we try to model the problem we are solving using objects. We can have many objects of various kinds (classes). At any point in time, each object in our program has some particular state (a set of variables storing data for that object). We can perform actions on the objects in our program by calling methods on them.
11
(e.g. accelerate, brake, refuel, ...)
Classes vs. objects Blueprint Building new cars State State Shared behaviour (e.g. accelerate, brake, refuel, ...)
12
(e.g. accelerate, brake, refuel, ...)
Classes vs. objects Blueprint Building new cars State State Shared behaviour (e.g. accelerate, brake, refuel, ...) class Car: .... Car class
13
(e.g. accelerate, brake, refuel, ...)
Classes vs. objects Blueprint Building new cars State State Shared behaviour (e.g. accelerate, brake, refuel, ...) a = Car() b = Car() class Car: .... Creating new objects Instantiating objects Car class
14
Classes vs. objects Blueprint Building new cars State State
Shared behaviour (e.g. accelerate, brake, refuel, ...) a = Car() b = Car() class Car: .... state state a Creating new objects Instantiating objects Car class b Car objects Instances of the Car class
15
Classes Python has a number of classes built-in
list, dict, int, float, bool, str,.... a = [10, 20, 30] b = {'a': 5, 'b': 10} c = 10 d = 10.55 e = True f = 'Hello' print(type(a)) print(type(b)) print(type(c)) print(type(d)) print(type(e)) print(type(f)) <class 'list'> <class 'dict'> <class 'int'> <class 'float'> <class 'bool'> <class 'str'>
16
Classes We can define our own classes Classes consist of
This allows us to create new types of objects in Python Think of a class definition as a blueprint that can be used to create many objects of the same type Classes consist of State variables (sometimes called instance variables) Methods (functions that are linked to a particular instance of the class) class Name_of_class: definition goes here
17
The simplest class possible
We can define a class with an “empty” definition Can’t be truly empty – the statement “pass” is a statement that does nothing “pass” is often used as a placeholder during code development class Point: pass p = Point() print(p) p.x = 10 p.y = 20 print(p.x, p.y) <__main__.Point object at 0x026A7410> 10 20
18
Saving the class Classes are designed to help build modular code
Classes can be defined within a module that also contains application code, or they can be defined in separate files Multiple classes can be defined in the same file In this course, we will typically store each class in their own module To use a class in another module, you will need to import the module Geometry.py main.py class Point: pass from Geometry import Point p = Point()
19
The object in memory Visualise objects as enclosing a set of “attributes” Attributes are just like variables, but they are associated with the object main.py from Geometry import Point p = Point() p.x = 5 p.y = 7 x 5 7 y p
20
Initialising the state of the object
We may want to define the Point class such that when we create an object we also set the initial values of the attributes main.py from Geometry import Point p = Point(5, 7) To allow this, we need to define a special method of the Point class called a initialiser. The initialiser method is called whenever you create a Point object.
21
Initialisers Each class should contain a initialiser method x 5 p y 7
The name of the method is __init__ The method always has at least one input parameter, called “self” “self” is a reference to the object that we are creating The constructor method can have other parameters main.py Geometry.py from Geometry import Point p = Point(5, 7) print(p.x) print(p.y) class Point: def __init__(self, loc_x, loc_y): self.x = loc_x self.y = loc_y x 5 p y 7
22
Adding functionality to the class
Defining more methods For example, we could add a method to our Point class to shift a point by a given amount in horizontal and vertical directions The method is named normally, but has the additional parameter “self” as the very first parameter All methods that are called on an object need the “self” parameter main.py Geometry.py from Geometry import Point p = Point(2, 3) print(p.x, p.y) p.translate(1, 0) class Point: def __init__(self, loc_x, loc_y): self.x = loc_x self.y = loc_y def translate(self, dx, dy): self.x += dx self.y += dy We call these methods using the name of the object (p) followed by a dot (.) followed by the name of the method (translate)
23
Adding functionality to the class
main.py Geometry.py from Geometry import Point p = Point(2, 3) print(p.x, p.y) p.translate(1, 0) class Point: def __init__(self, loc_x, loc_y): self.x = loc_x self.y = loc_y def translate(self, dx, dy): self.x += dx self.y += dy x 2 p y 3
24
Adding functionality to the class
main.py Geometry.py from Geometry import Point p = Point(2, 3) print(p.x, p.y) p.translate(1, 0) class Point: def __init__(self, loc_x, loc_y): self.x = loc_x self.y = loc_y def translate(self, dx, dy): self.x += dx self.y += dy x 3 p y 3
25
How does this work? p.translate(1, 0) translate(p, 1, 0)
When you call a method like this: the method call translates to: p.translate(1, 0) translate(p, 1, 0) And this first parameter is the magical “self”!
26
Exercise Define a Square class with an initializer that accepts a number representing the length of the side of the square. Add a method to the class to calculate the perimeter of the square. The following code shows how the class may be used >>> from Geometry import Square >>> s = Square(10) >>> p = s.perimeter() >>> print(p) 40 COMPSCI Computer Science Fundamentals
27
Summary A class provides the definition for a certain kind of object
Classes define the variables that an object will use to store information Classes define the methods that we can use to perform actions with objects Example: main.py Geometry.py from Geometry import Square side = 10 s = Square(side) class Square: def __init__(self, s): self.size = s
28
Example: a Fraction class
Consider writing a class to represent a fraction in Python Create a fraction Add, subtract, multiply, divide two fractions Display a text representation of a fraction numerator 1 / 2 denominator
29
Example: a Fraction class
class Fraction: pass f = Fraction() f.num = 1 f.den = 2 print('{} / {}'.format(f.num, f.den))
30
Example: a Fraction class
Placing the definition of the Fraction class in a separate file helps to “abstract away” the details of the class implementation from the client code Fraction.py main.py class Fraction: pass from Fraction import Fraction f = Fraction()
31
Example Fraction object (aka “instance” of the Fraction class) f
32
Example 1 num den 2 f Fraction object
(aka “instance” of the Fraction class) num 1 den 2 f
33
Visualising Fraction objects
We could create several Fraction objects as follows: f1 = Fraction(1, 2) f2 = Fraction(3, 4) f3 = Fraction(7, 8) 1 numerator 2 denominator f1 3 numerator f2 4 denominator f3 7 numerator 8 denominator
34
Initialiser for the Fraction class
All classes must have an initialiser The initialiser for the Fraction class should store the numerator and the denominator Fraction.py class Fraction: def __init__(self, top, bottom): self.numerator = top self.denominator = bottom
35
Using the Fraction class
So far we can create a Fraction object: We can access the state variables directly Although it is considered not good practice to do so What else can we do with Fraction objects? Nothing yet... we need to write the methods! main.py from Fraction import Fraction f1 = Fraction(3, 4) main.py print(f1.numerator) print(f1.denominator)
36
Using the Fraction class
Why is accessing the state variables directly not advised? from Fraction import Fraction f1 = Fraction(3, 4) print(f1.numerator) print(f1.denominator) f1.denominator = 0 This shouldn’t be allowed – but currently we can’t prevent people using our Fraction class like this. What can we do?
37
Hiding the instance variables
To prevent direct modification of the data fields (instance variables), we can stop the client (the user of the class) from accessing them directly This is known as data hiding, which can be done by defining private data fields in a class In Python, the private data fields are defined with two leading underscore characters You can also define private methods in the same way (although this is not something we will do) class Fraction: def __init__(self, top, bottom): self.__numerator = top self.__denominator = bottom
38
Accessing private data fields
Private data fields can be accessed by code within the class definition, but they cannot be accessed by code outside the class (client code) To make a private data field accessible to the client, provide a method to return its value To enable a private data field to be modifiable, provide a method to set its value f1 = Fraction(3, 4) print(f1.__numerator) Traceback (most recent call last): File "lecture.py", line 5, in <module> print(f1.__numerator) AttributeError: 'Fraction' object has no attribute '__numerator'
39
Accessor and mutator methods
A “get” method is referred to as an accessor method A “set” method is referred to as a mutator method def get_numerator(self): return self.__numerator def set_numerator(self, top): self.__numerator = top Example: accessing the private data fields through accessor and mutator methods from Fraction import Fraction f1 = Fraction(1, 2) print(f1.get_numerator()) f1.set_numerator(12) 1 12
40
Overriding default behaviour
All classes get a number of special methods provided by default including methods for creating a text-based representation of the object and methods for comparing two objects of the class type But these default versions are not very useful. We should define our own that make more sense for our class. f1 = Fraction(1, 2) f2 = Fraction(1, 2) print(f1) print(f1 == f2) Calls a special function “__str__” <Fraction.Fraction object at 0x025E7410> False Calls a special function “__eq__”
41
The __str__ method You should define a __str__ method in your class
This should return a “nicely-formatted” version of the object class Fraction: def __init__(self, top, bottom): self.__numerator = top self.__denominator = bottom .... def __str__(self): return '(' + str(self.__numerator) + '/' + str(self.__denominator) + ')' f1 = Fraction(1, 2) print(f1) (1/2)
42
The __eq__ method The __eq__ method will be called automatically whenever you use “==” to compare two instances of your class the default behaviour just compares the references class Fraction: def __init__(self, top, bottom): self.__numerator = top self.__denominator = bottom def __eq__(self, other): return self.__numerator * other.__denominator == other.__numerator * self.__denominator f1 = Fraction(1, 2) f2 = Fraction(3, 4) f3 = Fraction(3, 6) print(f1 == f2) print(f1 == f3) False True
43
The __repr__ method There is one more special method that you should define The __repr__ method should return “a string that unambiguously describes the object” Ideally, the representation should be an expression that could be used to create the object class Fraction: def __init__(self, top, bottom): self.__numerator = top self.__denominator = bottom .... def __repr__(self): return 'Fraction(' + str(self.__numerator) + ', ' + str(self.__denominator) + ')' If you don’t define __str__ then by default it will call __repr__, but otherwise __str__ is used for printing. In this example, we call __repr__ explicitly using repr() f1 = Fraction(1, 2) print(repr(f1)) Fraction(1, 2)
44
Overloading operators
Python operators work for built-in classes But the same operator behaves differently with different types For example, consider “+” Performs arithmetic between two numbers Merges two lists Concatenates two strings Operator overloading is when one operator can perform different functions depending on the context
45
The __add__ method Another special method is __add__ (5/6)
This is called automatically when the “+” operator is used In other words, f1 + f2 gets translated into f1.__add__(f2) class Fraction: def __init__(self, top, bottom): self.__numerator = top self.__denominator = bottom def __add__(self, other): new_num = self.__numerator * other.__denominator + self.__denominator * other.__numerator new_den = self.__denominator * other.__denominator return Fraction(new_num, new_den) f1 = Fraction(1, 2) f2 = Fraction(1, 3) result = f1 + f2 print(result) (5/6)
46
Improving the __eq__ method
Recall the __eq__ method in our Fraction class: What happens here? def __eq__(self, other): return self.__numerator * other.__denominator == other.__numerator * self.__denominator x = Fraction(2, 3) y = Fraction(1, 3) z = y + y print(x == z) print(x is z) w = x + y print(w == 1)
47
Improving the __eq__ method
Recall the __eq__ method in our Fraction class: What happens here? def __eq__(self, other): return self.__numerator * other.__denominator == other.__numerator * self.__denominator x = Fraction(2, 3) y = Fraction(1, 3) z = y + y print(x == z) print(x is z) w = x + y print(w == 1)
48
Improving the __eq__ method
Recall the __eq__ method in our Fraction class: What happens here? def __eq__(self, other): return self.__numerator * other.__denominator == other.__numerator * self.__denominator x = Fraction(2, 3) y = Fraction(1, 3) z = y + y print(x == z) print(x is z) w = x + y print(w == 1) True False Traceback (most recent call last): File "lecture.py", line 13, in <module> print(w == 1) File "Fraction.py", line 19, in __eq__ return self.__numerator * ... AttributeError: 'int' object has no attribute '_Fraction__denominator'
49
Improving the __eq__ method
To fix this, we need to check if the type of “other” is a Fraction object But maybe we would like to be able to compare Fraction objects to integers? we could extend this so that if the type of “other” is an int, then we perform an appropriate comparison. def __eq__(self, other): if not isinstance(other, Fraction): return False return self.__numerator * other.__denominator == other.__numerator * self.__denominator
50
Simplifying fractions
There is no need to store numerators or denominators larger than required Simplifying fractions: calculate the greatest common divisor(GCD) of the numerator and denominator (i.e. the common divisors of 12 and 30 are 1, 2, 3 and 6) divide both numerator and denominator by GCD 12 / 30 is the same as 2 / 5
51
Calculating the GCD Fortunately, Euclid came up with a nice algorithm for computing this around 300BC Given two number, n and m, find the number k such that k is the largest number the evenly divides both n and m: Notice also that there is no “self” as the first input to this method. We will not be calling this method on an object – this is an example of a static method (it is not called on an object of the class) class Fraction: .... def __gcd(m, n): while m % n != 0: old_m = m old_n = n m = old_n n = old_m % old_n return n By defining this method with a leading “__”, it makes it private to the Fraction class. Client code (that uses the Fraction class) cannot directly call the gcd method (it is only used by the Fraction class itself to reduce fractions)
52
Improving the constructor
Now we can improve the constructor so that it always represents a fraction using the “lowest terms” (reduced) form: class Fraction: def __init__(self, top, bottom): common = Fraction.__gcd(top, bottom) self.__numerator = top // common self.__denominator = bottom // common Notice how static methods are called using the name of the class (and not a particular instance of the class)
53
Improving the constructor
Example from Fraction import Fraction x = Fraction(2,3) y = Fraction(3,4) z = x + y print(z) w = z + Fraction(5, 12) print(w) print(w + Fraction(1,6) == Fraction(2,1)) (17/12) (11/6) True
54
Other standard Python operators
Many standard operators and funtions: Common Arithmetic operators object.__add__(self, other) object.__sub__(self, other) object.__mul__(self, other) object.__floordiv__(self, other) object.__truediv__(self, other) Common Relational operators object.__lt__(self, other) object.__le__(self, other) object.__eq__(self, other) object.__ne__(self, other) object.__gt__(self, other) object.__ge__(self, other)
55
Summary A class is a template, a blueprint and a data type for objects
A class defines the data fields of objects, and provides an initialiser for initialising objects and other methods for manipulating data The initialiser is always called __init__() The first input parameter is always “self”, and this refers to the object being constructed The data fields in the class should be hidden to prevent data tampering and to make the class easy to maintain This is achieved by prefixing the field name with __ We can override the default methods in a class definition Such as __repr__ and __str__ and __eq__
Similar presentations
© 2024 SlidePlayer.com Inc.
All rights reserved.