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:
upperis a method of the string object
appendis a method of the object
[1, 2, 3].
writeis a method on the file handle object created by
closedis 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
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")
<__main__.Greeter object at 0x107daa438> hi john !
.in a method call determines the class and then which method is acutally called
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
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()
g1 is <__main__.Greeter object at 0x107daa748> g2 is <__main__.Greeter object at 0x107daa6a0> I am <__main__.Greeter object at 0x107daa748> I am <__main__.Greeter object at 0x107daa6a0>
So if we call
g1.who_am_i() we actually execute the
who_am_i method like
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))
i1.inc is 1 42 incremented is 43 is.inc is 2 0 incremented is 2
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
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
Afterwards we can check the attributes:
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
v1 = Vector2D(3, 4)
and we can see that the same initializer was executed:
v1 now has more methods than before:
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)
Vector2D(2.000, 3.000 with length 3.606)
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
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)
Vector2D(3.000, 4.000 with length 5.000)
If we now replace
__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)
Vector2D(3.000, 4.000 with length 5.000)
Here the Python interpreter translates the final
v1 + v2 to
Reference for all special methods: https://docs.python.org/2/reference/datamodel.html
__len__and pass a 2d vector to the builtin
lenfunction (the one we used for lists and strings).
__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 for vnext in li[1:]: value = self.combine(value, vnext) return value class ListAdder(ListReducer): def combine(self, v1, v2): return v1 + v2
reduce method does not work, because we call a missing method
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-18-781bf3738f1f> in <module>() ----> 1 ListReducer().reduce([1, 2]) <ipython-input-17-c2ad05b98eca> in reduce(self, li) 6 value = li 7 for vnext in li[1:]: ----> 8 value = self.combine(value, vnext) 9 return value 10 AttributeError: 'ListReducer' object has no attribute 'combine'
In contrast the
reduce and implements
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])
Threadclass: derive from this class and implement a method named
startwhich contains the code to run in a different thread.