Python: Object Orientation

This is part of my Python & Django Series which can be found here including information on how to download all the source code.

Like many modern day languages, Python is object orientated or at least has object orientated features.

The Pillars Of Object Orientation

Object orientation has three defined pillars; abstraction, encapsulation and polymorphism.

Abstraction

Abstraction is the process of taking a complex implementation and providing a simplified interface for consuming it. Consumers are hidden from the complexities of the internals by being presented with an abstracted interface.

Abstraction is implemented by creating classes with state (member fields) and behaviour (methods).

Encapsulation

Encapsulation is the process of restricting access of an objects state and behaviour from consumers to prevent any incorrect usage.

Another advantage of encapsulation is that by defining an interface for a consuming an object, any changes to the internals of that object can safely occur without breaking any of the consumers, as long as the existing interface remains in tact.

Encapsulation is implemented by adding access modifiers onto types and their defined members; ie by making them private.

Pythons does not directly support access modifiers, though convention states that sate or behaviour with the naming convention of __state__ is to be treated as private. The convention __classname__state__ can be used to ensure name uniqueness.

However the private state is only a convention and it is up to the developer to respect this.

Polymorphism

The ability of a group of heterogeneous objects to be treated as a homogeneous group by exposing the same interface.

In more traditional OO languages there are two types of polymorphism; interface and inheritance.

Python with its dynamic functionality allows for all objects to be treated as the same same as long as they respond to the state or behaviour being called, otherwise it goes a a big bang.

Class Definition

Classes in Python feel little more than giving functions and variables scope. Their simple syntax and their ability to be dynamic strengthen this thought. To create a class we simply need the class keyword and the name of the class. Below we create a class called Person.

Code:

class Person():

An instance of the class can be created with a method named after the class.

Code:

foo = Person():

Methods

Class methods are any functions which are in scope; i.e they follow the class definition and are indented.

Code:

class Person():

    def DoSomething(self):
        return 'This is a person!'

To access a member method we use the name of the method along with the dot notation and either the self key word or the class instance. Self for when we are inside the scope of the class and the class instance when we are outside.

Methods are by default all public unless explicitly defined as private.

Code:

foo = Person():
print(foo.DoSomething())

Output:

This is a person!

State

In object orientation, state is represented as data. Other OO languages allow data to belong to an instance of a class, or shared between all instances.

Instance Variables

Instance or member variables are simply variables which are assigned to a class within it’s scope. In its simplest form we can simply use self.variable_name = x to assign a value to a member variable. This can be done without any prior knowledge of the member variable.

Code:

self.Name = name
classinstance.Name = name

The preferred way of creating member variables is within the _init__ method which is Python’s constructor method for a class.

To access a member variable we use the name of the variable along with the dot notation and either the self keyword or the class instance. Self for when we are inside the scope of the class and the class instance when we are outside.

In the following code we create two member variables, Name and Age, onto the Person class within the constructor.

The str method is a special method to report the class instance as a string. It is called by the print() method.

Code:

class Person():

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

    def __str__(self):
        return '{0} is {1} years old.'.format(self.Name, self.Age)

Code:

a_person = Person("Luke", 36)
print(a_person)
print(a_person.Age)

Output:

Luke is 36 years old.
36

Class member variables are by default all public unless explicitly defined as private.

Class Variables

Class variables are similar to static or friend variables in other languages. They are state which is shared between class instances however they do not behave as you would expect!

Immutable objects appear to be instance variables while mutable objects are shared until they are reassigned. Take the following class.

Code:

class ClassVariable():

    AnInt = 1
    AList = []

    def __str__(self):
        return "AnInt = {0}, AList = {1}".format(self.AnInt, self.AList)

If we manipulate the state of two separate instances:

Code:

one = ClassVariable()
two = ClassVariable()
one.AnInt += 1
one.AList.append("Item-1")

print(one)
print(two)

two.AList = []
print(one)
print(two)

We get the following at the terminal.

Output:

AnInt = 2, AList = [‘Item-1’]
AnInt = 1, AList = [‘Item-1’]
AnInt = 2, AList = [‘Item-1’]
AnInt = 1, AList = []

The AnInt, being an integer and immutable appears to work as an instance variable, i.e changes to an instance only affect that instances.

The AList being mutable acts as a shared variable when modifying the object, i.e changes to an instance are reflected by all class instances. However when we reassign a new list onto one of the instances, both instances now point to two separate list objects; changes to the new list do not affect other classes.

It is strongly advised against using class variables.

Python Classes Are Dynamic

Extending upon the above, Python classes are actually dynamic; during run time we can assign an instance of a member variable or even a method which will only be available to that instance.

Code:

a_person.IsCool = "In a quirky way"

print("Is {0} a cool?: {1}".format(a_person.Name, a_person.IsCool))

Output:

Is Luke a cool?: In a quirky way

Subsequent class instances of Person won’t respond to the IsCool instance method as it was added onto an instance of the class Person.

In short two class instances which are of the same class type can literally have a different set of member fields and functions!

We can use the hasattr method to determine if an instance responds to a variable or method name.

Code:

a_person = Person("Luke", 36)

if hasattr(a_person, 'IsCool'):
    print("Is {0} a cool?: {1}".format(a_person.Name, a_person.IsCool))
else:
    print("{0} does not respond to IsCool".format(a_person.Name))

Output:

Luke does not respond to IsCool

IsInstance

Due to Python’s dynamic nature you don’t know what class or type a variable holds until run time. Fortunately the isinstance method can be used. Taking a variable and a type name it returns true if the variable is a instance of the type.

Code:

isinstance(a_person, Person)

Inheritance

Inheritance promotes code reuse by implementing common ancestors; state and behaviour can be inherited from another class.

The following class Boy extends Person by inheriting from it.

Code:

class Boy(Person):
    def __init__(self, name, age):
        super().__init__(name, age)
        self.Sex = "Boy"

    def __str__(self):
        return "{0} and is a {1}".format(super().__str__(), self.Sex)

We can access the state of the Person class or its behaviour directly from the Boy subclass.

The super function can be used to ensure the parent class state or behaviour is called. In the example above both Boy and Person have a constructor called init. We use super.init to call the Person.Init from inside the Boy.Init function.

Code:

    a_boy = Boy("LukeyBoy", 36)
    print(a_boy.Name)

Output:

LukeyBoy

All member variables and methods are virtual by default; they can be overridden by any inheriting classes. This is shown by the two implementations of the str method. Boy overrides the version provided by Person. Calling string on Boy calls the version local to to that class, however we can still access overridden data and behaviour by replacing self with super().

IsSubClass

The method issubclass can be used to determine if a type is another type or has it in its ancestry. This means it inherits from it by not necessarily directly.

The method works directly on the types and not instances of the types. The type method can be used to get a type from an instance.

Code:

print("Is Boy A SubClass of Boy?:", issubclass(Boy, Boy))
print("Is Boy A SubClass of Person?:", issubclass(Boy, Person))
print("Is Person A SubClass of Boy?:", issubclass(Person, Boy))

Output:

Is Boy A SubClass of Boy?: True
Is Boy A SubClass of Person?: True
Is Person A SubClass of Boy?: False

Multiple Inheritance

Python allows multiple direct ancestors or multiple inheritance, by simply separating the classes being extended by commas.

Code:

    class ExtendingClass(Base1, Base2, Base3.....):

Any calls to state of behaviour on an instance of ExtendingClass will simply look in the order of ExtendingClass, Base1, Base2, Base3 stopping when a match is made.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s