Ch.9: Object-oriented programming Joakim Sundnes 1 , 2 1 Simula - - PDF document

ch 9 object oriented programming
SMART_READER_LITE
LIVE PREVIEW

Ch.9: Object-oriented programming Joakim Sundnes 1 , 2 1 Simula - - PDF document

Ch.9: Object-oriented programming Joakim Sundnes 1 , 2 1 Simula Research Laboratory 2 University of Oslo, Dept. of Informatics Oct 20, 2020 0.1 Plan for Oct 24 Exercises 7.10, 7.11, 7.12 and 7.25 Object-oriented programming (OOP)


slide-1
SLIDE 1

Ch.9: Object-oriented programming

Joakim Sundnes1,2

1Simula Research Laboratory 2University of Oslo, Dept. of Informatics

Oct 20, 2020

0.1 Plan for Oct 24

  • Exercises 7.10, 7.11, 7.12 and 7.25
  • Object-oriented programming (OOP)

– Introduction – Examples

0.2 The title Object-oriented programming (OOP) may mean two different things

  • 1. Programming with classes and objects (better: object-based programming)
  • 2. Programming with class hierarchies (class families)

0.3 New concept: collect classes in families (hierarchies)

What is a class hierarchy?

  • A family of closely related classes
  • A key concept is inheritance: child classes can inherit attributes and

methods from parent class(es) - this saves much typing and code duplication OO is a Norwegian invention by Ole-Johan Dahl and Kristen Nygaard in the 1960s - one of the most important inventions in computer science, because OO is used in all big computer systems today!

slide-2
SLIDE 2

0.4 Object-oriented programming

  • Everything in Python is an object, so all Python-programming is object-

based

  • Object-oriented programming (OOP) takes the ideas of classes and pro-

gramming a step further

  • We exploit a very useful property of classes; that they can be combined

and reused as building blocks.

  • If we define a class class A, we can define a second class class B(A).
  • Class B inherits all attributes and methods from A
  • Class becomes an extension of class A
  • We say that A is a superclass or base class, and B is a subclass of A

0.5 OOP in Python and scientific programming

  • OO is less important in Python than in C++, Java and C#, so the benefits
  • f OO are less obvious in Python
  • Our examples on OOP employ numerical methods for

b

a f(x)dx, f ′(x),

u′ = f(u, t) - make sure you understand the simplest of these numerical methods before you study the combination of OOP and numerics

  • Our goal: write general, reusable modules with lots of methods for numeri-

cal computing of b

a f(x)dx, f ′(x), u′ = f(u, t)

0.6 OOP fundamentals - inheritance

class A: def __init__(self,v0,v1) self.v0 = v0 self.v1 = v1 def f(self, x): return x**2 class B(A): def g(self, x): return x**4 class C(B): def h(self, x): return x**6

2

slide-3
SLIDE 3

We have now defined three classes

  • A: two attributes (v0, v1) and two methods (__init__, f)
  • B: two attributes (v0, v1) and three methods (__init__, f, g)
  • C: two attributes (v0, v1) and four methods (__init__, f, g, h)

0.7 Why is OOP more important in other languages?

Languages like Java and C++ have static typing. A function is declared to take input arguments of a certain type:

void my_func(A my_obj) { ... }

OOP gives extra flexibility, since this function will also accept arguments of classes B and C. OOP is still very useful in Python, to avoid code duplication and produce structured and readable code!

0.8 OOP fundamentals - overriding methods

A subclass can override methods in the superclass. Say we want to add some extra attributes in a subclass:

class A: def __init__(self,v0,v1) self.v0 = v0 self.v1 = v1 def f(self, x): return x**2 class B(A): def __init__(self,v0,v1,v2): self.v0 = v0 self.v1 = v1 self.v2 = v2 def g(self, x): return x**4

Usage:

a = A(v0=1, v1=2) #calling A.__init__ b = B(v0=1, v1=2, v3=3) #calling B.__init__

3

slide-4
SLIDE 4

0.9 The overridden method can still be called

A more elegant implementation looks like:

class A: def __init__(self,v0,v1) self.v0 = v0 self.v1 = v1 def f(self, x): return x**2 class B(A): def __init__(self,v0,v1,v2): super().__init__(v0,v1) #or A.__init__(self.v0,v1) self.v2 = v2 def g(self, x): return x**4

0.10 Example: a class for straight lines

Problem: Make a class for evaluating lines y = c0 + c1x. Code:

class Line: def __init__(self, c0, c1): self.c0, self.c1 = c0, c1 def __call__(self, x): return self.c0 + self.c1*x def table(self, L, R, n): """Return a table with n points for L <= x <= R.""" s = '' for x in linspace(L, R, n): y = self(x) s += '%12g %12g\n' % (x, y) return s

0.11 A class for parabolas

Problem: Make a class for evaluating parabolas y = c0 + c1x + c2x2. Code:

