Showing posts with label Python. Show all posts
Showing posts with label Python. Show all posts

Saturday, 7 August 2021

Python crash course part 10: inheritance and polymorphism

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:

class Runner:
    def __init__(self):
        self.shoes = True
    def hasShoes(self):
        return self.shoes

We can create an object of this class and call the method hasShoes on it:

# !/usr/bin/python3

class Runner:
    def __init__(self):
        self.shoes = True
    def hasShoes(self):
        return self.shoes

def main():

    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

class Runner:
    def __init__(self):
        self.shoes = True
    def hasShoes(self):
        return self.shoes

class LongDistanceRunner(Runner):
    def run(self):
            if self.hasShoes():
                print("Running slowly")
            else:
                print("Not running (no shoes)")

def main():

    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

class Runner:
    def __init__(self):
        self.shoes = True
    def hasShoes(self):
        return self.shoes

class LongDistanceRunner(Runner):
    def run(self):
            if self.hasShoes():
                print("Running slowly")
            else:
                print("Not running (no shoes)")

class ShortDistanceRunner(Runner):
    def run(self):
            if self.hasShoes():
                print("Running fast")
            else:
                print("Not running (no shoes)")
def main():

    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

class Runner:
    def __init__(self):
        self.shoes = True
    def hasShoes(self):
        return self.shoes

class LongDistanceRunner(Runner):
    def run(self):
            if self.hasShoes():
                print("Running slowly")
            else:
                print("Not running (no shoes)")
    def drinkWater(self):
        print("Drinking water (while running)")

class ShortDistanceRunner(Runner):
    def run(self):
            if self.hasShoes():
                print("Running fast")
            else:
                print("Not running (no shoes)")
    def drinkWater(self):
        print("Drinking water (not while running)")
def main():

    myRunner = Runner()
    print(myRunner.hasShoes())

    ldRunner = LongDistanceRunner()
    ldRunner.run()
    print(ldRunner.hasShoes())

    sdRunner = ShortDistanceRunner()

    runners = [ldRunner, sdRunner]

    print("Do runners have shoes?")
    for runner in runners:
        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.

Tuesday, 3 August 2021

Python crash course part 9: basic object-oriented code examples


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

class ClassWithMethod:
    def say_something(self):
        print("Elementary!")

def main():
    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).

class ClassWithField:
    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:

class ClassWithFieldAndMethod:
    def tellName(self):
        print(self.name)

If we instantiate it, and set field name to something, we can call our method (in main!):

    objectWithFieldAndMethod = ClassWithFieldAndMethod()
    objectWithFieldAndMethod.name = "John Watson"
    objectWithFieldAndMethod.tellName()

Giving result:

John Watson

What's interesting, we can add fields to a class object on the fly, without declaring them. Add this to main:

    objectWithFieldAndMethod2 = ClassWithFieldAndMethod()
    objectWithFieldAndMethod2.name = "Sherlock Holmes"
    objectWithFieldAndMethod2.tellName()

    # you can add field on the fly...
    # ... to the object!
    objectWithFieldAndMethod2.age = 33 
    
    # you can do this only if field has been added:
    print(objectWithFieldAndMethod2.age)

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.

def main():
    objectWithMethod = ClassWithMethod()
    objectWithMethod.say_something()

    objectWithField = ClassWithField("Lestrade")
    print(objectWithField.name)

    objectWithFieldAndMethod = ClassWithFieldAndMethod()
    objectWithFieldAndMethod.name = "John Watson"
    objectWithFieldAndMethod.tellName()

    objectWithFieldAndMethod2 = ClassWithFieldAndMethod()
    objectWithFieldAndMethod2.name = "Sherlock Holmes"
    objectWithFieldAndMethod2.tellName()

    # you can add field on the fly...
    # ... to the object!
    objectWithFieldAndMethod2.age = 33 
    
    # you can do this only if field has been added:
    print(objectWithFieldAndMethod2.age)

    # this results in error!
    print(objectWithFieldAndMethod.age)

...gives:

Elementary!
Lestrade
John Watson
Sherlock Holmes
33
Traceback (most recent call last):
  File "d:\Projekty\Python\controlflow.py", line 71, in <module>
    main()
  File "d:\Projekty\Python\controlflow.py", line 50, in main
    print(objectWithFieldAndMethod.age)
AttributeError: 'ClassWithFieldAndMethod' object has no attribute 'age'

What about private fields? We define it by preceding initialized field name with double underscore:

class ClassWithPrivateFieldAndMethod:
    def setName(self, name):
        self.__name = name

We can create an object of this class and call the method to set field __name:

    objectWithPrivateField = ClassWithPrivateFieldAndMethod()
    objectWithPrivateField.setName("Mary Watson")

..but we can't do this:

 print(objectWithPrivateField.__name)

... as the program will behave as such attribute doesn't exist:

  File "d:\Projekty\Python\controlflow.py", line 71, in <module>
    main()
  File "d:\Projekty\Python\controlflow.py", line 55, in main
    print(objectWithPrivateField.__name)
AttributeError: 'ClassWithPrivateFieldAndMethod' object 
has no attribute '__name'

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):

print(objectWithPrivateField._ClassWithPrivateFieldAndMethod__name)

This gives us:

Mary Watson

Now let's take a look at a param passed in the constructor.

The following class:

class ClassWithParamaterInConstructor:
    def __init__(self, name):
        self.name = name

...can be created like this:

   objectConstructedWithParam = ClassWithParamaterInConstructor("Moriarty")

We can then normally access the name field, so:

   print(objectConstructedWithParam.name)

...gives us this ominous result:

Moriarty

The last thing: static (class) fields. They're defined simply in class body, not in the methods:

class ClassWithStaticField:
    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:


... and gives the following result:

Elementary!
Lestrade
John Watson
Sherlock Holmes
33
Mary Watson
Moriarty
40
41
40
40
42

I hope it happens to be useful for some of you :)

Saturday, 24 July 2021

Python crash course part 8: file input and output

In today's post I'll provide some short samples of working with files in Python.

Let's begin with text files and let's create a file first.

# !/usr/bin/python3

def main():
    # create a file
    my_file = open('newfile.txt', 'w')
    # write some data to it:
    my_file.write("Anakin Skywalker")
    my_file.close()

    # contents of the file: 
    # Anakin Skywalker

if __name__ == "__main__":
    main()

The result of running this code is creating a file newfile.txt with content: Anakin Skywalker

'w' means that the we will write to the file, and if that file exits, it will be overwritten. Remember to call .close() on the file object after opening and doing some operation on it.

Let's extend our program, by calling the similar code again:

# !/usr/bin/python3

def main():
    # create a file
    my_file = open('newfile.txt', 'w')
    # write some data to it:
    my_file.write("Anakin Skywalker")
    my_file.close()

    # contents of the file: 
    # Anakin Skywalker

    # opening it again
    my_file = open('newfile.txt', 'w')
    my_file.write("Darth Vader")
    my_file.close()

    # contents of the file: 
    # Dath Vader

if __name__ == "__main__":
    main()

As the 'w' mode makes the write call overwrite the file contents, it will be just Darth Vader  after the call. What if we want to actually add something to the file, not deleting it contents?

That's what 'a' (append) mode is for:

# !/usr/bin/python3

def main():
    # create a file
    my_file = open('newfile.txt', 'w')
    # write some data to it:
    my_file.write("Anakin Skywalker")
    my_file.close()

    # contents of the file: 
    # Anakin Skywalker

    # opening it again
    my_file = open('newfile.txt', 'w')
    my_file.write("Darth Vader")
    my_file.close()

    # contents of the file: 
    # Dath Vader
    my_file = open('newFile.txt','a')
    my_file.write("\nLuke Skywalker")
    my_file.close()
    # contents:
    # Darth Vader
    # Luke Skywalker


if __name__ == "__main__":
    main()

Because 'a' mode is used instead of 'w', \nLuke Skywalker is added (appended) to the existing file. The \n before the text itself in the third write call is a new line sign between the first and second line. The contents of the file become:

Darth Vader
Luke Skywalker

We know how to write to file, but how to read from it? This is what open() with 'r' (read) parameter is for. We use it like this:

# !/usr/bin/python3

def main():
    # create a file
    my_file = open('newfile.txt', 'w')
    # write some data to it:
    my_file.write("Anakin Skywalker")
    my_file.close()

    # contents of the file: 
    # Anakin Skywalker

    # opening it again
    my_file = open('newfile.txt', 'w')
    my_file.write("Darth Vader")
    my_file.close()

    # contents of the file: 
    # Dath Vader
    my_file = open('newFile.txt','a')
    my_file.write("\nLuke Skywalker")
    my_file.close()
    # contents:
    # Darth Vader
    # Luke Skywalker
    
    to_read = open('newFile.txt','r')
    file_contents = to_read.readlines()
    for val in file_contents:
        print(val)

    #result:
    #Darth Vader
    #
    #Luke Skywalker   

if __name__ == "__main__":
    main()

The code above creates the file, adds 2 lines (Darth Vader and Luke Skywalker) to it, and the reads lines from the file, puts them in file_contents variable (list) and prints them to console.

The execution result is therefore:

Darth Vader

Luke Skywalker

As you can see, there is something strange with this result: there is an empty line between Darth and Luke, which is not in the file. That's because print(val) adds a new line character after the text it prints, and there is already one new line between Darth Vader and Luke Skywalker in the file.

In order to fix it, we can use an overloaded version of the print() function: print(val,end=''), which specifies that in the end of the text it should use an empty character instead of new line:

# !/usr/bin/python3

def main():
    # create a file
    my_file = open('newfile.txt', 'w')
    # write some data to it:
    my_file.write("Anakin Skywalker")
    my_file.close()

    # contents of the file: 
    # Anakin Skywalker

    # opening it again
    my_file = open('newfile.txt', 'w')
    my_file.write("Darth Vader")
    my_file.close()

    # contents of the file: 
    # Dath Vader
    my_file = open('newFile.txt','a')
    my_file.write("\nLuke Skywalker")
    my_file.close()
    # contents:
    # Darth Vader
    # Luke Skywalker

    to_read = open('newFile.txt','r')
    file_contents = to_read.readlines()
    for val in file_contents:
        print(val,end='')

if __name__ == "__main__":
    main()

The execution of the code above finally results in:

Darth Vader
Luke Skywalker

... which are contents of the file created above :).

We've covered text files - what about binary files? Here we're going to focus on reading from such file (let's be honest - writing to binary file is not very commond situation).

And reading from binary files is easy: we have to set 'rb' (read binary) instead of just 'r' (read) as our file open mode, and then we can read, for instance, a few bytes from the file. Those bytes become bytes array. So if we have, for instance, wav file (which should begin with letters RIFF in ASCII form), the reading of a first fragment of such file would look like this:

# !/usr/bin/python3

def main():

    binary_file = open('sound.wav','rb')
    bytes = binary_file.read(4) #bytes
    print(bytes[0])
    print(bytes[1])
    print(bytes[2])
    print(bytes[3])
    
    print(str(bytes))

if __name__ == "__main__":
    main()

Which results in:

82
73
70
70
b'RIFF'

(which means it looks like some wav file indeed :) )

Thursday, 22 July 2021

Python crash course part 7: more on defining functions

 In the part 2 we've shown most basic ways of definining functions in Python.

Let's elaborate on that a bit more, using some examples.

First, a simple function just doing something, for instance printing text to screen (something tradinionally called a procedure rather than a function), is defined like this:

def just_do_it():
    print("Just do it")

... and called like this:

    just_do_it()

So execution of the program below:

# !/usr/bin/python3

def just_do_it():
    print("Just do it")

def main():

    just_do_it()

...results in the following output:

Just do it

What if we want to return value from function, so we can use it in the moment it's called? Well, let's simply use return keyword:

def please_return_something():
    return 3.14

The type doesn't have to be defined.

As an example we'll assign the value returned by the function above to pi variable, and then print it to the screen.

def main():

    pi = please_return_something()
    print(pi)

Result:

3.14

What if we want our function to take some parameters?

This is when the things became interesting, as Python allows us to defined both named and unnamed (positional) paramaters. By default, both. So we can define our function as:

def divide(nominator, denominator):
    return nominator/denominator

...and call it in two ways:

    print(divide(6,2))

Or:

    print(divide(nominator=6,denominator=2))

...both resulting in:

3.0

Please note that if we call our function with named parameters, their order doesn't matter. So call:

    print(divide(denominator=2,nominator=6))

...also results in:

3.0

The last thing for today will be default parameter value, which is defined using the = sign.

An example looks like this...

def power(a, b = 2):
    return a**b

( ** is power operator, a**b means a to the power of b).

The above function has two parameters, but the second one (exponent) has default value. This means that if we skip the second param, our function becomes simply square function.

So the "full-param" call looks like this:

    print(power(2,3))

...and results in:

8

And the call using default parameter value can look like this:

print(power(3))

..resulting in:

9

There are more details about defining and calling functions in Python, which we can elaborate further on in future, but I think for now we've covered the most important features.


Tuesday, 20 July 2021

Python crash course part 6: basic exceptions.

Another basic language feature I'll cover in this tutorial is exception handling (which I wrote about in Kotlin a few days ago.

Traditionally, I'll show some examples. Let's begin with simple code, asking user for two numbers and dividing them. Of course, such program may encounter probably the most basic exception: division by 0. But also another one: if we put something not being a number, it cannot be converted to float and some other exception would be expected.

First, the code without any exeption handling would look like this:

# !/usr/bin/python3

def divide(a, b):
    return a/b

def main():
    a = float(input('a = '))
    b = float(input('b = '))
    c = divide(a,b)
    print(c)

We create a function divide(a, b) , returning division result. Then we get two numbers from user, a and b using input('... ') function and converting them to float using float() function (as input('a = ') returns simply a string).

If given two numbers which we can divide by eachother, the result is not surprising:

a = 10
b = 3
3.3333333333333335

But if we put 0 as b...

a = 10
b = 0
Traceback (most recent call last):
  File "d:\Projekty\Python\controlflow.py", line 13, in <module>
    main()
  File "d:\Projekty\Python\controlflow.py", line 9, in main     
    c = divide(a,b)
  File "d:\Projekty\Python\controlflow.py", line 4, in divide   
    return a/b
ZeroDivisionError: float division by zero

... we get a cute stacktrace. Well, division by 0 is not possible, and therefore results in an exception. Quite obvious :)

Before we try to deal with this proble, let's put another strange data as input to program: try to divide 10 by horse. Well, this also shouldn't be possible, right?

a = 10
b = horse
Traceback (most recent call last):
  File "d:\Projekty\Python\controlflow.py", line 17, in <module>
    main()
  File "d:\Projekty\Python\controlflow.py", line 9, in main
    b = float(input('b = '))
ValueError: could not convert string to float: 'horse'

... as expected. Of course, here another exception is caught (and in other place: not when trying to divide, but when trying to get float from quite horse-like input...)

What can we do with exception? We can catch it. First, a general exception:

# !/usr/bin/python3


def divide(a, b):
    return a/b

def main():
    
    try:
        a = float(input('a = '))
        b = float(input('b = '))
        c = divide(a,b)
        print(c)
    except:
        print("Some exception caught!")

if __name__ == "__main__":
    main()

...which handles both division by 0:

a = 10
b = 0
Some exception caught!

... and division by horse :) 

a = 10
b = horse
Some exception caught!

The exception handling in Python is done using try except instruction. In the case above, no specific exception has been specified to be caught, so we get the same result in case of any exception (also known as Pokemon exception handling... Catch them all ;) ).

So what if we wanted to catch some specific exception? 

# !/usr/bin/python3


def divide(a, b):
    return a/b

def main():
    
    try:
        a = float(input('a = '))
        b = float(input('b = '))
        c = divide(a,b)
        print(c)
    except ZeroDivisionError:
        print("Division by 0? Blasphemy!")

if __name__ == "__main__":
    main()

Result:

a = 10
b = 0
Division by 0? Blasphemy!

What if we want to catch more specific exceptions?

We can specify more except blocks:

# !/usr/bin/python3


def divide(a, b):
    return a/b

def main():
    
    try:
        a = float(input('a = '))
        b = float(input('b = '))
        c = divide(a,b)
        print(c)
    except ZeroDivisionError:
        print("Division by 0? Blasphemy!")
    except ValueError:
        print("Some strange value...")

if __name__ == "__main__":
    main()

Division by 0 is then detected separately from division by horse:

a = 10
b = 0
Division by 0? Blasphemy!

and

a = 10
b = horse
Some strange value...

That's all for now - the custom exceptions and throwing them "by hand" will be one of our next topics in the nearest future :)



Python crash course part 10: inheritance and polymorphism

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