Advanced OOP topics in Python

Static Attributes

Static Attributes

Overview

A Class is an object in Python
Class objects can have attributes: data and methods
A Class attribute data in Python behaves like static data
A Class attribute method in Python behaves like static mehotd

Class data attributes (Static Attributes)

Class data attributes are shared among all Class instances

      class Person():
        count = 0

        def __init__(self, name, age):
          self.name = name
          self.age = age

          # should be __count, but good to show that Person.count != self.count
          self.count = Person.count

          # increment objects counter:
          Person.count += 1


        def __str__(self):
          return "{}. {}: {}".format(self.count, self.name, self.age)


      maria = Person("Maria", 20)
      pesho = Person("Pesho", 30)

      print(maria)
      print(pesho)
    

Class Methods

Class Methods

Overview

A class method receives the class as implicit first argument (just like an instance method receives the instance)
Class methods have access to the state of the class
Class methods are build by the @classmethod decorator
Class methods are used to create factory methods (can emulate method overloading, which is not supported in Python)

Syntax


      class Spam():
          @classmethod
          def foo(cls, arg1, arg2, ...):
    

cls will be bound to class, not to instance.

Example


      class Person():
        count = 0

        @classmethod
        def increment_counter(cls):
          cls.count += 1
          print("count:",cls.count )

        def __init__(self, name, age):
          self.name = name
          self.age = age

          self.increment_counter()
          # attach count to an object
          self.count = Person.count


        def __str__(self):
          return "{}. {}: {}".format(self.count, self.name, self.age)


      maria = Person("Maria", 20)
      pesho = Person("Pesho", 30)

      print(maria)
      print(pesho)

      # obviously, we would not want that. So, be careful with class methods!
      maria.increment_counter()
      pesho.increment_counter()
    

Static Methods

Static Methods

Overview

A static method does not receive an implicit first argument.
It is like normal function, but placed in a class.
Static methods are generally used to create utility functions.

Syntax


      class Spam():
          @staticmethod
          def foo(arg1, arg2, ...):
    

Note, that no implicit argument is passed to foo()

Example


      class Person():
        @staticmethod
        def validate_age(age):
          if not 0 < age < 100:
            raise ValueError("Invalid age value")

        def __init__(self, name, age):
          self.name = name
          try:
            self.validate_age(age)
          except:
            raise
          else:
            self.age = age

        def __str__(self):
          return "{}: {}".format(self.name, self.age)

      maria = Person("Maria", 20)
      print(maria)

      pesho = Person("Pesho", 300)
      print(pesho)
    

"Normal" Method vs Class Method vs Static Method

"Normal" Method vs Class Method vs Static Method


    class A():
      @staticmethod
      def staticMethod():
        print("STATIC method fired!")
        print("Nothing is bound to me (but I'm defined inside a class)")
        print("~" * 30)

      @classmethod
      def classMethod(cls):
        print("CLASS method fired!")
        print(str(cls) + " is bound to me")
        print("~" * 30)

      # normal method
      def normalMethod(self):
        print("'normalMethod' fired!")
        print(str(self) + " is bound to me")
        print("~" * 30)


    a = A()
    a.staticMethod()
    a.classMethod()
    a.normalMethod()
  

Inheritance

Inheritance

Overview

The mechanism of Inheritance allows programmers to create classes that are built upon existing classes
Python supports class-based inheritance
I.e. inheritance allows a new class (Derived class) to be build upon existing class ( called Super or Base) class, extending (if needed) its behaviour

Syntax


      class DerivedClassName(BaseClassName):
        pass
    
The name BaseClassName must be defined in a scope containing the derived class definition

Example


      class Person():
        """docstring for Person"""
        def __init__(self, name, age):
          self.name = name
          self.age = age

        def __str__(self):
          return "{} is {} years old".format(self.name, self.age)

      class Employee(Person):
        pass

      pesho = Employee("Pesho",25)

      print(pesho)
    

Inheritance mechanism

If a requested attribute is not found in the child class, the search proceeds to look in the base class.
This rule is applied recursively if the base class itself is derived from some other class.
Derived classes may override methods of their base classes

Method overriding

Derived classes may override methods of their base classes.

      class Person():
        """docstring for Person"""
        def __init__(self, name, age):
          self.name = name
          self.age = age

        def __str__(self):
          return "{} is {} years old".format(self.name, self.age)

      class Employee(Person):
        def __str__(self):
          return "{} is  employee".format(self.name)

      maria = Person("Maria", 20)
      pesho = Employee("Pesho",25)

      print(maria)
      print(pesho)
    

Direct access to base methods

We can call the base class method directly


      BaseClass.method(self, arguments)
    

      class Person():
        """docstring for Person"""
        def __init__(self, name, age):
          self.name = name
          self.age = age

        def __str__(self):
          return "{} is {} years old".format(self.name, self.age)

      class Employee(Person):
        """docstring for Employee"""
        def __init__(self, name, age, salary):
          Person.__init__(self,name,age)
          self.salary = salary


      pesho = Employee("Pesho",25, 3500)
      print(pesho)
    

super() access to base methods

When we need to access inherited methods that have been overridden in a class, it's preffered to use super()


      class C(B):
          def method(self, arg):
              super().method(arg)
    

super() access to base methods - example


      class Person():
        """docstring for Person"""
        def __init__(self, name, age):
          self.name = name
          self.age = age

        def __str__(self):
          return "{} is {} years old".format(self.name, self.age)

      class Employee(Person):
        """docstring for Employee"""
        def __init__(self, name, age, salary):
          super().__init__(name,age)
          self.salary = salary

        def __str__(self):
          # return super().__str__() + ". Has salary: {}".format(self.salary)
          return Person.__str__(self) + ". Has salary: {}".format(self.salary)


      pesho = Employee("Pesho",25, 3500)
      print(pesho)
    

Operator overloading

Operator overloading

Overview

A class can implement certain operations that are invoked by special syntax (such as arithmetic operations or subscripting and slicing) by defining methods with special names
This is Python’s approach to operator overloading, allowing classes to define their own behavior with respect to language operators
Setting a special method to None indicates that the corresponding operation is not available

Example


      class Person():
        """docstring for Person"""
        def __init__(self, name, age):
          self.name = name
          self.age = age

        def __str__(self):
          return "{} is {} years old".format(self.name, self.age)

      class Employee(Person):
        """docstring for Employee"""
        def __init__(self, name, age, salary):
          super().__init__(name,age)
          self.salary = salary

        def __str__(self):
          # return super().__str__() + ". Has salary: {}".format(self.salary)
          return Person.__str__(self) + ". Has salary: {}".format(self.salary)

        def __add__(self,other):
          return self.salary + maria.salary


      pesho = Employee("Pesho",25, 3500)
      maria = Employee("maria",20, 2500)
      print(pesho)
      print(maria)

      print("pesho + maria = ",pesho + maria)
      # pesho + maria =  6000
    

Duck typing

Duck typing, EAFP

Overview

If it walks like a duck and it quacks like a duck, then it must be a duck

Duck typing is a feature of a type system where the semantics of a class is determined by his behaviour.

simple example


      class Duck:
         def quack(self):
            print('Quack, Quack')

      class Goose:
         def quack(self):
            print('Quack!')

      def quack(obj):
         obj.quack()


      donald = Duck()
      lea = Goose()

      quack(donald)
      quack(lea)
    

use case

Easy Dependency Injection implementation

      class Car:
         def __init__(self, engine):
            self.engine = engine
         def run(self):
             self.engine.start()

      class Engine:
        def start(self):
          print("Engine started!")

      trinity5_8 = Engine()
      ford = Car(trinity5_8)

      ford.run()
    

Resources

blogs, tutorials

Classes @python tutorial
Method overriding in Python
super() @python docs
Special method names @python docs

These slides are based on

customised version of

Hakimel's reveal.js

framework