In the last part we've shown how to create and use a class in Python. Today we're going to talk about inheritance: wchich means creating sub-classes, which add different behaviour to the class they inherit from (or extended class, in Java nomenclature).
Suppose we have a class like this:
classRunner:
def__init__(self):
self.shoes = True
defhasShoes(self):
returnself.shoes
We can create an object of this class and call the method hasShoes on it:
# !/usr/bin/python3
classRunner:
def__init__(self):
self.shoes = True
defhasShoes(self):
returnself.shoes
defmain():
myRunner = Runner()
print(myRunner.hasShoes())
if __name__ == "__main__":
main()
The code results in:
True
Now let's extend this class by creating a subclass (LongDistanceRunner) and adding a method there - and let's create an instance of subclass and call this method:
# !/usr/bin/python3
classRunner:
def__init__(self):
self.shoes = True
defhasShoes(self):
returnself.shoes
classLongDistanceRunner(Runner):
defrun(self):
ifself.hasShoes():
print("Running slowly")
else:
print("Not running (no shoes)")
defmain():
myRunner = Runner()
print(myRunner.hasShoes())
ldRunner = LongDistanceRunner()
ldRunner.run()
print(ldRunner.hasShoes())
if __name__ == "__main__":
main()
The code results in:
True
Running slowly
True
As we can see, we can call both run() - which is method from the subclass (LongDistanceRunner) and hasShoes() which is method defined in superclass ( Runner ) on the object of class LongDistanceRunner. What' more - in the class LongDistanceRunner (in the method run() ) we can use our superclass features (in this case: the method hasShoes() ).
Now let's add another class inheriting from Runner:
# !/usr/bin/python3
classRunner:
def__init__(self):
self.shoes = True
defhasShoes(self):
returnself.shoes
classLongDistanceRunner(Runner):
defrun(self):
ifself.hasShoes():
print("Running slowly")
else:
print("Not running (no shoes)")
classShortDistanceRunner(Runner):
defrun(self):
ifself.hasShoes():
print("Running fast")
else:
print("Not running (no shoes)")
defmain():
myRunner = Runner()
print(myRunner.hasShoes())
ldRunner = LongDistanceRunner()
ldRunner.run()
print(ldRunner.hasShoes())
sdRunner = ShortDistanceRunner()
if __name__ == "__main__":
main()
We can add both our "runners" to a collection and call the method hasShoes() on both of them:
# !/usr/bin/python3
classRunner:
def__init__(self):
self.shoes = True
defhasShoes(self):
returnself.shoes
classLongDistanceRunner(Runner):
defrun(self):
ifself.hasShoes():
print("Running slowly")
else:
print("Not running (no shoes)")
defdrinkWater(self):
print("Drinking water (while running)")
classShortDistanceRunner(Runner):
defrun(self):
ifself.hasShoes():
print("Running fast")
else:
print("Not running (no shoes)")
defdrinkWater(self):
print("Drinking water (not while running)")
defmain():
myRunner = Runner()
print(myRunner.hasShoes())
ldRunner = LongDistanceRunner()
ldRunner.run()
print(ldRunner.hasShoes())
sdRunner = ShortDistanceRunner()
runners = [ldRunner, sdRunner]
print("Do runners have shoes?")
forrunnerinrunners:
print(runner.hasShoes())
if __name__ == "__main__":
main()
Which results in:
True
Running slowly
True
Do runners have shoes?
True
True
What's interesting here is that the hasShoes() method works "polymorphic" here - although it's not defined in the superclass. This behaviour is what differs polymorphism in Python from Java, C# or C++ - in all these languages polymorphism is strictly connected to inheritance.
Today we'll take a look at some examples of object-oriented code in Python.
As I'm learning this topic almost in the same time I'm writing this, I'll do lot of mistakes - especially because some topics seem different than C++/Java/C# style I'm used to.
Ok, let's begin with creating a class with one method, creating an object of this class and calling this method.
# !/usr/bin/python3
classClassWithMethod:
defsay_something(self):
print("Elementary!")
defmain():
objectWithMethod = ClassWithMethod()
objectWithMethod.say_something()
if __name__ == "__main__":
main()
We define a class ClassWithMethod, with one method: say_something(self) simply printing some text. self param is, as we will see, the reference to the calling object. For now, we just instantiate the class:
objectWithMethod = ClassWithMethod()
...and call the method:
objectWithMethod.say_something()
The result of running this code is:
Elementary!
Now let's define other class with one field (or attribute).
classClassWithField:
def__init__(self, name):
self.name = name
To instantiate and use it we can add the following lines to main function:
objectWithField = ClassWithField("Lestrade")
print(objectWithField.name)
As we can see, __init__(self, name) works as a constructor with one parameter. The code now prints:
Elementary!
Lestrade
What about a method? Well, take a look at another class:
classClassWithFieldAndMethod:
deftellName(self):
print(self.name)
If we instantiate it, and set field name to something, we can call our method (in main!):
We create another object (objectWithFieldAndMethod2), add the field named age to it (simply by initializing it) and then we can access it. The code execution result becomes:
Elementary!
Lestrade
John Watson
Sherlock Holmes
33
Please note we've added the age field to our object, not class. It means that if we try to get to this field on our previous object, we get error.
BUT in fact, we can access this field - as "private" in Python does not mean the attribute is unaccesible - it rather means it should not be accessed. So we can access this attrbute using the following syntax: objectName._ClassName__field_name (please note the underscores, one before class name and two before field name):
The last thing: static (class) fields. They're defined simply in class body, not in the methods:
classClassWithStaticField:
field = 40
But beware: they can be accessed with both class name and object name. In the first case, they behave like static. In hte latter - they're simply fields, initialized with given value.
An example should give us more light on this:
stat1 = ClassWithStaticField()
stat2 = ClassWithStaticField()
stat2.field = 41
print(stat1.field) # object's field default value is 40
print(stat2.field) # this object's field value is 41
print(ClassWithStaticField.field) # static field value is still 40!
... which code gives us:
40
41
40
40
We create two objects of class ClassWithStaticField, stat1 and stat2. Then we assign value 41 to the field field of the first object. It changes this instance's value, but the field field of object stat1 stays the same! What's more - the ClassWithStaticField.field is also another reference. We can change it, of course, by calling:
ClassWithStaticField.field = 42
So the code:
stat1 = ClassWithStaticField()
stat2 = ClassWithStaticField()
stat2.field = 41
print(stat1.field) # object's field default value is 40
print(stat2.field) # this object's field value is 41
print(stat1.field) # object's field default value is 40
print(ClassWithStaticField.field) # static field value is still 40!
ClassWithStaticField.field = 42
print(ClassWithStaticField.field) # not anymore!
results in:
40
41
40
40
42
Confusing? Well, for me it certainly is ;D The full program listing illustrating the concepts above looks as follows:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This time again we're going to be talking about something available in Kotlin, but not in Java: operator overloading.
Operator overloading means that we can apply operators, like + (someobject+anotherone) or [ ] (someobject[5]) to our objects. Expression like this (someobject + anotherone) can be thought of as methods: it's just another way of expressing someobject.plus(anotherone). someobject[5] would mean "call the method [] with parameter 5 on someobject.
Altough operator overloading is an old concept (it's common in C++, where - for example - the famous cout<<"Something"; actually uses overloaded left bit shift operator for object cout of class ostream; it's also available in C# and Python), it's not implemented in Java (which was a deliberate choice of its creators).
Still, it can be very useful and Kotlin allows it.
Let's get to our code. I think the most basic example of operator overloading is use of class representing complex numbers (the code below is the full program, with both class definition and it's use):
package com.mypackage
import kotlin.Exception import kotlin.math.sqrt
class Complex(var re: Float, var im: Float){
fun modulus(): Float{ // just a method return sqrt(re*re+im*im) }
// "this" can be omitted operator fun plus(another: Complex): Complex{ // a+b return Complex(this.re+another.re,this.im+another.im) }
operator fun minus(another: Complex): Complex{ // a - b return Complex(re-another.re, im-another.im) }
operator fun times(another: Complex): Complex{ var resultRe = re*another.re-im*another.im var resultIm = re*another.im+im*another.re return Complex(resultRe, resultIm) }
... which defines the class Complex representing a complex number, and overloads the infix "+", "-", "*", comparison (<, >, >=, <=) and [ ] operators. There is also modulus function, which is just a method... And which we use to define our overloaded comparison operators behaviour (operator fun compareTo).
The execution of the code above results in the following output:
multiplied:(80.0,15.0)
(2.0,0.0)
(2.0,0.0)
2.0
true
true
We can see that my implementation of complex multiplication is correct ;)
The +, -, * operators, implemented as plus,minus, and times are quite self-explanatory: they take one parameter, and use this.re and this.im (or just re and im) of current (left to operand) values to compute the results, which they return.
In order to override them, we use operator keyword before a method definition. Please note they can be called both as operators ( val sum = c+c2 ) or just as methods ( val sum2 = c.plus(c2)).
In comparison operator overload (operator fun compareTo(another: Complex)) I've used previously defined modulus method, because there is no unequivocal way of comparing complex numbers - so I compare their moduli. This operator should return -1 if the first operand is "smaller" than second one (in our case it's modulus is smaller), 1 if it's "bigger" and and 0 it they're the same.
I've also overloaded get operator, which affects suqare brackets, like in the call println(sum[0]). What it does, is simply return real part of our complex as 0-th element and the imaginary part as 1-st one. If called with something else than 0 or 1, it would throw IllegalArgumentException(). Typically this operator would be used for some class representing collection (like list - please note in Java you'd have to use somelist.get(i) instead of just somelist[i] to get i-th element of the somelist List or like text string, where it would return n-th character).
There are other operators to overload, like not (!) - but I simply can think of no use of it regarding complex numbers :). When you type operator fun (with the spacebar after the word fun) and hit Ctrl+Space in IntelliJ Idea or Android Studio, you'll get the full list of the operators possible to overload with their symbols on the right and corresponding keywords (like plus or minus in the example above) on the left:
Please note compareTo does not affect == comparison effect: that's what overriden function override fun equals(other: Any?) does.
A word on Java - of course, lack of operator overloading in Java doesn't mean we could not implement a class Complex in this language. We can, of course. But the only way of representing "sum of complex1 and complex2" would be some function or method, like complex1.add(complex2) or maybe sum(complex1, complex2) - not the intuitive and obvious Kotlin's complex1+complex2.
Well, that would be basics. More details, including the full list of operators available for overloading in Kotlin are available in the Kotlin's documentation.