class Parabola: def __init__(self, c0, c1, c2): self.c0, self.c1, self.c2 = c0, c1, c2 def __call__(self, x): return self.c2*x**2 + self.c1*x + self.c0

4

slide-5
SLIDE 5

def table(self, L, R, n): """Return a table with n points for L <= x <= R.""" s = '' for x in linspace(L, R, n): y = self(x) s += '%12g %12g\n' % (x, y) return s

Observation: This is almost the same code as class Line, except for the things with c2

0.12 Class Parabola as a subclass of Line; principles

  • Parabola code = Line code + a little extra with the c2 term
  • Can we utilize class Line code in class Parabola?
  • This is what inheritance is about!

Writing

class Parabola(Line): pass

makes Parabola inherit all methods and attributes from Line, so Parabola has attributes c0 and c1 and three methods

  • Line is a superclass, Parabola is a subclass

(parent class, base class; child class, derived class)

  • Class Parabola must add code to Line’s constructor (an extra c2 attribute),

__call__ (an extra term), but table can be used unaltered

  • The idea is to reuse as much code in Line as possible and avoid duplicating

code

0.13 Class Parabola as a subclass of Line; code

A subclass method can call a superclass method in this way:

superclass_name.method(self, arg1, arg2, ...)

Class Parabola as a subclass of Line:

class Parabola(Line): def __init__(self, c0, c1, c2): Line.__init__(self, c0, c1) # Line stores c0, c1 self.c2 = c2 def __call__(self, x): return Line.__call__(self, x) + self.c2*x**2

5

slide-6
SLIDE 6

What is gained?

  • Class Parabola just adds code to the already existing code in class Line -

no duplication of storing c0 and c1, and computing c0 + c1x

  • Class Parabola also has a table method - it is inherited
  • __init__ and __call__ are overridden or redefined in the subclass

0.14 We can check class type and class relations with isinstance(obj, type) and issubclass(subclassname, superclassname)

>>> from Line_Parabola import Line, Parabola >>> l = Line(-1, 1) >>> isinstance(l, Line) True >>> isinstance(l, Parabola) False >>> p = Parabola(-1, 0, 10) >>> isinstance(p, Parabola) True >>> isinstance(p, Line) True >>> issubclass(Parabola, Line) True >>> issubclass(Line, Parabola) False >>> p.__class__ == Parabola True >>> p.__class__.__name__ # string version of the class name 'Parabola'

0.15 Line as a subclass of Parabola

  • Subclasses are often special cases of a superclass
  • A line c0 + c1x is a special case of a parabola c0 + c1x + c2x2
  • Can Line be a subclass of Parabola?
  • No problem - this is up to the programmer’s choice
  • Many will prefer this relation between a line and a parabola

6

slide-7
SLIDE 7

0.16 Code when Line is a subclass of Parabola

class Parabola: def __init__(self, c0, c1, c2): self.c0, self.c1, self.c2 = c0, c1, c2 def __call__(self, x): return self.c2*x**2 + self.c1*x + self.c0 def table(self, L, R, n): """Return a table with n points for L <= x <= R.""" s = '' for x in linspace(L, R, n): y = self(x) s += '%12g %12g\n' % (x, y) return s class Line(Parabola): def __init__(self, c0, c1): Parabola.__init__(self, c0, c1, 0)

Note: __call__ and table can be reused in class Line!

0.17 Recall the class for numerical differentiation from

  • Ch. 8

f ′(x) ≈ f(x + h) − f(x) h

class Derivative: def __init__(self, f, h=1E-5): self.f = f self.h = float(h) def __call__(self, x): f, h = self.f, self.h # make short forms return (f(x+h) - f(x))/h def f(x): return exp(-x)*cos(tanh(x)) from math import exp, cos, tanh dfdx = Derivative(f) print dfdx(2.0)

7

slide-8
SLIDE 8

0.18 There are numerous formulas numerical differentia- tion

f ′(x) = f(x + h) − f(x) h + O(h) f ′(x) = f(x) − f(x − h) h + O(h) f ′(x) = f(x + h) − f(x − h) 2h + O(h2) f ′(x) = 4 3 f(x + h) − f(x − h) 2h − 1 3 f(x + 2h) − f(x − 2h) 4h + O(h4) f ′(x) = 3 2 f(x + h) − f(x − h) 2h − 3 5 f(x + 2h) − f(x − 2h) 4h + 1 10 f(x + 3h) − f(x − 3h) 6h + O(h6) f ′(x) = 1 h

  • −1

6f(x + 2h) + f(x + h) − 1 2f(x) − 1 3f(x − h)

  • + O(h3)

0.19 How can we make a module that offers all these for- mulas?

It’s easy:

class Forward1: def __init__(self, f, h=1E-5): self.f = f self.h = float(h) def __call__(self, x): f, h = self.f, self.h return (f(x+h) - f(x))/h class Backward1: def __init__(self, f, h=1E-5): self.f = f self.h = float(h) def __call__(self, x): f, h = self.f, self.h return (f(x) - f(x-h))/h class Central2: # same constructor # put relevant formula in __call__

