Python Concepts/Classes

From Wikiversity
Jump to navigation Jump to search

Objective[edit | edit source]

Lesson[edit | edit source]

Python classes range from the simple to the complicated. We're already familiar with several of python's simple classes: int, float, complex. Classes more sophisticated include list, tuple, str and set, dict.

Python's builtin function type shows the class of an object.

>>> type (6)
<class 'int'>
>>> 
>>> type (6.4)
<class 'float'>
>>> 
>>> type (3-4j)
<class 'complex'>
>>> 
>>> type ('2.34')
<class 'str'>
>>> 
>>> type([1,2,3])
<class 'list'>
>>> 
>>> type((1,2,3))
<class 'tuple'>
>>> 
>>> type(bytes((1,2,3)))
<class 'bytes'>
>>> 
>>> type(bytearray((1,2,3)))
<class 'bytearray'>
>>> 
>>> type({2,3,4})
<class 'set'>
>>> 
>>> type({3:4, 8:10, 'blue':'red', 'green': 5})
<class 'dict'>
>>> 
>>> type(range(2))
<class 'range'>

Many classes become available after the appropriate module has been imported.

>>> import decimal
>>> 
>>> type(decimal.Decimal)
<class 'type'>
>>> 
>>> type(decimal.Decimal(8))
<class 'decimal.Decimal'>
>>> 
>>> type(decimal.BasicContext)
<class 'decimal.Context'>
>>> 
>>> type(decimal.getcontext)
<class 'builtin_function_or_method'>
>>> 
>>> type(decimal.getcontext())
<class 'decimal.Context'>
>>>

The real power of python lies in the fact that each user of python may create new classes as simply as writing new functions. And each new class easily encapsulates features that enhance the class.

Object Oriented Programming[edit | edit source]

OOP is a programming approach where objects are defined with methods (functions, actions or events) and properties (values, characteristics), resulting in more readable, more reusable code.

Lets say you're writing a program where you need to keep track of multiple cars. Each car has different characteristics like mileage, color, and top speed, but lucky for us they all can perform some common actions like braking, accelerating, and turning.

Instead of writing code separately for each car we could create a class called 'Car' that will be the blueprint for each particular car.

Constructing a class[edit | edit source]

Class is the name given to a generic description of an object. In python you define a class method (an action, event, or function) using the following structure:

class <<name>>:
    def <<method>> (self [, <<optional arguments>>]):
        <<Function codes>>

Let's take a detailed look. We define our object using the 'class' keyword, the name we want, and a colon. We define its methods as we would a normal function: only one indent with 'self' as its first argument (we get to this later). So our example car class may look like this:

class Car:
    def brake(self):
        print "Brakes"

    def accelerate(self):
        print "Accelerating"

But how do I use it?[edit | edit source]

Once you have created the class, you actually need to create an object for each instance of that class. In python we create a new variable to create an instance of a class. Example:

car1 = Car() # car 1 is my instance for the first car
car2 = Car()

# And use the object methods like
car1.brake()

Using the parentheses ("calling" the class) tells Python that you want to create an instance and not just copy the class definition. You would need to create a variable for each car. However, now each car object can take advantage of the class methods and attributes, so you don't need to write a brake and accelerate function for each car independently.

Properties[edit | edit source]

Right now all the cars look the same, but let's give them some properties to differentiate them.

A property is just a variable that is specific to a given object. To assign a property we write it like:
car1.color = "Red"
And retrieve its value like:
# Python 2
print car1.color

# Python 3
print(carl.color)

It is good programming practice to write functions to get (or retrieve) and set (or assign) properties that are not 'read-only'. For example:

class car:
    ... previous methods ...
    
    def set_owner(self,Owners_Name): # This will set the owner property
        self._owner = Owners_Name

    def get_owner(self): # This will retrieve the owner property
        return self._owner

Notice the single underscore before the property name; this is a way of hiding variable names from users.

Beginning from Python 2.2, you may also define the above example in a way that looks like a normal variable:

class car:
    ... previous methods ...
    owner = property(get_owner, set_owner)

Then, when you do like mycar.owner = "John Smith", the set_owner function is instead called transparently.

Extending a class[edit | edit source]

Let's say we want to add more functions to our class, but we are reluctant to change its code for fear that this might mess up programs that depend on its current version. The solution is to 'extend' our class. When you extend a class you inherit all the parent methods and properties and can add new ones. For example, we can add a start_car method to our car class. To extend a class we supply the name of the parent class in parentheses after the new class name, for example:

class new_car(car):
   def start_car(self):
      self.on = True
This new class extends the parent class.

Special Class Methods[edit | edit source]

In Python the names of special methods begin and end with double underscore - __. For example, the special method __init__ is used to initialize the state of newly created objects.For instance, we could create a new car object and set its brand, model, and year attributes on a single line, rather than expending an additional line for each attribute:

class new_car(car):
    def __init__(self,brand, model, year):
        # Sets all the properties
        self.brand = brand
        self.model = model
        self.year = year

    def start_car(self):
        """ Start the cars engine """
        print "vroem vroem"

if __name__ == "__main__":
    # Creates two instances of new_car, each with unique properties
    car1 = new_car("Ford","F-150",2001)
    car2 = new_car("Toyota","Corolla",2007)

    car1.start_car()
    car2.start_car()

For more information on classes go to the Class Section in Wikibooks.

class Queue[edit | edit source]

The following code implements class Queue. It serves as an example of a not-too-complicated class with attributes (usually not visible to the user) and methods that are used to implement FIFO (first in, first out) functionality.

class Queue :
# The following comment may be accessed via the class's .__doc__ attribute.
    '''
##################################
## class Queue
## To create a new class:
## Q1 = Queue()
## Q1 = Queue([1,2,3,4,5])
## Q1 = Queue('12345')
##
## To add to the queue:
## Q1.append([5,6,7])
##
## To get length of queue:
## Q1.size()
##
## To get data from the queue:
## v1, = Q1.get()
## v1,v2,v3  = Q1.get(3)
##
## To empty the queue:
## L1 = Q1.get(Q1.size())
##
## To get a string containing contents of queue and suitable for printing:
## Q1.pr()
## Q1.pr(5)
## Q1.pr(Q1.size())
##
## For .__init__ and .append
## typesAllowed = (list, tuple, str, bytes, bytearray, Queue )
##################################
'''

    def __init__(self, itemsForQueue=[]):
        line1 = ''
        for line in self.__doc__.split('\n') :
            if 'typesAllowed' in line :
                line1 = line ; break
        v1,equal,v2 = line1.partition('=')
        tA = self.typesAllowed = eval(v2)

        status = 0
        for type_ in tA :
            if isinstance(itemsForQueue, type_) :
                status = 1; break
        if not status :
            print ('Queue.__init__ : Input must be 1 of ', tA,sep='')
            return

        if isinstance(itemsForQueue, Queue) :
            self.queue = list(itemsForQueue.queue)
            return

        self.queue = list(itemsForQueue)
        return
        
    def getName(self) :
        status = 0
        try: name = self.name
        except : status = 1
        if status :
# Determine this queue's global name.
            v = name = ''
            for v in globals() :
                if globals()[v] == self :
                    name = v ; break
            if not name : name = str(self)
            self.name = name
        return name
        
    def pr (self, numberToBePrinted=0) :
        '''
#+++++++++++++++++++++++++++++++++
#+ method Queue.pr(numberToBePrinted)
#+ This method returns a string containing contents of queue and suitable for printing:
#+
#+ Q1 = Queue('1234567')
#+
#+ Basic info about Q1
#+ print (Q1.pr(-4)) If numberToBePrinted is negative, only one line is printed.
#+ Queue Q1: 7 item/s in queue.
#+ 
#+ print (Q1.pr())
#+ Queue Q1: 7 item/s in queue.
#+     7 of <class 'str'>
#+
#+ Basic info and the first 4 items in the queue:
#+ print (Q1.pr(4))
#+ Queue Q1: 7 item/s in queue.
#+     7 of <class 'str'>
#+     Q1.queue[0] = 1    <class 'str'>
#+     .............................
#+     Q1.queue[3] = 4    <class 'str'>
#+
#+ Basic info and all items in the queue:
#+ print (Q1.pr(Q1.size()))
#+ Queue Q1: 7 item/s in queue.
#+     7 of <class 'str'>
#+     Q1.queue[0] = 1    <class 'str'>
#+     .............................
#+     Q1.queue[6] = 7    <class 'str'>
#+++++++++++++++++++++++++++++++++
'''

        name = self.getName()

        if not isinstance(numberToBePrinted, int) :
            print ('Queue ',name,'.pr: Input must be int.',sep='')
            return

        L1 = [ 'Queue ' + name + ': ' + str(self.size()) + ' item/s in queue.' ]
