[Contents](../Contents.md) \| [Previous (3.6 Design discussion)](../03_Program_organization/06_Design_discussion.md) \| [Next (4.2 Inheritance)](02_Inheritance.md) # 4.1 Classes This section introduces the class statement and the idea of creating new objects. ### Object Oriented (OO) programming A Programming technique where code is organized as a collection of *objects*. An *object* consists of: * Data. Attributes * Behavior. Methods which are functions applied to the object. You have already been using some OO during this course. For example, manipulating a list. ```python >>> nums = [1, 2, 3] >>> nums.append(4) # Method >>> nums.insert(1,10) # Method >>> nums [1, 10, 2, 3, 4] # Data >>> ``` `nums` is an *instance* of a list. Methods (`append()` and `insert()`) are attached to the instance (`nums`). ### The `class` statement Use the `class` statement to define a new object. ```python class Player: def __init__(self, x, y): self.x = x self.y = y self.health = 100 def move(self, dx, dy): self.dx += dx self.dy += dy def damage(self, pts): self.health -= pts ``` In a nutshell, a class is a set of functions that carry out various operations on so-called *instances*. ### Instances Instances are the actual *objects* that you manipulate in your program. They are created by calling the class as a function. ```python >>> a = Player(2, 3) >>> b = Player(10, 20) >>> ``` `a` and `b` are instances of `Player`. *Emphasize: The class statement is just the definition (it does nothing by itself). Similar to a function definition.* ### Instance Data Each instance has its own local data. ```python >>> a.x 2 >>> b.x 10 ``` This data is initialized by the `__init__()`. ```python class Player: def __init__(self, x, y): # Any value stored on `self` is instance data self.x = x self.y = y self.health = 100 ``` There are no restrictions on the total number or type of attributes stored. ### Instance Methods Instance methods are functions applied to instances of an object. ```python class Player: ... # `move` is a method def move(self, dx, dy): self.x += dx self.y += dy ``` The object itself is always passed as first argument. ```python >>> a.move(1, 2) # matches `a` to `self` # matches `1` to `dx` # matches `2` to `dy` def move(self, dx, dy): ``` By convention, the instance is called `self`. However, the actual name used is unimportant. The object is always passed as the first argument. It is merely Python programming style to call this argument `self`. ### Class Scoping Classes do not define a scope of names. ```python class Player: ... def move(self, dx, dy): self.x += dx self.y += dy def left(self, amt): move(-amt, 0) # NO. Calls a global `move` function self.move(-amt, 0) # YES. Calls method `move` from above. ``` If you want to operate on an instance, you always refer to it explicitly (e.g., `self`). ## Exercises Starting with this set of exercises, we start to make a series of changes to existing code from previous sctions. It is critical that you have a working version of Exercise 3.18 to start. If you don't have that, please work from the solution code found in the `Solutions/3_18` directory. It's fine to copy it. ### Exercise 4.1: Objects as Data Structures In section 2 and 3, we worked with data represented as tuples and dictionaries. For example, a holding of stock could be represented as a tuple like this: ```python s = ('GOOG',100,490.10) ``` or as a dictionary like this: ```python s = { 'name' : 'GOOG', 'shares' : 100, 'price' : 490.10 } ``` You can even write functions for manipulating such data. For example: ```python def cost(s): return s['shares'] * s['price'] ``` However, as your program gets large, you might want to create a better sense of organization. Thus, another approach for representing data would be to define a class. Create a file called `stock.py` and define a class `Stock` that represents a single holding of stock. Have the instances of `Stock` have `name`, `shares`, and `price` attributes. For example: ```python >>> import stock >>> a = stock.Stock('GOOG',100,490.10) >>> a.name 'GOOG' >>> a.shares 100 >>> a.price 490.1 >>> ``` Create a few more `Stock` objects and manipulate them. For example: ```python >>> b = stock.Stock('AAPL', 50, 122.34) >>> c = stock.Stock('IBM', 75, 91.75) >>> b.shares * b.price 6117.0 >>> c.shares * c.price 6881.25 >>> stocks = [a, b, c] >>> stocks [, , ] >>> for s in stocks: print(f'{s.name:>10s} {s.shares:>10d} {s.price:>10.2f}') ... look at the output ... >>> ``` One thing to emphasize here is that the class `Stock` acts like a factory for creating instances of objects. Basically, you call it as a function and it creates a new object for you. Also, it must be emphasized that each object is distinct---they each have their own data that is separate from other objects that have been created. An object defined by a class is somewhat similar to a dictionary--just with somewhat different syntax. For example, instead of writing `s['name']` or `s['price']`, you now write `s.name` and `s.price`. ### Exercise 4.2: Adding some Methods With classes, you can attach functions to your objects. These are known as methods and are functions that operate on the data stored inside an object. Add a `cost()` and `sell()` method to your `Stock` object. They should work like this: ```python >>> import stock >>> s = stock.Stock('GOOG', 100, 490.10) >>> s.cost() 49010.0 >>> s.shares 100 >>> s.sell(25) >>> s.shares 75 >>> s.cost() 36757.5 >>> ``` ### Exercise 4.3: Creating a list of instances Try these steps to make a list of Stock instances from a list of dictionaries. Then compute the total cost: ```python >>> import fileparse >>> with open('Data/portfolio.csv') as lines: ... portdicts = fileparse.parse_csv(lines, select=['name','shares','price'], types=[str,int,float]) ... >>> portfolio = [ stock.Stock(d['name'], d['shares'], d['price']) for d in portdicts] >>> portfolio [, , , , , , ] >>> sum([s.cost() for s in portfolio]) 44671.15 >>> ``` ### Exercise 4.4: Using your class Modify the `read_portfolio()` function in the `report.py` program so that it reads a portfolio into a list of `Stock` instances as just shown in Exercise 4.3. Once you have done that, fix all of the code in `report.py` and `pcost.py` so that it works with `Stock` instances instead of dictionaries. Hint: You should not have to make major changes to the code. You will mainly be changing dictionary access such as `s['shares']` into `s.shares`. You should be able to run your functions the same as before: ```python >>> import pcost >>> pcost.portfolio_cost('Data/portfolio.csv') 44671.15 >>> import report >>> report.portfolio_report('Data/portfolio.csv', 'Data/prices.csv') Name Shares Price Change ---------- ---------- ---------- ---------- AA 100 9.22 -22.98 IBM 50 106.28 15.18 CAT 150 35.46 -47.98 MSFT 200 20.89 -30.34 GE 95 13.48 -26.89 MSFT 50 20.89 -44.21 IBM 100 106.28 35.84 >>> ``` [Contents](../Contents.md) \| [Previous (3.6 Design discussion)](../03_Program_organization/06_Design_discussion.md) \| [Next (4.2 Inheritance)](02_Inheritance.md)