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 ListReducers 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.