# L1 = [ 'Queue Qx: 4 item/s in queue.' ]
        if (numberToBePrinted < 0) : return L1[0]
        
        d1 = dict()
        for p in self.queue : d1[type(p)] = 0
        for p in self.queue : d1[type(p)] += 1
        for p in d1 : L1 += [ '    ' + str(d1[p]) + ' of ' + str(p) ]
# L1 = [ 'Queue Qx: 4 item/s in queue.',
#        "    4 of <class 'str'>" ]

        min = sorted([numberToBePrinted, self.size()])[0]
        len2 = len(str(min-1))
        for p in range (0, min) :
            p_as_str = ((' '*len2) + str(p))[-len2:]
            value = self.queue[p]
            printedValue = str(value)
            if len(printedValue) > 80 :
                len1 = len(printedValue)
                printedValue = printedValue[:40] + '.....' + printedValue[-40:]
                addendum = 'len(str(queue[{}]))={}'.format(p,len1)
                L1 += [ '    ' + name + '.queue[' + p_as_str + ']' ]
                L1 += [ '        = ' + printedValue ]
                L1 += [ '        ' + str(type(value)) + ' ' + addendum]
            else :
                L1 += [ '    ' + name + '.queue[' + p_as_str + '] = ' + printedValue + '    ' + str(type(value)) ]
# L1 = [ 'Queue Qx: 4 item/s in queue.',
#        "    4 of <class 'str'>",
#        "    Qx.queue[0] = 15    <class 'str'>",
#        "    Qx.queue[1] = 16    <class 'str'>",
#        "    Qx.queue[2] = 17    <class 'str'>",
#        "    Qx.queue[3] = 18    <class 'str'>" ]

        return '\n'.join(L1)
        
    def append (self, itemsToBeAppended=[]) :
        '''
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#% method Queue.append(itemsToBeAppended)
#% This method appends the data supplied to the end of the queue.
#% If no data is supplied, this method quietly does nothing.
#%
#% Q1 = Queue('123')
#%
#% Q1.append((4,5))
#%
#% Basic info and all items in the queue:
#% print (Q1.pr(Q1.size()))
#% Queue Q1: 5 item/s in queue.
#%     3 of <class 'str'>
#%     2 of <class 'int'>
#%     Q1.queue[0] = 1    <class 'str'>
#%     .............................
#%     Q1.queue[4] = 5    <class 'int'>
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
'''
        tA = self.typesAllowed
        status = 0
        for type_ in tA :
            if isinstance(itemsToBeAppended, type_) :
                status = 1; break
        if not status :
# Determine this queue's global name.
            name = self.getName()
            print ('Queue ',name,'.append: Input must be 1 of ', tA, sep='')
            return

        if isinstance(itemsToBeAppended, Queue) :
            self.queue += list(itemsToBeAppended.queue)
            return

        self.queue += list(itemsToBeAppended)
        return
        
    def get (self, numberOfItemsToGet=1) :
        '''
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#! method Queue.get(numberOfItemsToGet)
#! This method gets data from the front of the queue.
#! Default value of numberOfItemsToGet=1.
#!
#! If Q1 is empty, the following returns an empty list.
#! list1 = Q1.get()
#!
#! If Q1 is empty, the following raises an error.
#! v1, = Q1.get()
#!
#! Q1 = Queue('123')
#! The following quietly empties the queue:
#! v1,v2,v3 = Q1.get(100)
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
'''
        if not isinstance(numberOfItemsToGet, int) :
            name = self.getName()
            print ('Queue ',name,'.get: Input must be int.', sep='')
            return
        if (numberOfItemsToGet < 0) :
            name = self.getName()
            print ('Queue ',name,'.get: Input must be non-negative.', sep='')
            return
        value = self.queue[:numberOfItemsToGet]
        self.queue[:numberOfItemsToGet] = list([])
        return value


    def size (self) :
        '''
#*********************************
#* method Queue.size()
#* This method returns number of items in queue.
#*********************************
'''
        return len (self.queue)

Examples[edit | edit source]

Create a new Queue and print contents:

Q1 = Queue('123')
Q1.append([2,3.14159])
print (type(Q1))
print ( Q1.pr(-44) )
print ( Q1.pr() )
print ( Q1.pr(Q1.size()) )
<class '__main__.Queue'>
Queue Q1: 5 item/s in queue.
Queue Q1: 5 item/s in queue.
    3 of <class 'str'>
    1 of <class 'int'>
    1 of <class 'float'>
