from IPython.core.display import HTML
HTML(open("custom.html", "r").read())
We already used some object in Python: every data type in Python is an so called "object". In object oriented programming the objects have attributes and methods, we have mainly seen as methods. Examples:
upper
is a method of the string object "visa"
. ("visa".upper()
)append
is a method of the object [1, 2, 3]
. write
is a method on the file handle object created by open(...)
closed
is an attribute on such a file handle:fh = open("/dev/zero", "r")
fh.closed
A class is like a template describing how an object is created and how the attached methods are implemented. One can also imagine a class being the type of an object.
Here we define our very first class:
class Greeter:
def greet(self, who):
print("hi %s !" % who)
This defines a class
named Greeter
having one single method named greet
. This method takes one argument who
, ignore the self
for a moment, we will come back to this later !
Now that we know what method(s) the class Greeter
provides we can create an object of this class:
g = Greeter()
print(g)
g.greet("john")
.
in a method call determines the class and then which method is acutally calledself
argument in methods¶As said a class is a template for creating objects. So we can (and often do) create many instances of the same class. For example there are many strings being instances of the string class.
In the definition of greet
the self
argument is the object from the left side of .
from the method call. We can check this:
class Greeter:
def who_am_i(self):
print("I am %s" % self)
g1 = Greeter()
g2 = Greeter()
print("g1 is", g1)
print("g2 is", g2)
g1.who_am_i()
g2.who_am_i()
So if we call g1.who_am_i()
we actually execute the who_am_i
method like who_am_i(g1)
.
We can attach arbitrary values to an object:
g1.x = 42
print(g1.x)
But the usual procedure is to do access attributes within a method:
class Incrementer:
def set_increment(self, inc):
self.inc = inc # we set an attribute of the acutal object
def increment(self, what):
return what + self.inc # we fetch an attribute of the actual object
i1 = Incrementer()
i1.set_increment(1)
print("i1.inc is", i1.inc)
print("42 incremented is", i1.increment(42))
print()
i1.set_increment(2)
print("is.inc is", i1.inc)
print("0 incremented is", i1.increment(0))
Up to no we created objectes by calling the class name using ()
. To pass arguments to this call we need to implement a special method named __init__
:
import math
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
v = Point2D(3.0, 4.0)
The last call actually creates an temporary object (let's say obj
) and calls __init__(obj, 3.0, 4.0)
. Then this object is assigned to v
.
Afterwards we can check the attributes:
print(v.x, v.y)
We can extend an existing class by an mechanism called "inheritance". Inheriting from a given class simply attaches or overwrites existing methods. If a class A
derives from a class B
we say A is a base class of B
.
We declare the base class within ()
brackets after the class name:
import math
class Vector2D(Point2D):
def length(self):
return math.hypot(self.x, self.y)
Here the class Vector2D
uses the same __init__
as Point2D
:
v1 = Vector2D(3, 4)
and we can see that the same initializer was executed:
print(v1.x, v1.y)
But v1
now has more methods than before:
print(v1.length())
The method names starting and ending with double _
are often called "dunder methods". They have special and defined meanings. We already know __init__
for initializing an object.
Let us start by extending the previous example with an method for adding two vectors. We add another special method named __str__
which is called when we try to convert the object to a string. This happens automatically when we print such an object and helps us to prettify the output:
import math
class Vector2D(Point2D):
def length(self):
return math.hypot(self.x, self.y)
def __str__(self):
l = self.length()
return "Vector2D(%.3f, %.3f with length %.3f)" % (self.x, self.y, l)
v1 = Vector2D(2, 3)
print(v1)
A final and more advanced example demonstrate how we can implement the additon for objects of our class.
We start with a "traditional" method named add
:
import math
class Vector2D(Point2D):
def length(self):
return math.hypot(self.x, self.y)
def __str__(self):
l = self.length()
return "Vector2D(%.3f, %.3f with length %.3f)" % (self.x, self.y, l)
def add(self, other):
assert isinstance(other, Vector2D)
return Vector2D(self.x + other.x, self.y + other.y)
v1 = Vector2D(2, 3)
v2 = Vector2D(1, 1)
v3 = v1.add(v2)
print(v3)
If we now replace add
by __add__
we see a "magic" effect:
import math
class Vector2D(Point2D):
def length(self):
return math.hypot(self.x, self.y)
def __str__(self):
l = self.length()
return "Vector2D(%.3f, %.3f with length %.3f)" % (self.x, self.y, l)
def __add__(self, other):
assert isinstance(other, Vector2D)
return Vector2D(self.x + other.x, self.y + other.y)
v1 = Vector2D(2, 3)
v2 = Vector2D(1, 1)
print(v1 + v2)
Here the Python interpreter translates the final v1 + v2
to v1.__add__(v2)
!
Reference for all special methods: https://docs.python.org/2/reference/datamodel.html
Example:
length
to __len__
and pass a 2d vector to the builtin len
function (the one we used for lists and strings).Vector3D
including methods length
, __str__
, and __add__
.__sub__
for subtraction of two vectors.The following example shows how we can implement different "variations" of a given "base algorithm" by inheritance.
Lets say we want to implement an algorithm which sums all values in a list, and the variation multiplies all values.
We implement the "general" procedure in a base class and the specific variations in base classes:
class ListReducer:
def reduce(self, li):
assert len(li) > 0, "no empty list accepted"
value = li[0]
for vnext in li[1:]:
value = self.combine(value, vnext)
return value
class ListAdder(ListReducer):
def combine(self, v1, v2):
return v1 + v2
Calling the ListReducer
s reduce
method does not work, because we call a missing method combine
.
ListReducer().reduce([1, 2])
In contrast the ListAdder
inherits reduce
and implements combine
:
ListAdder().reduce([1, 2, 3, 4])
Implementing a new variation of our "algorithm" is now very easy:
class ListMultiplier(ListReducer):
def combine(self, v1, v2):
return v1 * v2
ListMultiplier().reduce([1, 2, 3, 4])
Thread
class: derive from this class and implement a method named start
which contains the code to run in a different thread.