Classes
Classes
Classes
A class packs together data and functions in a single unit. As seen in previous
chapters, functions that are bound to a class or an object are usually called
methods, and we will stick to this notation in the present chapter. Classes
have some similarity with modules, which are also collections of variables and
functions that naturally belong together. However, while there can be only
a single instance of a module, we can create multiple instances of a class.
Different instances of the same class can contain different data, but they all
behave in the same way and have the same methods. Think of a basic Python
class such as int; we can create many integer variables in a program, and
they obviously have different values (data), but we know that they all have
the same general behavior and the same set of operations defined for them.
© The Author(s) 2020
J. Sundnes, Introduction to Scientific Programming with 115
Python, Simula SpringerBriefs on Computing 6,
https://doi.org/10.1007/978-3-030-50356-7_8
116 8 Classes
The same goes for more complex Python classes such as lists and strings;
different objects contain different data, but they all have the same methods.
The classes we create in this chapter behave in exactly the same way.
First example: A class representing a function. To start with a familiar
example, we return to the formula calculating atmospheric pressure p as a
function of altitude h. The formula we used is a simplification of a more
general barometric formula, given by:
where M is the molar mass of air, g is the gravitational constant, R is the gas
constant, T is temperature, and p0 is the pressure at sea level. We obtain the
simpler formula used earlier by defining the scale height as h0 = RT /M g. It
could be interesting to evaluate (8.1) for different temperatures and, for each
value of T , to create a table or plot of how the pressure varies with altitude.
For each value of T , we need to call the function many times, with different
values of h. How should we implement this in a convenient way? One possible
solution would be to have both h and T as arguments:
from math import exp
return p0 * exp(-M*g*h/(R*T))
This solution obviously works, but if we want to call the function many times
for the same value of T then we still need to pass it as an argument every
time it is called. However, what if the function is to be passed as an argument
to another function that expects it to take a single argument only?1 In this
case, our function with two arguments will not work. A partial solution would
be to include a default value for the T argument, but we would still have a
problem if we want a different value of T.
Another solution would be to have h as the only argument, and T as a
global variable:
T = 245.0
def barometric(h):
g = 9.81 #m/(s*s)
1
This situation is quite common in Python programs. Consider, for instance, the
implementation of Newton’s method in Chapter 4, in the functions Newton and Newton2.
These functions expect two functions as arguments (f and dfdx), and both are expected
to take a single argument (x). Passing in a function that requires two or more arguments
will lead to an error.
8.1 Basics of Classes 117
R = 8.314 #J/(K*mol)
M = 0.02896 #kg/mol
p0 = 100.0 #kPa
return p0 * exp(-M*g*h/(R*T))
We now have a function that takes a single argument, but defining T as a
global variable is not very convenient if we want to evaluate y(t) for different
values of T. We could also set T as a local variable inside the function and
define different functions barometric1(h), barometric2(h), etc., for dif-
ferent values of T, but this is obviously inconvenient if we want many values
of T. However, we shall see that programming with classes and objects of-
fers exactly what we need: a convenient solution to create a family of similar
functions that all have their own value of T.
As mentioned above, the idea of a class is to pack together data and
methods (or functions) that naturally operate on the data. We can make a
class Barometric for the formula at hand, with the variables R, T,‘M‘, g, and
p0 as data, and a method value(t) for evaluating the formula. All classes
should also have a method named __init__ to initialize the variables. The
following code defines our function class
class Barometric:
def __init__(self, T):
self.T = T #K
self.g = 9.81 #m/(s*s)
self.R = 8.314 #J/(K*mol)
self.M = 0.02896 #kg/mol
self.p0 = 100.0 #kPa
define all the constants used in the formula – self.T, self.g, and so on –
where the prefix self means that these variables become bound to the object
created. Such bound variables are called attributes. Finally, we define the
method value, which evaluates the formula using the predefined and object-
bound parameters self.T, self.g, self.R, self.M, and self.p0. After
we have defined the class, every time we write a line such as
b1 = Barometric(T=245)
we create a new variable (instance) b1 of type Barometric. The line looks like
a regular function call, but, since Barometric is the definition of a class and
not a function, Barometric(T=245) is instead a call to the class’ constructor.
The constructor creates and returns an instance of the class with the specified
values of the parameters, and we assign this instance to the variable b. All the
__init__ functions we encounter in this book will follow exactly the same
recipe. Their purpose is to define a number of attributes for the class, and
they will typically contain one or more lines of the form self.A = A, where
A is either an argument passed to the constructor or a value defined inside
it.
As always in programming, there are different ways to achieve the same
thing, and we could have chosen a different implementation of the class above.
Since the only argument to the constructor is T, the other attributes never
change and they could have been local variables inside the value method:
class Barometric1:
def __init__(self, T):
self.T = T #K
Notice that, inside the value method, we only use the self prefix for T, since
this is the only variable that is a class attribute. In this version of the class
the other variables are regular local variables defined inside the method. This
class does exactly the same thing as the one defined above, and one could
argue that this implementation is better, since it is shorter and simpler than
the one above. However, defining all the physical constants in one place (in
the constructor) can make the code easier to read, and the class easier to
extend with more methods. As a third possible implementation, we could
move some of the calculations from the value method to the constructor:
class Barometric2:
def __init__(self, T):
g = 9.81 #m/(s*s)
R = 8.314 #J/(K*mol)
M = 0.02896 #kg/mol
self.h0 = R*T/(M*g)
self.p0 = 100.0 #kPa
8.1 Basics of Classes 119
In this class, we use the definition of the scale height from above and compute
and store this value as an attribute inside the constructor. The attribute
self.h0 is then used inside the value method. Notice that the constants g,
R, and M are, in this case, local variables in the constructor, and neither these
nor T are stored as attributes. They are only accessible inside the constructor,
while self.p0 and self.h0 are stored and can be accessed later from within
other methods.
At this point, many will be confused by the self variable, and the fact
that, when we define the methods __init__ and value they take two ar-
guments, but, when calling them, they take only one. The explanation for
this behavior is that self represents the object itself, and it is automatically
passed as the first argument when we call a method bound to the object.
When we write
p1 = b1.value(2469)
Here we explicitly call the value method that belongs to the Barometric
class and pass the instance b1 as the first argument. Inside the method, b1
then becomes the local variable self, as is usual when passing arguments
to a function, and we can access its attributes T, g, and so on. Exactly the
same thing happens when we call b1.value(2469), but now the object b1 is
automatically passed as the first argument to the method. It looks as if we
are calling the method with a single argument, but in reality it gets two.
The use of the self variable in Python classes has been the subject of many
discussions. Even experienced programmers find it confusing, and many have
questioned why the language was designed this way. There are some obvious
advantages to the approach, for instance, it very clearly distinguishes between
instance attributes (prefixed with self) and local variables defined inside a
method. However, if one is struggling to see the reasoning behind the self
variable, it is sufficient to remember the following two rules: (i) self is always
the first argument in a method definition, but is never inserted when the
method is called, and (ii) to access an attribute inside a method, the attribute
needs to be prefixed with self.
An advantage of creating a class for our barometric function is that we
can now send b1.value as an argument to any other function that expects
a function argument f that takes a single argument. Consider, for instance,
the following small example, where the function make_table prints a table
of the function values for any function passed to it:
from math import sin, exp, pi
120 8 Classes
def g(t):
return sin(t)*exp(-t)
b1 = Barometric(2469)
make_table(b1.value, 2*pi, 11) # send class method
Because of how f(t) is used inside the function, we need to send make_table
a function that takes a single argument. Our b1.value method satisfies this
requirement, but we can still use different values of T by creating multiple
instances.
More general Python classes. Of course, Python classes have far more
general applicability than just the representation of mathematical functions.
A general Python class definition follows the recipe outlined in the example
above, as follows:
class MyClass:
def __init__(self, p1, p2,...):
self.attr1 = p1
self.attr2 = p2
...
def method2(self):
...
print(...)
We can define as many methods as we want inside the class, with or with-
out arguments. When we create an instance of the class the methods be-
come bound to the instance, and are accessed with the prefix, for instance,
m.method2() if m is an instance of MyClass. It is common to have a construc-
tor where attributes are initialized, but this is not a requirement. Attributes
can be defined whenever desired, for instance, inside a method, as in the line
self.attrx = arg in the example above, or even from outside the class:
m = MyClass(p1,p2, ...)
m.new_attr = p3
8.2 Protected Class Attributes 121
The second line here creates a new attribute new_attr for the instance m of
MyClass. Such addition of attributes is entirely valid, but it is rarely good
programming practice since we can end up with instances of the same class
having different attributes. It is a good habit to always equip a class with a
constructor and to primarily define attributes inside the constructor.
def print_info(self):
first = self.first_name; last = self.last_name
number = self.number; bal = self.balance
s = f’{first} {last}, {number}, balance: {balance}’
print(s)
Typical use of the class could be something like the following, where we
create two different account instances and call the various methods for de-
posits, withdrawals, and printing information:
>>> a1 = Account(’John’, ’Olsson’, ’19371554951’, 20000)
>>> a2 = Account(’Liz’, ’Olsson’, ’19371564761’, 20000)
>>> a1.deposit(1000)
>>> a1.withdraw(4000)
>>> a2.withdraw(10500)
>>> a1.withdraw(3500)
>>> print "a1’s balance:", a1.balance
a1’s balance: 13500
>>> a1.print_info()
John Olsson, 19371554951, balance: 13500
>>> a2.print_info()
122 8 Classes
def print_info(self):
first = self.first_name; last = self.last_name
number = self.number; bal = self.balance
s = f’{first} {last}, {number}, balance: {balance}’
print(s)
When using this class, it will still be technically possible to access the at-
tributes directly, as in
a1 = BankAccountP(’John’, ’Olsson’, ’19371554951’, 20000)
a1._number = ’19371554955’
However, all experienced Python programmers will know that the second
line is a serious violation of good coding practice and will look for a better
8.3 Special Methods 123
way to solve the task. When using code libraries developed by others, such
conventions are risky to break, since internal data structures can change,
while the interface to the class is more static. The convention of protected
variables is how programmers tell users of the class what can change and
what is static. Library developers can decide to change the internal data
structure of a class, but users of the class might not even notice this change
if the methods to access the data remain unchanged. Since the class interface
is unchanged, users who followed the convention will be fine, but users who
have accessed protected attributes directly could be in for a surprise.
Now we can call an instance of the class Barometric just as any other Python
function
baro = Barometric(245)
p = baro(2346) #same as p = baro.__call__(2346)
The instance baro now behaves and looks like a function. The method is
exactly the same as the value method, but creating a special method by
renaming it to __call__ produces nicer syntax when the class is used.
Special method for printing. We are used to printing an object a using
print(a), which works fine for Python’s built-in object types such as strings
and lists. However, if a is an instance of a class we defined ourselves, we
do not obtain much useful information, since Python does not know what
information to show. We can solve this problem by defining a special method
named __str__ in our class. The __str__ method must return a string ob-
ject, preferably a string that provides useful information about the object,
and it should not take any arguments except self. For the function class
seen above, a suitable __str__ method could look like the following:
class Barometric:
...
def __call__(self, h):
return self.p0 * exp(-self.M*self.g*h/(self.R*self.T))
def __str__(self):
return f’p0 * exp(-M*g*h/(R*T)); T = {self.T}’
If we now call print for an instance of the class, the function expression and
the value of T for that instance will be printed, as follows:
>>> b = Barometric(245)
>>> b(2469)
70.86738432067067
>>> print(b)
p0 * exp(-M*g*h/(R*T)); T = 245
c = a - b # c = a.__sub__(b)
c = a*b # c = a.__mul__(b)
8.3 Special Methods 125
c = a/b # c = a.__div__(b)
c = a**e # c = a.__pow__(e)
It is natural, in most but not all cases, for these methods to return an object
of the same type as the operands. Similarly, there are special methods for
comparing objects,as follows:
a == b # a.__eq__(b)
a != b # a.__ne__(b)
a < b # a.__lt__(b)
a <= b # a.__le__(b)
a > b # a.__gt__(b)
a >= b # a.__ge__(b)
def __str__(self):
return f’p0 * exp(-M*g*h/(R*T)); T = {self.T}’
def __repr__(self):
126 8 Classes
The last two lines confirm that the repr method works as intended, since
running eval(repr(b) returns an object identical to b. Both __repr__ and
__str__ return strings with information about an object, the difference being
that __str__ gives information to be read by humans, whereas the output
of __repr__ is intended to be read by Python.
How to know the contents of a class. Sometimes listing the contents of a
class can be useful, particularly for debugging. Consider the following dummy
class, which does nothing useful except to define a doc string, a constructor,
and a single attribute:
class A:
"""A class for demo purposes."""
def __init__(self, value):
self.v = value
If we now write dir(A) we see that the class actually contains a great deal
more than what we put into it, since Python automatically defines certain
methods and attributes in all classes. Most of the items listed are default
versions of special methods, which do nothing useful except to give the error
message NotImplemented if they are called. However, if we create an instance
of A, and use dir on that instance, we obtain more useful information:
>>> a = A(2)
>>> dir(a)
[’__class__’, ’__delattr__’, ’__dict__’, ’__dir__’, ’__doc__’, ’__eq__’,
’__format__’, ’__ge__’, ’__getattribute__’, ’__gt__’, ’__hash__’,
’__init__’, ’__init_subclass__’, ’__le__’, ’__lt__’, ’__module__’,
’__ne__’, ’__new__’, ’__reduce__’, ’__reduce_ex__’, ’__repr__’,
’__setattr__’, ’__sizeof__’, ’__str__’, ’__subclasshook__’,
’__weakref__’, ’v’]
We see that the list contains the same (mostly useless) default versions of
special methods, but some of the items are more meaningful. If we continue
the interactive session to examine some of the items, we obtain
>>> a.__doc__
’A class for demo purposes.’
8.4 Example: Automatic Differentiation of Functions 127
>>> a.__dict__
{’v’: 2}
>>> a.v
2
>>> a.__module__
’__main__’
The __doc__ attribute is the doc string we defined, while __module__ is the
name of the module to which class belongs, which is simply __main__ in this
case, since we defined it in the main program. However, the most useful item
is probably __dict__, which is a dictionary containing the names and values
of all the attributes of the object a. Any instance holds its attributes in the
self.__dict__ dictionary, which is automatically created by Python. If we
add new attributes to the instance, they are inserted into the __dict__:
>>> a = A([1,2])
>>> print a.__dict__ # all attributes
{’v’: [1, 2]}
>>> a.myvar = 10 # add new attribute (!)
>>> a.__dict__
{’myvar’: 10, ’v’: [1, 2]}
When programming with classes we are not supposed to use the internal data
structures such as __dict__ explicitly, but printing it to check the values of
class attributes can be very useful if something goes wrong in our code.
f (x + h) − f (x)
f (x) ≈ .
h
For a small (yet moderate) h, say h = 10−5 , this estimate will be sufficiently
accurate for most applications. The key parts of the implementation are to let
the function f be an attribute of the Derivative class and then implement
the numerical differentiation formula in a __call__ special method:
class Derivative:
def __init__(self, f, h=1E-5):
self.f = f
self.h = float(h)
(1.093562409134085, True, 4)
This function follows the standard recipe for test functions: we construct a
problem with a known result, create an instance of the class, call the method,
and compare the result with the expected result. However, some of the details
inside the test function may be worth commenting on. First, we use a lambda
function to define f(x). As you may recall from Chapter 4, a lambda function
is simply a compact way of defining a function, with
f = lambda x: a*x + b
being equivalent to
def f(x):
return a*x + b
The use of the lambda function inside the test function appears straightfor-
ward at first:
f = lambda x: a*x + b
a = 3.5; b = 8
dfdx = Derivative(f, h=0.5)
dfdx(4.5)
130 8 Classes
The function f is defined to taking one argument x and also using two two
local variables a and b that are defined outside the function before it is called.
However, looking at this code in more detail can raise questions. Calling
dfdx(4.5) implies that Derivative.__call__ is called, but how can this
methods know the values of a and b when it calls our f(x) function? These
variables are defined inside the test function and are therefore local, whereas
the class is defined in the main program. The answer is that a function defined
inside another function "remembers," or has access to, all the local variables
of the function where it is defined. Therefore, all the variables defined inside
test_Derivative become part of the namespace of the function f, and f can
access a and b in test_Derivative even when it is called from the __call__
method in class Derivative. This construction is known as a closure in
computer science.
1 + 0 · x − 1 · x2 + 2 · x3
and the coefficients can be stored as a list [1, 0, -1, 2]. We now want
to create a class for such a polynomial and equip it with functionality to
evaluate and print polynomials and to add two polynomials. Intended use of
the class Polynomial could look like the following:
>>> p1 = Polynomial([1, -1])
>>> print(p1)
1 - x
>>> p2 = Polynomial([0, 1, 0, 0, -6, -1])
>>> p3 = p1 + p2
>>> print(p3.coeff)
[1, 0, 0, 0, -6, -1]
>>> print(p3)
1 - 6*x^4 - x^5
>>> print(p3(2.0))
-127.0
>>> p4 = p1*p2
>>> p2.differentiate()
>>> print(p2)
1 - 24*x^3 - 5*x^4
To make all these operations possible, the class needs the following special
methods:
8.6 Example: A Polynomial Class 131
The order of the sum of two polynomials is equal to the highest order of the
two, so the length of the returned polynomial must be equal to the length of
the longest of the two coeff lists. We utilize this knowledge in the code by
starting with a copy of the longest list and then looping through the shortest
and adding to each element.
132 8 Classes
M
⎛ N ⎞
M N
ci xi ⎝ dj xj ⎠ = ci dj xi+j ,
i=0 j=0 i=0 j=0
Here, the differentiate method will change the polynomial itself, since
this is the behavior indicated by the way the function was used above. We
have also added a separate function derivative that does not change the
polynomial but, instead, returns its derivative as a new Polynomial object.
Finally, let us implement the __str__ method for printing the polynomial
in human-readable form. This method should return a string representation
close to the way we write a polynomial in mathematics, but achieving this can
be surprisingly complicated. The following implementation does a reasonably
good job:
class Polynomial:
...
def __str__(self):
s = ’’
for i in range(0, len(self.coeff)):
if self.coeff[i] != 0:
s += f’ + {self.coeff[i]:g}*x^{i:g}’
# fix layout (many special cases):
s = s.replace(’+ -’, ’- ’)
s = s.replace(’ 1*’, ’ ’)
s = s.replace(’x^0’, ’1’)
s = s.replace(’x^1 ’, ’x ’)
if s[0:3] == ’ + ’: # remove initial +
s = s[3:]
if s[0:3] == ’ - ’: # fix spaces for initial -
s = ’-’ + s[3:]
return s
Open Access Dieses Kapitel wird unter der Creative Commons Namensnennung 4.0
International Lizenz (http://creativecommons.org/licenses/by/4.0/deed.de) veröffentli-
cht, welche die Nutzung, Vervielfältigung, Bearbeitung, Verbreitung und Wiedergabe
in jeglichem Medium und Format erlaubt, sofern Sie den/die ursprünglichen Autor(en)
und die Quelle ordnungsgemäß nennen, einen Link zur Creative Commons Lizenz
beifügen und angeben, ob Änderungen vorgenommen wurden.
Die in diesem Kapitel enthaltenen Bilder und sonstiges Drittmaterial unterliegen eben-
falls der genannten Creative Commons Lizenz, sofern sich aus der Abbildungsleg-
ende nichts anderes ergibt. Sofern das betreffende Material nicht unter der genannten
Creative Commons Lizenz steht und die betreffende Handlung nicht nach gesetzlichen
Vorschriften erlaubt ist, ist für die oben aufgeführten Weiterverwendungen des Materi-
als die Einwilligung des jeweiligen Rechteinhabers einzuholen.