0.20 What is the problem with this type of code?

All the constructors are identical so we duplicate a lot of code. 8

slide-9
SLIDE 9
  • A general OO idea: place code common to many classes in a superclass

and inherit that code

  • Here: inhert constructor from superclass,

let subclasses for different differentiation formulas implement their version

  • f __call__

0.21 Class hierarchy for numerical differentiation

Superclass:

class Diff: def __init__(self, f, h=1E-5): self.f = f self.h = float(h)

Subclass for simple 1st-order forward formula:

class Forward1(Diff): def __call__(self, x): f, h = self.f, self.h return (f(x+h) - f(x))/h

Subclass for 4-th order central formula:

class Central4(Diff): def __call__(self, x): f, h = self.f, self.h return (4./3)*(f(x+h)

  • f(x-h))

/(2*h) - \ (1./3)*(f(x+2*h) - f(x-2*h))/(4*h)

0.22 Use of the differentiation classes

Interactive example: f(x) = sin x, compute f ′(x) for x = π

>>> from Diff import * >>> from math import sin >>> mycos = Central4(sin) >>> # compute sin'(pi): >>> mycos(pi)

  • 1.000000082740371

Central4(sin) calls inherited constructor in the superclass, while mycos(pi) calls __call__ in the subclass Central4 9

slide-10
SLIDE 10

0.23 Formulas for numerical integration

There are numerous formulas for numerical integration and all of them can be put into a common notation: b

a

f(x)dx ≈

n−1

  • i=0

wif(xi) wi: weights, xi: points (specific to a certain formula) The Trapezoidal rule has h = (b − a)/(n − 1) and xi = a + ih, w0 = wn−1 = h 2 , wi = h (i = 0, n − 1) The Midpoint rule has h = (b − a)/n and xi = a + h 2 + ih, wi = h

0.24 More formulas

Simpson’s rule has xi = a + ih, h = b − a n − 1 w0 = wn−1 = h 6 wi = h 3 for i even, wi = 2h 3 for i odd Other rules have more complicated formulas for wi and xi

0.25 Why should these formulas be implemented in a class hierarchy?

  • A numerical integration formula can be implemented as a class: a, b and

n are attributes and an integrate method evaluates the formula

  • All such classes are quite similar: the evaluation of

j wjf(xj) is the same,

  • nly the definition of the points and weights differ among the classes
  • Recall: code duplication is a bad thing!
  • The general OO idea: place code common to many classes in a superclass

and inherit that code

  • Here we put

j wjf(xj) in a superclass (method integrate)

10

slide-11
SLIDE 11
  • Subclasses extend the superclass with code specific to a math formula, i.e.,

wi and xi in a class method construct_rule

0.26 The superclass for integration

class Integrator: def __init__(self, a, b, n): self.a, self.b, self.n = a, b, n self.points, self.weights = self.construct_method() def construct_method(self): raise NotImplementedError('no rule in class %s' % \ self.__class__.__name__) def integrate(self, f): s = 0 for i in range(len(self.weights)): s += self.weights[i]*f(self.points[i]) return s def vectorized_integrate(self, f): # f must be vectorized for this to work return dot(self.weights, f(self.points))

0.27 A subclass: the Trapezoidal rule

class Trapezoidal(Integrator): def construct_method(self): h = (self.b - self.a)/float(self.n - 1) x = linspace(self.a, self.b, self.n) w = zeros(len(x)) w[1:-1] += h w[0] = h/2; w[-1] = h/2 return x, w

0.28 Another subclass: Simpson’s rule

  • Simpson’s rule is more tricky to implement because of different formulas

for odd and even points

  • Don’t bother with the details of wi and xi in Simpson’s rule now - focus
  • n the class design!

11

slide-12
SLIDE 12

class Simpson(Integrator): def construct_method(self): if self.n % 2 != 1: print 'n=%d must be odd, 1 is added' % self.n self.n += 1 <code for computing x and w> return x, w

0.29 About the program flow

Let us integrate 2

0 x2dx using 101 points:

def f(x): return x*x method = Simpson(0, 2, 101) print method.integrate(f)

Important:

  • method = Simpson(...): this invokes the superclass constructor, which

calls construct_method in class Simpson

  • method.integrate(f) invokes the inherited integrate method, defined

in class Integrator

0.30 Summary of object-orientation principles

  • A subclass inherits everything from the superclass
  • When to use a subclass/superclass?

– if code common to several classes can be placed in a superclass – if the problem has a natural child-parent concept

  • The program flow jumps between super- and sub-classes
  • It often takes time to master when and how to use OOP
  • Typical exercise in OOP; when creating a subclass, examine the superclass

to identify the parts that can be reused, and what needs to be added. Often, the subclass definition can be quite short! 12