Queue Q1: 5 item/s in queue.
    3 of <class 'str'>
    1 of <class 'int'>
    1 of <class 'float'>
    Q1.queue[0] = 1    <class 'str'>
    Q1.queue[1] = 2    <class 'str'>
    Q1.queue[2] = 3    <class 'str'>
    Q1.queue[3] = 2    <class 'int'>
    Q1.queue[4] = 3.14159    <class 'float'>

Print the __doc__ string:

print (Q1.__doc__)
print (Queue.size.__doc__)
print (Q1.size.__doc__)
##################################
## class Queue
## To create a new class:
## Q1 = Queue()
## Q1 = Queue([1,2,3,4,5])
## Q1 = Queue('12345')
##
## To add to the queue:
## Q1.append([5,6,7])
##
## To get length of queue:
## Q1.size()
##
## To get data from the queue:
## v1, = Q1.get()
## v1,v2,v3  = Q1.get(3)
##
## To empty the queue:
## L1 = Q1.get(Q1.size())
##
## To get a string containing contents of queue and suitable for printing:
## Q1.pr()
## Q1.pr(5)
## Q1.pr(Q1.size())
##
## For .__init__ and .append
## typesAllowed = (list, tuple, str, bytes, bytearray, Queue )
##################################


#*********************************
#* method Queue.size()
#* This method returns number of items in queue.
#*********************************


#*********************************
#* method Queue.size()
#* This method returns number of items in queue.
#*********************************

Make a shallow or soft copy:

Q1 = Queue ('123')
Q2 = Q1
print (Q2.pr())
Queue Q1: 3 item/s in queue. # It thinks its name is Q1.
    3 of <class 'str'>

Make a deep or hard copy:

Q1 = Queue ('123')
Q2 = Queue (Q1)
Q1 = Queue()
print (Q1.pr())
print (Q2.pr())
Queue Q1: 0 item/s in queue.
Queue Q2: 3 item/s in queue.
    3 of <class 'str'>

Print long strings:

import decimal
decimal.getcontext().prec = 200
d1 = decimal.Decimal('1234567890' * 15)

Q200 = Queue([d1,3.14159,int('12345'*20),3+4j,'3456'])
print ( Q200.pr(100))
Queue Q200: 5 item/s in queue.
    1 of <class 'decimal.Decimal'>
    1 of <class 'float'>
    1 of <class 'int'>
    1 of <class 'complex'>
    1 of <class 'str'>
    Q200.queue[0]
        = 1234567890123456789012345678901234567890.....1234567890123456789012345678901234567890
        <class 'decimal.Decimal'> len(str(queue[0]))=150
    Q200.queue[1] = 3.14159    <class 'float'>
    Q200.queue[2]
        = 1234512345123451234512345123451234512345.....1234512345123451234512345123451234512345
        <class 'int'> len(str(queue[2]))=100
    Q200.queue[3] = (3+4j)    <class 'complex'>
    Q200.queue[4] = 3456    <class 'str'>

Get data from queue:

Q1 = Queue('123456')
L1 = Q1.get(3)
print ('L1 =', L1)
print (Q1.pr(10))
L1 = ['1', '2', '3']
Queue Q1: 3 item/s in queue.
    3 of <class 'str'>
    Q1.queue[0] = 4    <class 'str'>
    Q1.queue[1] = 5    <class 'str'>
    Q1.queue[2] = 6    <class 'str'>

Examples of errors:

Q1 = Queue({2,3,4,5})
Queue.__init__ : Input must be 1 of (<class 'list'>, <class 'tuple'>, <class 'str'>, <class 'bytes'>, <class 'bytearray'>, <class '__main__.Queue'>)

Q200 = Queue('1,2,3,4,5')
Q200.get(2.4)
Q200.get(-3)
Queue Q200.get: Input must be int.
Queue Q200.get: Input must be non-negative.

Q2 = Queue(1,2,3,4,5)
Traceback (most recent call last):
  File "test.py", line 286, in <module>
    Q2 = Queue(1,2,3,4,5)
TypeError: __init__() takes from 1 to 2 positional arguments but 6 were given

Assignments[edit | edit source]

Further Reading or Review[edit | edit source]

Completion status: this resource is a stub, which means that pretty much nothing has been done yet.

References[edit | edit source]

1. Python's documentation:

"9. Classes"


2. Python's methods:


3. Python's built-in functions: