In [19]:
class Chair(object):

    def __init__(self, occupant=None, colour='unknown'):
        self.occupant = occupant
        self.colour = colour
        
    def is_occupied(self):
        if self.occupant == None:
            return False
        else:
            return True
        
    def sit_in(self, person):
        if self.is_occupied():
            raise ValueError("Chair occupied by {}".format(
                                self.occupant))
        else:
            self.occupant = person
    
    def get_up(self):
        self.occupant = None

In [20]:
chair1 = Chair()
chair1.sit_in('Yves')
print chair1.occupant
chair1.sit_in('Megan')


Yves
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-20-38b923b4ce47> in <module>()
      2 chair1.sit_in('Yves')
      3 print chair1.occupant
----> 4 chair1.sit_in('Megan')

<ipython-input-19-68ef520e308f> in sit_in(self, person)
     14         if self.is_occupied():
     15             raise ValueError("Chair occupied by {}".format(
---> 16                                 self.occupant))
     17         else:
     18             self.occupant = person

ValueError: Chair occupied by Yves

In [23]:
class Bed(object):
    
    def __init__(self):
        self.occupants = []
        self.size = 1
    
    def set_size(self, size):
        self.size = size
    
    def get_size(self):
        if self.size == 1:
            return 'single'
        elif self.size == 2:
            return 'double'
        elif self.size == 3:
            return 'king'
        
    def get_in(self, person):
        if len(self.occupants) == self.size:
            return ValueError("Bed full!")
        else:
            self.occupants.append(person)
    
    def has_space(self):
        if len(self.occupants) < self.size:
            return True
        else:
            return False
    
    def leave(self, person):
        if person in self.occupants:
            del self.occupants[self.occupants.index(person)]

In [28]:
bed1 = Bed()
bed1.get_size()
bed1.set_size(2)
bed1.get_in('Peter')
bed1.get_in('Andrew')
print bed1.occupants
print bed1.has_space()


['Peter', 'Andrew']
False

In [55]:
class BlastHit(object):
    
    def __init__(self, fields):
        if type(fields) != list and len(fields) != 12:
            raise ValueError("BLAST hit requires a list of 12 fields")
        self.query_id = fields[0]
        self.subject_id = fields[1]
        self.perc_identity = float(fields[2])
        self.aln_length = int(fields[3])
        self.mismatches = int(fields[4])
        self.gap_opens = int(fields[5])
        self.q_start = int(fields[6])
        self.q_end = int(fields[7])
        self.s_start = int(fields[8])
        self.s_end = int(fields[9])
        self.e_val = float(fields[10])
        self.bit_score = float(fields[11])

Classes have a signature which is their collection of methods, i.e. their behaviour. Special method names allow us to give a class a certain behaviour, e.g. acting like a collection. See the documentation on emulating containter types.


In [66]:
class BlastHits(object):
    
    def __init__(self):
        self.hits = []
    
    def add_hit(self, hit):
        if type(hit) != BlastHit:
            raise ValueError("Can only add a BlastHit type")
        else:
            self.hits.append(hit)
    
    def __getitem__(self, i):
        return self.hits[i]
    
    def __len__(self):
        return len(self.hits)

In [112]:
import sys
import subprocess
import shlex
import os
import tempfile

class BlastRunner(object):
    
    def __init__(self, query_filename, dbname):
        self.query_filename = query_filename
        self.dbname = dbname
        self.blast_cmd = 'unknown'

    def parse_results(self, tempfile):
        input_file = open(tempfile)
        hits = BlastHits()
        with input_file:
            # Fields: query id, subject id, % identity, alignment length, mismatches, 
            # gap opens, q. start, q. end, s. start, s. end, evalue, bit score
            for line in input_file:
                line = line.rstrip()
                fields = line.split('\t')
                blast_hit = BlastHit(fields)
                hits.add_hit(blast_hit)
        return hits        

    def run(self):
        if self.blast_cmd == 'unknown':
            raise ValueError("Unknown BLAST type")
        tmpfile = tempfile.NamedTemporaryFile(delete=False)
        cmd_str = '{} -query {} -db {} -outfmt 6 -out {}'.format(
                    self.blast_cmd, self.query_filename, 
                    self.dbname, tmpfile.name)
        cmd = shlex.split(cmd_str)
        code = subprocess.call(cmd)
        hits = self.parse_results(tmpfile.name)
        os.remove(tmpfile.name)
        return hits
    
class NuclBlastRunner(BlastRunner):
    
    def __init__(self, query_filename, dbname):
        self.query_filename = query_filename
        self.dbname = dbname
        self.blast_cmd = 'blastn'

class ProtBlastRunner(BlastRunner):
    
    def __init__(self, query_filename, dbname):
        self.query_filename = query_filename
        self.dbname = dbname
        self.blast_cmd = 'blastp'

In [115]:
runner = NuclBlastRunner('query.fasta', 'blastdb')
hits = runner.run()

In [106]:
print hits


<__main__.BlastHits object at 0x7f4d3c0d4d50>

In [120]:
class RollableChair(Chair):
    
    def __init__(self, occupant=None, colour='unknown', location='here'):
        super(RollableChair, self).__init__(occupant, colour)
        self.location = location
        
    def roll(self, destination):
        self.location = destination

In [123]:
chair3 = RollableChair()
chair3.roll("Peter's office")
chair3.sit_in("Long")

Illustration of class variables (aka. class attributes) that are distinct from the attributes of objects.


In [142]:
class DNA(object):
    bases = set('ACTG')
    
    def __init__(self, sequence):
       self.sequence = sequence
    
    def is_dna(self):
        for char in self.sequence:
            if char not in self.__class__.bases:
                return False
        return True
    
    def change(self, bases):
        self.bases = set(bases)

In [143]:
dna1 = DNA('ACTG')

In [145]:
print dna1.is_dna()
dna1.change('ACTGN')
print dna1.bases
print dna1.__class__.bases
print DNA.bases


True
set(['A', 'C', 'T', 'G', 'N'])
set(['A', 'C', 'T', 'G'])
set(['A', 'C', 'T', 'G'])

By convention if we want to make an attribute (or a method) private, we prefix its named with an undercore (_). There is nothing stopping a programmer from directly accessing the attributes in the example below but they should not.


In [146]:
class Bird(object):
    
    def __init__(self, species):
        self._species = species
    
    def get_species(self):
        return species

    def set_species(self, species):
        self._species = species

(1) Create a class Table that has the attributes num_legs and colour and a method paint to change the colour.


In [153]:
class Table(object):
    
    def __init__(self, num_legs, colour):
        self.colour = colour
        self.num_legs = num_legs
    
    def paint(self, colour):
        self.colour = colour
    
    def get_colour():
        return self.colour
    
    def __str__(self):
        return "{} table with {} legs".format(self.colour, 
                                              self.num_legs)
    
    def __int__(self):
        return self.num_legs

In [154]:
table1 = Table(4, 'red')
print "table1:", table1
table1.paint('white')
print "table1:", table1
int(table1)


table1: red table with 4 legs
table1: white table with 4 legs
Out[154]:
4

In [ ]: