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__(selfname):
        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__(selfname) 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(selfname):
        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__(selfname):
        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 ClassWithStaticFieldstat1 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 :)

No comments:

Post a Comment

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