1. Objects, attributes, methods and __init__
A class is a recipe for an object, objects are instantiations of a class. For instance the class may be ‘Person’, and the instantiation might be ‘Bob’. Objects give us a natural way of thinking about code, instead of just having functions and variables, we have things that mirror those things we are trying to model. Basically it gives you a way to create your own data structure. Let’s do an example, and let me warn you: this post will have a star trek theme! See if you can spot it.
class IntelligentLifeForm(object): def __init__(self, name): self.name = name def say_name(self): print 'My name is %s.' % self.name def greeting(self):
So the class is called ‘IntelligentLifeForm’, the ‘object’ part in brackets is just something technical that I add in to make it compatible with certain Python 3 behaviours. As you can see, classes have what look like function definitions inside them.
We call these ‘methods’ or ‘class methods’. The funny looking one, ‘__init__’ has a special function: whenever an object is made, it runs init.
In addition to methods, classes also have variable or attributes, these you declare in init like ‘self.attribute’. The ‘self’ thing you see everywhere just means that it is referring to the object itself, and every method needs self as an argument. So how do we make an IntelligentLifeForm? Easy, we type:
CrystalEntity = IntelligentLifeForm(name='Crystal Entity') print CrystalEntity.name CrystalEntity.say_name() CrystalEntity.greeting() #OUTPUT Crystal Entity My name is Crystal Entity.
So all you do is call it like a function, passing whatever arguments that init asks for. Notice that attributes can accessed using the ‘.’ syntax directly, or indirectly through calling a method.
One of the most useful ideas in object orientated programming is inheritance, this allows us to create new classes that inherit features from a parent class, allowing us to be more specific, whilst avoiding code duplication. The best way to explain it is by doing it.
class Human(IntelligentLifeForm, object): def greeting(self): print 'Howdy!' def act_emotional(self): print 'KHAAAN!!!' Kirk = Human(name='Kirk') Kirk.greeting() Kirk.say_name() Kirk.act_emotional() #OUTPUT Howdy! My name is Kirk. KHAAAN!!!
So to inherit from a class, just add it to the class definition arguments. The new class will be exactly the same as the old one, except that we can declare new methods, like act_emotional, or override inherited methods, like greeting. After you play with this for a bit, you may wonder: how can I change behaviours instead of just overriding them? The answer is super.
class Vulcan(IntelligentLifeForm, object): def __init__(self, time_till_Pon_farr=7, **kwargs): self.time_till_Pon_farr = time_till_Pon_farr super(Vulcan, self).__init__(**kwargs) def greeting(self): print 'Live long and prosper.' def act_logical(self): print 'It is logical.' Tpol=Vulcan(time_till_Pon_farr=3, name='T\'Pol') print Tpol.time_till_Pon_farr #OUTPUT 3
So as you see when you call super, it gives you access to the methods of the parents, which is especially useful if you want to extend init, but can be used in any method. Also notice the kwargs argument to init, this refers to keyword arguments, it anticipates and stores an arbitrary number of keyword arguments, which you can then pass into another function. More on this in my forthcoming post on decorators.
3. Multiple Inheritance
What if you want to inherit from more than one class? Well this is not generally advised if possible, because it creates a problem of precedence when the parents have methods or attributes of the same name. We’ll see how this turns out in the following.
class ChildOfTwoWorlds(Vulcan, Human): pass Spock = ChildOfTwoWorlds(name='Spock', time_till_Pon_farr=6) Spock.greeting() Spock.say_name() Spock.act_logical() Spock.act_emotional() #OUTPUT Live long and prosper. My name is Spock. It is logical. KHAAAN!!!
So ChildOfTwoWorlds inherits from both the Vulcan and Human classes, and they from IntelligentLifeForms. The order in which the arguments are given in the class definition is important! It determines priority. Here we see that since Vulcan is before Human, ChildOfTwoWorlds uses the Vulcan greeting and not the Human. This example is simple, but it could quickly get difficult to manage.
Composition is a way of reusing code without inheritance and should probably be used where possible, as it avoids the complexities of class hierarchies.
It is used when you can think of one object as having parts that are other objects. For example:
class StarShip(object): def __init__(self, crew): self.crew=crew def go_to_warp(self, warp_factor): print 'Ahead warp factor %i.' % warp_factor def shields_up(self): print 'Shields up!' Enterprise = StarShip(crew=[Kirk, Spock]) Enterprise.go_to_warp(8) #OUTPUT Ahead warp factor 8.
For less trivial examples, the composed class will want to access the methods and attributes of its parts.
5. Dynamic Inheritance
This final section concerns another problem of reusing code, where two classes are very similar, and differ only in who they inherit from. You could of course define each separately, but code duplication is a sin! So what I will do is wrap the class definition in a function that takes the parent class as an argument, like this:
def return_starfleet_officer_class(Species): class Starfleet_Officer(Species, object): def __init__(self, rank, **kwargs): self.rank=rank super(Starfleet_Officer, self).__init__(**kwargs) return Starfleet_Officer Spock = return_starfleet_officer_class(ChildOfTwoWorlds)(name='Spock', rank='Commander') Spock.greeting() Spock.say_name() Spock.act_logical() Spock.act_emotional() print Spock.rank #OUTPUT Live long and prosper. My name is Spock. It is logical. KHAAAN!!! Commander
6. Next Time
There you have it, next time we will look at a another way of avoiding code duplication – decorators.