# Object Oriented Programming (OOP) in Python. # We've seen code such as 'A.append(x)', brush.draw_line(..), fd.close(), # etc... These are function calls, but they are slightly different from # the kind of functions that we have so far studied. Previously, all # the information that a function needs are passed in through its list of # parameters (arguments). But in an expression such as A.append(x), the # function 'append' is *oriented* towards a certain *data object*, namely A. # An object is a collection of data or attributes. And functions that # that are oriented towards objects are also called 'methods'. # Functions allow us to structure large programs into self-contained # components. Objects serve this purpose at an even higher level. # A note of warning however: using objects in a program is not quite # the same as "object oriented programming" which relies on a technique # called dynamic dispatch. # Time for an example. Suppose I want to write a program that manages # bank accounts. Each account contains several pieces of data or # *attributes*, such as who owns the account, the current account balance, # interest rate, etc... Each account is a data *object*. # The operations that I want to be able to define for each account object # are withdraw, deposit, balance-inquiry, etc... I want to be able to # *eventually* write the following code: ## create an account object for me with an initial balance of $1000: # myaccount = account("prof essor",1000) # youraccount = account("stu dent",2000) # create an account for you too. # myaccount.withdraw(400) # withdraw $400 from myaccount # youraccount.deposit(30) # deposit $30 into youraccount # print myaccount.inquiry() # balance inquiry on both accounts # print youraccount.inquiry() ## In this example, 'myaccount' and 'youraccount' are data objects. They're # also called *instances* of account. The code is almost self-explanatory, # which is the advantage that objects give us. But in order for the above # code to work, we'll need to define how to create an account, and what the # methods withdraw/depoist/inquiry actually do. This is done by # defining a *class*: class account: # The *constructor* function: def __init__(self,name,initbalance): self.owner = name # initialize data fields of the object self.balance = initbalance # end of constructor function def deposit(self, amount): self.balance = self.balance + amount # add to balance # deposit method def withdraw(this, amount): # don't have to always call it 'self' if amount<=this.balance: this.balance = this.balance - amount else: raise Exception("you don't have that much money!") # withdraw method def inquiry(me): # note "me" used instead of "self" return me.balance # balance inquiry method # end of class account myaccount = account("chuck liang",1000) youraccount = account("stu dent",2000) # create an account for you too. myaccount.withdraw(400) # withdraw $400 from myaccount youraccount.deposit(30) # deposit $30 into youraccount print myaccount.inquiry() # balance inquiry on both accounts print youraccount.inquiry() youraccount.withdraw(-2000000) # he he he ... print youraccount.owner, "has a balance of ", youraccount.inquiry() account.deposit(myaccount,50) # equivalent to myaccount.deposit(50) # The account 'class' is a template or blueprint for creating account objects. # The class basically contains a series of function (method) # definitions. The most important of these functions is __init__. This # function, which MUST be called __init__ (two underscores on each side), # is called the "constructor" of the class. This the function that's called # when you say myaccount = account("my name",1000). It is the job of this # function to initialized the account object. Even though there is no # 'return' statement at the end of __init__, this special function actually # will return a pointer to the object that it has just created. # The first parameter of __init__, which I called 'self' but really it # can be called anything ('this', 'me', etc...) has a special status. # It is a pointer to the object that's being constructed. The __init__ # method then defines the *fields* (variables) of the object (owner and # balance). You can think of the object as just a collection of variables. # Every account object contains a 'balance' variable and a 'owner' variable. # When I call account("stu dent",2000), the 'self' value is constructed # automatically. The string "stu dent" is passed to the parameter 'name' and # and 2000 is passed to the parameter 'initbalance'. # The class then defines the methods or functions that one wishes to call # on account objects, in this case deposit, withdraw and balance-inquiry. # Each of these methods also must have a distinguished first parameter: # This parameter points to the object that the function is operating on. # This is why deposit changes 'self.balance' and withdraw changes this.balance. # The first parameter is always a pointer to the data object: without it # we would not known *which* account we're depositing into. # To call a method on an object, we can use one of two forms: # "Functional Form": account.deposit(youraccount, 50) # calls the deposit method of class account, passing the pointer youraccount # to the functions's 'self' parameter, and 50 to the 'amount' parameter. # However, we will usually invoke the function as follows # "Object Oriented Form": youraccount.deposit(50) # This is the correct form for object oriented programming. The # pointer to the left of the . is implicitly passed as the first paramter # of deposit. The class that youraccount belongs to is infered from # the type of the youraccount object. # That is, when I make a call such as myaccount.withdraw(30), the # parameter 'this' is passed a pointer to myaccount, and the parameter # 'amount' is passed 30. # *** Remember: every method is defined with one extra parameter: the # *** first parameter is always the pointer to the object that the method # *** operates on. # Also note that you can have two different classes: A and B, and each # can define a function 'f'. But there's no confusion because the 'f' # is only called on different kinds of objects. # Remember: a class is a template for creating objects. The __init__ # method initializes the fields of the object. Do not confuse the class # with an object itself. The objects are 'myaccount' and 'youraccount', # which are two 'instances' of the class 'account'. print "--------- Another Example of a Class and Objects ---------" # This time I want to write a program to manage the win-loss records of # sports teams. Each team is a data object that records the number of # wins and the number of losses, as well as the number of games left on # the team's schedule (you can also imagine many other attributes). Thus # each team object contains the variables 'wins', 'losses' and 'gamesleft'. class team: def __init__(self,totalgames): self.wins = 0 self.losses = 0 self.gamesleft = totalgames # constructor def win(self): # method that records a win self.wins += 1 self.gamesleft -= 1 print "yeah!" # some arbitrary action associated with winning # win def lose(self): # method that records a loss self.losses += 1 self.gamesleft -=1 print "boo!" # lose def percentage(self): # calculate the team's winning percentage gamesplayed = self.wins + self.losses if gamesplayed==0: return 0 # don't divide by zero! return self.wins/(gamesplayed *1.0) # *1.0 to make it a float # percentage def project(self): # project the win-loss record of the entire seaon wp = self.percentage() # call percentage to get current percentage totalgames = self.wins + self.losses + self.gamesleft predictedwins = int(wp * totalgames) predictedlosses = totalgames - predictedwins return (predictedwins, predictedlosses) # project # note how percentage is called from within this function. def betterthan(myteam,yourteam): # see if myteam's perentage is better mp = myteam.percentage() yp = yourteam.percentage() return (mp > yp) # returns True or False # betterthan # end class team # Now we can create team objects (instances of class team): jets = team(16) # calls __init__, self = pointer to jets, totalgames = 16 giants = team(16) giants.win() # calls win, self = pointer to giants giants.lose() giants.win() jets.lose() jets.lose() jets.win() jets.lose() jets.lose() print "jets' winning percentage: ", jets.percentage() print "giants' winning percentage: %.2f" % giants.percentage() (w,l) = giants.project() print "giants' predicted record: ",w,"-",l if giants.betterthan(jets): print "giants have better record" # Pay close attention to how the 'betterthan' method is defined and called. # I used 'myteam' instead of 'self' in the definition. What's important is # not the world "self" but the fact that 'myteam' is the first parameter # which always points to the object that the method operates on. In the # above call to betterthan, 'myteam' points to giants and the remaining # parameter 'yourteam' points to jets. ### Another interesting point to notice: in the project method, I had to # calculate the value totolgames (total number of games in a season). # You cannot just use the totalgames variable from the __init__ function # because it's a local variable of that function. But then why isn't the # same true of the self variable? Isn't the self variable local to each # function? And if so, then wouldn't any changes made to it also be # in effect only locally? To understand this you need to remember what I # told you about pointers. Objects, like arrays (which are special kinds of # objects as well), are referred to using pointers (memory addresses). Yes, # each 'self' variable is local to each function, but they can all point to # the same object - the same collection of data - in memory. When I call # giants.win() and then giants.lose(), the 'self' variable in both the win # and lose functions both point to the object giants. So changing self.wins # or self.losses will have an effect outside of the function. print "-------------------- An Abstract Example --------------------" # Examples of every-day objects such as bank accounts and football teams # may help motivate why we use oop. But it's important to understand the # precise mechanisms that are driving the two programs above. So the # following example is completely abstract. class AA: def __init__(a,i): a.x = i a.y = 0 # constructor def f(s, j): s.x += j return s.x + s.y # method f def g(self): self.y = 1 # method g # class AA #### instances of class AA a1 = AA(0) # invokes constructor, a = a1, i = 0, a2 = AA(2) # creates an AA object (an instance of AA) a1.g() # invokes method g, self = a1 print a1.f(1) # invokes method f, s=a1, j = 1 print a2.f(2) # Each instance of the class AA contains two attributes: x and y. This # is indicated by the variables being instantiated inside the constructor # method. In each method, including the constructor, the first parameter # (a, s, self respectively) points to the object being operated on (the # "object in question"). The constructor takes one additional argument, # which must be passed in when the object is first created.