Search icon
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletters
Free Learning
Arrow right icon
Scientific Computing with Python 3

You're reading from  Scientific Computing with Python 3

Product type Book
Published in Dec 2016
Publisher Packt
ISBN-13 9781786463517
Pages 332 pages
Edition 1st Edition
Languages
Authors (3):
Claus Führer Claus Führer
Profile icon Claus Führer
Jan Erik Solem Jan Erik Solem
Olivier Verdier Olivier Verdier
View More author details

Table of Contents (23) Chapters

Scientific Computing with Python 3
Credits
About the Authors
About the Reviewer
www.PacktPub.com
Acknowledgement
Preface
1. Getting Started 2. Variables and Basic Types 3. Container Types 4. Linear Algebra – Arrays 5. Advanced Array Concepts 6. Plotting 7. Functions 8. Classes 9. Iterating 10. Error Handling 11. Namespaces, Scopes, and Modules 12. Input and Output 13. Testing 14. Comprehensive Examples 15. Symbolic Computations - SymPy References

Chapter 8. Classes

In mathematics, when we write sin, we refer to a mathematical object for which we know many methods from elementary calculus. For example:

  • We might want to evaluate sin x at x=0.5, that is, compute sin(0.5), which returns a real number
  • We might want to compute its derivative, which gives us another mathematical object, cos
  • We might want to compute the first three coefficients of its Taylor polynomial

These methods may be applied not only to sin but also to other sufficiently smooth functions. There are, however, other mathematical objects (for example, the number 5) for which these methods make no sense. Objects that have the same methods are grouped together in abstract classes, for example, functions. Every statement and every method that can be applied to functions applies in particular to sin or cos. Other examples for such classes might be a rational number, for which a denominator and numerator method exist; an interval, which has a left and right boundary method; an...

Introduction to classes


We will illustrate the concept of classes with an example of rational numbers, that is, numbers of the form q= qN ⁄ qD, where qN and qD are integers.

Figure 8.1: An example of a class declaration

We use rational numbers here only as an example for the class concept. For future work in Python with rational numbers use the fractions module (refer to [6]).

Class syntax

The definition of a class is made by a block command with the class keyword, the name of the class, and some statements in the block (refer to Figure 8.1):

class RationalNumber: 
      pass

An instance of this class (or in other words, an object of the type RationalNumber) is created by

r = RationalNumber()

and a query type(a) returns the answer,  <class'__main__.RationalNumber'>. If we want to investigate whether an object is an instance of this class, we can use this:

if isinstance(a, RationalNumber):
    print('Indeed it belongs to the class RationalNumber')  

So far we've generated an object...

Attributes and methods


One of the main reasons for working with classes is that objects can be grouped together and bound to a common object. We saw this already when looking at rational numbers; denominator and numerator are two objects which we bound to an instance of the RationalNumber class. They are called attributes of the instance. The fact that an object is an attribute of a class instance becomes apparent from the way they are referenced, which we have used tacitly before:

<object>.attribute

Here are some examples of instantiation and attribute reference:

q = RationalNumber(3, 5) # instantiation
q.numerator     # attribute access
q.denominator

a = array([1, 2])    # instantiation
a.shape

z = 5 + 4j    # instantiation
z.imag

Once an instance is defined we can set, change or delete attributes of that particular instance. The syntax is the same as for regular variables:

q = RationalNumber(3, 5) 
r = RationalNumber(7, 3)
q.numerator...

Attributes that depend on each other


Attributes of an instance can be changed (or created) by simply assigning them a value. However, if other attributes depend on the one just changed, it is desirable to change these simultaneously:

Let us consider a class that defines an object for planar triangles from three given points. A first attempt to set up such a class could be as follows:

class Triangle:
    def __init__(self,  A, B, C):
        self.A = array(A)
        self.B = array(B)
        self.C = array(C)
        self.a = self.C - self.B
        self.b = self.C - self.A
        self.c = self.B - self.A
    def area(self):
        return abs(cross(self.b, self.c)) / 2

An instance of this triangle is created by this:

tr = Triangle([0., 0.], [1., 0.], [0., 1.])

And its area is computed by this:

tr.area() # returns 0.5

If we change an attribute, say point B, the corresponding edges a and c are not automatically updated and the computed area is wrong...

Bound and unbound methods


We will now take a closer look at attributes that are methods. Let us consider an example:

class A:
    def func(self,arg):
        pass

A little inspection shows us how the nature of func changes after creating an instance:

A.func  # <unbound method A.func>
instA = A()  # we create an instance
instA.func  #  <bound method A.func of ... >

Calling, for example,  A.func(3) would result in an error message such as this:

TypeError: func() missing 1 required positional argument: 'arg'

instA.func(3) is executed as expected. Upon creation of an instance, the func method is bound to the instance. The self argument gets the instance assigned as its value. Binding a method to an instance makes the method usable as a function. Before that, it is of no use. Class methods, which we will consider later, are different in this aspect.

Class attributes


Attributes specified in the class declaration are called class attributes. Consider the following example:

class Newton:
    tol = 1e-8 # this is a class attribute
    def __init__(self,f):
        self.f = f # this is not a class attribute
    ....

Class attributes are useful for simulating default values and can be used if values have to be reset:

