Python Training - Lession 2 - classes in Object Oriented Programming

In Python, pretty much every variable is an object, and therefore an instance of some class. But what is a class? A first, basic understanding of a class should be: A data structure with named variables and procedures.

At this stage of programming, the simpler we keep things, the better. Let's see how we can define a class.

Simple class definition


In [21]:
class Example:
    a = 1

In [22]:
print type(Example)


<type 'classobj'>

Creating objects - instances of a class


In [23]:
object_from_class = Example()
print object_from_class


<__main__.Example instance at 0x0000000005A88C48>

Accessing objects 'fields'


In [24]:
object_from_class.a


Out[24]:
1

What are class variables and instance variables?

Class variables are variables attached to the definition of a class. Simply, they are just regular variable definitions inside a class Instance variables are variables created for each instance of a class. We denote them by adding "self." in front of them. Examples:


In [1]:
class ClassI:
    # Define instance variables in a special method, called a "constructor", that defines what happens when an object is created.
    def __init__(self):
        self.a = 1
        self.b = 2

class ClassC:
    # Define class variables normally. They are here, whether you create an object or not.
    a = 3
    b = 44

In [26]:
instance_of_ClassC = ClassC()
print instance_of_ClassC.a, instance_of_ClassC.b 

print ClassC.a


3 44
3

In [27]:
instance_of_ClassI = ClassI()
print instance_of_ClassI.a, instance_of_ClassI.b

# This will cause an error, because to access instance variables, you need an instance of class!
print ClassI.a


1 2
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-27-692a80d5a904> in <module>()
      3 
      4 # This will cause an error, because to access instance variables, you need an instance of class!
----> 5 print ClassI.a

AttributeError: class ClassI has no attribute 'a'

So what's up with that Object Oriented Programming?

Loose definition

It is a kind of methodology and a set of rules for programming. Loosely speaking, it means that we should split our data and functionalities into classes with methods (functions), to follow a specific set of principles.

Some definitions.

  • Class - a distinct set of variables and procedures, centered around one thing, to store data, and do operations on that data, to communicate, and other stuff.
  • Field = attribute = a variable defined in a class
  • Method = procedure - a set of instructions defined in a class
  • Static method - a function defined in a class, but that does not actually require to create an object of that class!
  • Self - when a method or field uses 'self', it means it targets the object with which they are associated - "this", "the object I am inside right now", "the object on which I was invoked"
  • Type - to which class does an object correspond, of which class it is an instance of

Inheritance, composition, relationships.

You will often use words like "parent" or "child", when talking about classes. THe main reason they are used in this context, is to indicate the hierarchy of inheritance. But what is inheritance?

Imagine now, you create a class, which fields are actually objects of other classes. This is composition. It means your objects HAVE other objects. We call this "has-a" relationship.

Now imagine, you want to write classes representing various jobs in some company. So you write classes "Driver", "Recruiter", "Boss". Now you start to think what they would do, and quickly realise there are many things they share, for example, they can get a salary, can leave work, have a break, etc.

The most simple thing would be to write procedures for those actions, separately in each class. But thanks to inheritance, you would need to write it only once, in a BASE CLASS named "Employee". THen, all the others would INHERIT from this base class, getting all those methods for free. You could say, that "Driver' is an "Employee", and so is "Recruiter". We call this "is-a" relationship.

You can mix those relationships together, to reuse code whenever possible. A rule of thumb is to use inheritance only when it really is the best thing to do, and not overdo it. Excessive inheritance actually looses all advantages of inheritance, and causes lot's of troubles in big projects (it is hard to modify the hierarchy). Another rule of thumb is, that usually inheritance is really good for very similar things, for storing data, and sharing data and procedures when we have a big amount of classes.

Polymorphism.

In simple words, this means that you do not care from which class in hierarchy some method comes from. Even simpler, that you create your code, you do not worry if some object is an instance of the base class, it's children, or grandchildren, you should be able to use the same methods on each of them.

Principles of OOP - SOLID

The five basic principles describe how to best write classes. Take your time to learn them, and do not rush into advanced programming before understanding these principles. OOP is a paradigm. There are others, like "functional programming", with their own design patterns and principles. This tutorial's scope is "beginner friendly", so we will skip this for now, but come back to them as soon as you feel you can understand them. https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

What does this mean in practice?

To write programs, you will need to write code that is readable, powerful and easily modified - using modularity, reusability, algorithms. Python is a language that allows to use all kinds of programming, not only OOP, to suit best your goals. In practice, we will creates all kinds of Python files:

  • libraries of functions
  • file with a class definition - only to model data
  • file with a class definition - as a "library" with data AND tools that operate on them
  • file with the main program - our entry point into running what we wanted to do
  • file with test cases - to check if our program works correctly
  • ...

From my perspective, design patterns and efficient, clear code is more important than sticking to one paradigm for no reason. For example, you do not need a class just for one method. You also do not need a class if all your methods are static, which means they do not need any "state", like an instance of an object that has a certain state during it's lifetime. Look at this code for example:


In [ ]:
# Let's define some functions.
def multiply(a,b):
    return a*b

def count_letter_in_word(word, letter):
    track_letters = {}
    for character in word:
        if character in track_letters:
            track_letters[character] += 1
        else:
            track_letters[character] = 1

    if letter in track_letters:
        return track_letters[letter]
    else:
        return 0

In [ ]:
# Let's define a class to store a model of data.
# This time, we put more parameters for the constructor: name and age. This allows us to fill the object during the creation.
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

In [ ]:
# Now let's use our code.
adam = Person("Adam", 18)
print count_letter_in_word(adam.name, "a")
print multiply(adam.age, 10)

If I put those functions in class "Person" definition, only my class would have those methods. If I put those functions in a separate class, like "Tools", it is also bad, because they do not have "self" inside, they do not need any additional data to work. We can say they are "stateless" or "static", and they can just lie around loosely in a separate file. Now we should have some understanding of how and when to put procedures in which files.

Remember:

  • procedure in class is a method
  • a stateless procedure in a class should be a class method, if it needs data from that class
  • a stateless procedure that does not need anything (only arguments) is a function and should be kept in a separate file without a class

Inheritance and composition summary: Use composition when you can = compose objects from other objects For obvious code duplication and shared data/methods/strategy/abstraction - use inheritance