N1 = Newton(f)
N2 = Newton(g)

Both instances have an attribute, tol, with the value initialized in the class definition:

N1.tol # 1e-8
N2.tol # 1e-8

Altering the class attribute automatically affects all the corresponding attributes of all instances:

Newton.tol = 1e-10
N1.tol # 1e-10
N2.tol # 1e-10

Altering tol for one instance does not affect the other instance:

N2.tol = 1.e-4
N1.tol  # still 1.e-10

But now N2.tol is detached from the class attribute. Changing Newton.tol no longer has any effect on N2.tol:

Newton.tol = 1e-5 # now all instances of the Newton classes have 1e-5
N1.tol # 1...

Class methods


We saw in the previous section on Bound and unbound methods how methods are either bound to an instance of a class or remain in a state as unbound methods. Class methods are different. They are always bound methods. They are bound to the class itself.

We will first describe the syntactic details and then give some examples to show what these methods can be used for. To indicate that a method is a class method the decorator line precedes the method definition:

@classmethod

While standard methods make a reference to an instance by the use of their first argument, the first argument of a class method refers to the class itself. By convention the first argument is called self for standard methods and cls for class methods.

  • Standard case:
      class A:
          def func(self,*args):
               <...>
  • Class method case:
      class B:
          @classmethod
          def func(cls,*args):
               <...>

In practice, class methods may be useful...

Subclassing and inheritance


In this section, we will introduce some central concepts from object-oriented programming: abstract classes, subclasses, and inheritance. To guide you through these concepts, we consider another mathematical example: one-step methods for solving a differential equation. The generic form of an ordinary initial value problem is

The data is the right-hand side function f, the initial value x0 , and the interval of interest [t0 , te]. The solution of this problem is a function . A numerical algorithm gives this solution as a vector u of discrete values ui being approximations to x(ti). Here, are discretized values of the independent variable t, which in physical models often represents time.

A one-step method constructs the solution values ui by the recursion steps:

Here, Φ is a step function that characterizes the individual methods (refer to [28]):

  • Explicit Euler:  
  • Midpoint Rule:  
  • Runge–Kutta 4 with 

What we did here is the typical way of describing a mathematical...

Encapsulation


Sometimes the use of inheritance is impractical or even impossible. This motivates the use of encapsulation. We will explain the concept of encapsulation by considering Python functions, that is, objects of the Python type function, which we encapsulate in a new class, Function, and provide with some relevant methods:

class Function:
    def __init__(self, f):
        self.f = f
    def __call__(self, x):
        return self.f(x)
    def __add__(self, g):
        def sum(x):
            return self(x) + g(x)
        return type(self)(sum) 
    def __mul__(self, g): 
        def prod(x):
            return self.f(x) * g(x)
        return type(self)(prod)
    def __radd__(self, g):
        return self + g
    def __rmul__(self, g):
        return self * g

Note that the __add__ and __mul__ operations should return an instance of the same class. This is achieved by the return type(self)(sum) statement...

Classes as decorators


In section Function as decorators in Chapter 7, Functions , we saw how functions can be modified by applying another function as a decorator. In previous examples, we saw how classes can be made to behave as functions as long as they are provided with the __call__ method. We will use this here to show how classes can be used as decorators.

Let us assume that we want to change the behavior of some functions in such a way that before the function is invoked, all input parameters are printed. This could be useful for debugging purposes. We take this situation as an example to explain the use of a decorator class:

class echo:
    text = 'Input parameters of {name}n'+
        'Positional parameters {args}n'+
        'Keyword parameters {kwargs}n'
    def __init__(self, f):
        self.f = f
    def __call__(self, *args, **kwargs):
        print(self.text.format(name = self.f.__name__,
              args = args, kwargs = kwargs))
...

Summary


One of the most important programming concepts in modern computer science is object-oriented programming. We learned in this chapter how to define objects as instances of classes, which we provide with methods and attributes. The first parameter of methods, usually denoted by self, plays an important and special role. You saw methods that can be used to define basic operations such as + and * for your own classes.

While in other programming languages attributes and methods can be protected against unintended use, Python allows a technique to hide attributes and access these hidden attributes through special getter and setter methods. To this end, you met an important function, property.

Exercises


Ex. 1 → Write a method simplify to the class RationalNumber. This method should return the simplified version of the fraction as a tuple.

Ex. 2 → To provide results with confidence intervals a special calculus, so-called interval arithmetic is introduced in numerical mathematics; (refer to [3, 14]). Define a class called Interval and provide it with methods for addition, subtraction, division, multiplication, and power (with positive integers only). These operations obey the following rules:

.

Provide this class with methods that allow operations of the type a + I, a I, I + a, I a, where I is an interval and a an integer or float. Convert an integer or float to an interval [a,a] first. (Hint: you may want to use function decorators for this; (refer to section Function as decorators in Chapter 7, Functions). Furthermore, implement the __contains__ method, which enables you to check if a given number belongs to the interval using the syntax x in I for an object I of type Interval...

lock icon The rest of the chapter is locked
You have been reading a chapter from
Scientific Computing with Python 3
Published in: Dec 2016 Publisher: Packt ISBN-13: 9781786463517
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at AU $19.99/month. Cancel anytime}