Edits
This commit is contained in:
@@ -1,17 +1,22 @@
|
||||
[Contents](../Contents) \| [Previous (3.6 Design discussion)](../03_Program_organization/06_Design_discussion) \| [Next (4.2 Inheritance)](02_Inheritance)
|
||||
|
||||
# 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*.
|
||||
A Programming technique where code is organized as a collection of
|
||||
*objects*.
|
||||
|
||||
An *object* consists of:
|
||||
|
||||
* Data. Attributes
|
||||
* Behavior. Methods, functions applied to the object.
|
||||
* Behavior. Methods which are functions applied to the object.
|
||||
|
||||
You have already been using some OO during this course.
|
||||
|
||||
For example with Lists.
|
||||
For example, manipulating a list.
|
||||
|
||||
```python
|
||||
>>> nums = [1, 2, 3]
|
||||
@@ -24,14 +29,14 @@ For example with Lists.
|
||||
|
||||
`nums` is an *instance* of a list.
|
||||
|
||||
Methods (`append` and `insert`) are attached to the instance (`nums`).
|
||||
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(object):
|
||||
class Player:
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
@@ -59,9 +64,10 @@ They are created by calling the class as a function.
|
||||
>>>
|
||||
```
|
||||
|
||||
`a` anb `b` are instances of `Player`.
|
||||
`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.*
|
||||
*Emphasize: The class statement is just the definition (it does
|
||||
nothing by itself). Similar to a function definition.*
|
||||
|
||||
### Instance Data
|
||||
|
||||
@@ -77,7 +83,7 @@ Each instance has its own local data.
|
||||
This data is initialized by the `__init__()`.
|
||||
|
||||
```python
|
||||
class Player(object):
|
||||
class Player:
|
||||
def __init__(self, x, y):
|
||||
# Any value stored on `self` is instance data
|
||||
self.x = x
|
||||
@@ -92,7 +98,7 @@ There are no restrictions on the total number or type of attributes stored.
|
||||
Instance methods are functions applied to instances of an object.
|
||||
|
||||
```python
|
||||
class Player(object):
|
||||
class Player:
|
||||
...
|
||||
# `move` is a method
|
||||
def move(self, dx, dy):
|
||||
@@ -113,15 +119,15 @@ 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 simply Python programming style to call this argument
|
||||
argument. It is merely Python programming style to call this argument
|
||||
`self`.
|
||||
|
||||
### Class Scoping
|
||||
|
||||
Classes do not define a scope.
|
||||
Classes do not define a scope of names.
|
||||
|
||||
```python
|
||||
class Player(object):
|
||||
class Player:
|
||||
...
|
||||
def move(self, dx, dy):
|
||||
self.x += dx
|
||||
@@ -132,13 +138,15 @@ class Player(object):
|
||||
self.move(-amt, 0) # YES. Calls method `move` from above.
|
||||
```
|
||||
|
||||
If you want to operate on an instance, you always have to refer too it explicitly (e.g., `self`).
|
||||
If you want to operate on an instance, you always refer to it explicitly (e.g., `self`).
|
||||
|
||||
## Exercises
|
||||
|
||||
Note: For this exercise you want to have fully working code from earlier
|
||||
exercises. If things are broken look at the solution code for Exercise 3.18.
|
||||
You can find this code in the `Solutions/3_18` directory.
|
||||
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
|
||||
|
||||
@@ -206,8 +214,8 @@ Create a few more `Stock` objects and manipulate them. For example:
|
||||
|
||||
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 needs
|
||||
to be emphasized that each object is distinct---they each have their
|
||||
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
|
||||
@@ -238,8 +246,8 @@ stored inside an object. Add a `cost()` and `sell()` method to your
|
||||
|
||||
### Exercise 4.3: Creating a list of instances
|
||||
|
||||
Try these steps to make a list of Stock instances and compute the total
|
||||
cost:
|
||||
Try these steps to make a list of Stock instances from a list of
|
||||
dictionaries. Then compute the total cost:
|
||||
|
||||
```python
|
||||
>>> import fileparse
|
||||
@@ -258,10 +266,11 @@ cost:
|
||||
|
||||
### 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. 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.
|
||||
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`.
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
[Contents](../Contents) \| [Previous (4.1 Classes)](01_Class) \| [Next (4.3 Special methods)](03_Special_methods)
|
||||
|
||||
# 4.2 Inheritance
|
||||
|
||||
Inheritance is a commonly used tool for writing extensible programs. This section explores that idea.
|
||||
Inheritance is a commonly used tool for writing extensible programs.
|
||||
This section explores that idea.
|
||||
|
||||
### Introduction
|
||||
|
||||
@@ -10,13 +13,13 @@ Inheritance is used to specialize existing objects:
|
||||
class Parent:
|
||||
...
|
||||
|
||||
class Child(Parent): # Check how `Parent` is between the parenthesis
|
||||
class Child(Parent):
|
||||
...
|
||||
```
|
||||
|
||||
The new class `Child` is called a derived class or subclass.
|
||||
The `Parent` class is known as base class or superclass.
|
||||
`Parent` is specified in `()` after the class name, `class Child(Parent):`.
|
||||
The new class `Child` is called a derived class or subclass. The
|
||||
`Parent` class is known as base class or superclass. `Parent` is
|
||||
specified in `()` after the class name, `class Child(Parent):`.
|
||||
|
||||
### Extending
|
||||
|
||||
@@ -33,7 +36,7 @@ In the end you are **extending existing code**.
|
||||
Suppose that this is your starting class:
|
||||
|
||||
```python
|
||||
class Stock(object):
|
||||
class Stock:
|
||||
def __init__(self, name, shares, price):
|
||||
self.name = name
|
||||
self.shares = shares
|
||||
@@ -61,9 +64,11 @@ Usage example.
|
||||
```python
|
||||
>>> s = MyStock('GOOG', 100, 490.1)
|
||||
>>> s.sell(25)
|
||||
>>> s.shares 75
|
||||
>>> s.shares
|
||||
75
|
||||
>>> s.panic()
|
||||
>>> s.shares 0
|
||||
>>> s.shares
|
||||
0
|
||||
>>>
|
||||
```
|
||||
|
||||
@@ -84,15 +89,15 @@ Usage example.
|
||||
>>>
|
||||
```
|
||||
|
||||
The new method takes the place of the old one. The other methods are unaffected.
|
||||
The new method takes the place of the old one. The other methods are unaffected. It's tremendous.
|
||||
|
||||
## Overriding
|
||||
|
||||
Sometimes a class extends an existing method, but it wants to use the original implementation.
|
||||
For this, use `super()`:
|
||||
Sometimes a class extends an existing method, but it wants to use the
|
||||
original implementation inside the redefinition. For this, use `super()`:
|
||||
|
||||
```python
|
||||
class Stock(object):
|
||||
class Stock:
|
||||
...
|
||||
def cost(self):
|
||||
return self.shares * self.price
|
||||
@@ -107,7 +112,7 @@ class MyStock(Stock):
|
||||
|
||||
Use `super()` to call the previous version.
|
||||
|
||||
*Caution: Python 2 is different.*
|
||||
*Caution: In Python 2, the syntax was more verbose.*
|
||||
|
||||
```python
|
||||
actual_cost = super(MyStock, self).cost()
|
||||
@@ -115,10 +120,10 @@ actual_cost = super(MyStock, self).cost()
|
||||
|
||||
### `__init__` and inheritance
|
||||
|
||||
If `__init__` is redefined, it is mandatory to initialize the parent.
|
||||
If `__init__` is redefined, it is essential to initialize the parent.
|
||||
|
||||
```python
|
||||
class Stock(object):
|
||||
class Stock:
|
||||
def __init__(self, name, shares, price):
|
||||
self.name = name
|
||||
self.shares = shares
|
||||
@@ -134,14 +139,15 @@ class MyStock(Stock):
|
||||
return self.factor * super().cost()
|
||||
```
|
||||
|
||||
You should call the `init` on the `super` which is the way to call the previous version as shown previously.
|
||||
You should call the `__init__()` method on the `super` which is the
|
||||
way to call the previous version as shown previously.
|
||||
|
||||
### Using Inheritance
|
||||
|
||||
Inheritance is sometimes used to organize related objects.
|
||||
|
||||
```python
|
||||
class Shape(object):
|
||||
class Shape:
|
||||
...
|
||||
|
||||
class Circle(Shape):
|
||||
@@ -151,8 +157,10 @@ class Rectangle(Shape):
|
||||
...
|
||||
```
|
||||
|
||||
Think of a logical hierarchy or taxonomy. However, a more common usage is
|
||||
related to making reusable or extensible code:
|
||||
Think of a logical hierarchy or taxonomy. However, a more common (and
|
||||
practical) usage is related to making reusable or extensible code.
|
||||
For example, a framework might define a base class and instruct you
|
||||
to customize it.
|
||||
|
||||
```python
|
||||
class CustomHandler(TCPHandler):
|
||||
@@ -162,14 +170,14 @@ class CustomHandler(TCPHandler):
|
||||
```
|
||||
|
||||
The base class contains some general purpose code.
|
||||
Your class inherits and customized specific parts. Maybe it plugs into a framework.
|
||||
Your class inherits and customized specific parts.
|
||||
|
||||
### "is a" relationship
|
||||
|
||||
Inheritance establishes a type relationship.
|
||||
|
||||
```python
|
||||
class Shape(object):
|
||||
class Shape:
|
||||
...
|
||||
|
||||
class Circle(Shape):
|
||||
@@ -185,7 +193,8 @@ True
|
||||
>>>
|
||||
```
|
||||
|
||||
*Important: Code that works with the parent is also supposed to work with the child.*
|
||||
*Important: Ideally, any code that worked with instances of the parent
|
||||
class will also work with instances of the child class.*
|
||||
|
||||
### `object` base class
|
||||
|
||||
@@ -198,25 +207,29 @@ class Shape(object):
|
||||
|
||||
`object` is the parent of all objects in Python.
|
||||
|
||||
*Note: it's not technically required in Python 3. If omitted in Python 2, it results in an "old style class" which should be avoided.*
|
||||
*Note: it's not technically required, but you often see it specified
|
||||
as a hold-over from it's required use in Python 2. If omitted, the
|
||||
class still implicitly inherits from `object`.
|
||||
|
||||
### Multiple Inheritance
|
||||
|
||||
You can inherit from multiple classes by specifying them in the definition of the class.
|
||||
|
||||
```python
|
||||
class Mother(object):
|
||||
class Mother:
|
||||
...
|
||||
|
||||
class Father(object):
|
||||
class Father:
|
||||
...
|
||||
|
||||
class Child(Mother, Father):
|
||||
...
|
||||
```
|
||||
|
||||
The class `Child` inherits features from both parents. There are some rather tricky details. Don't do it unless you know what you are doing.
|
||||
We're not going to explore multiple inheritance further in this course.
|
||||
The class `Child` inherits features from both parents. There are some
|
||||
rather tricky details. Don't do it unless you know what you are doing.
|
||||
Some further information will be given in the next section, but we're not
|
||||
going to utilize multiple inheritance further in this course.
|
||||
|
||||
## Exercises
|
||||
|
||||
@@ -271,7 +284,7 @@ following class:
|
||||
```python
|
||||
# tableformat.py
|
||||
|
||||
class TableFormatter(object):
|
||||
class TableFormatter:
|
||||
def headings(self, headers):
|
||||
'''
|
||||
Emit the table headings.
|
||||
@@ -286,10 +299,12 @@ class TableFormatter(object):
|
||||
```
|
||||
|
||||
This class does nothing, but it serves as a kind of design specification for
|
||||
additional classes that will be defined shortly.
|
||||
additional classes that will be defined shortly. A class like this is
|
||||
sometimes called an "abstract base class."
|
||||
|
||||
Modify the `print_report()` function so that it accepts a `TableFormatter` object
|
||||
as input and invokes methods on it to produce the output. For example, like this:
|
||||
Modify the `print_report()` function so that it accepts a
|
||||
`TableFormatter` object as input and invokes methods on it to produce
|
||||
the output. For example, like this:
|
||||
|
||||
```python
|
||||
# report.py
|
||||
@@ -341,12 +356,13 @@ Run this new code:
|
||||
```
|
||||
|
||||
It should immediately crash with a `NotImplementedError` exception. That's not
|
||||
too exciting, but continue to the next part.
|
||||
too exciting, but it's exactly what we expected. Continue to the next part.
|
||||
|
||||
### Exercise 4.6: Using Inheritance to Produce Different Output
|
||||
|
||||
The `TableFormatter` class you defined in part (a) is meant to be extended via inheritance.
|
||||
In fact, that's the whole idea. To illustrate, define a class `TextTableFormatter` like this:
|
||||
The `TableFormatter` class you defined in part (a) is meant to be
|
||||
extended via inheritance. In fact, that's the whole idea. To
|
||||
illustrate, define a class `TextTableFormatter` like this:
|
||||
|
||||
```python
|
||||
# tableformat.py
|
||||
@@ -485,11 +501,11 @@ that expected to use a `TableFormatter` object, it would work no
|
||||
matter what kind of `TableFormatter` you actually gave it. This
|
||||
behavior is sometimes referred to as "polymorphism."
|
||||
|
||||
One potential problem is figuring out how to allow a user to pick
|
||||
out the formatter that they want. Direct use of the class names
|
||||
such as `TextTableFormatter` is often annoying. Thus, you
|
||||
might consider some simplified approach. Perhaps you embed an `if-`statement
|
||||
into the code like this:
|
||||
One potential problem is figuring out how to allow a user to pick out
|
||||
the formatter that they want. Direct use of the class names such as
|
||||
`TextTableFormatter` is often annoying. Thus, you might consider some
|
||||
simplified approach. Perhaps you embed an `if-`statement into the
|
||||
code like this:
|
||||
|
||||
```python
|
||||
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
|
||||
@@ -521,10 +537,10 @@ the `portfolio_report()` function like that the best idea? It might
|
||||
be better to move that code to a general purpose function somewhere
|
||||
else.
|
||||
|
||||
In the `tableformat.py` file, add a
|
||||
function `create_formatter(name)` that allows a user to create a
|
||||
formatter given an output name such as `'txt'`, `'csv'`, or `'html'`.
|
||||
Modify `portfolio_report()` so that it looks like this:
|
||||
In the `tableformat.py` file, add a function `create_formatter(name)`
|
||||
that allows a user to create a formatter given an output name such as
|
||||
`'txt'`, `'csv'`, or `'html'`. Modify `portfolio_report()` so that it
|
||||
looks like this:
|
||||
|
||||
```python
|
||||
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
|
||||
@@ -587,9 +603,25 @@ you to define your own object that inherits from a provided base
|
||||
class. You're then told to fill in various methods that implement
|
||||
various bits of functionality.
|
||||
|
||||
That said, designing object oriented programs can be extremely difficult.
|
||||
For more information, you should probably look for books on the topic of
|
||||
design patterns (although understanding what happened in this exercise
|
||||
will take you pretty far in terms of using most library modules).
|
||||
Another somewhat deeper concept is the idea of "owning your
|
||||
abstractions." In the exercises, we defined *our own class* for
|
||||
formatting a table. You may look at your code and tell yourself "I should
|
||||
just use a formatting library or something that someone else already
|
||||
made instead!" No, you should use BOTH your class and a library.
|
||||
Using your own class promotes loose coupling and is more flexible.
|
||||
As long as your application uses the programming interface of your class,
|
||||
you can change the internal implementation to work in any way that you
|
||||
want. You can write all-custom code. You can use someone's third
|
||||
party package. You swap out one third-party package for a different
|
||||
package when you find a better one. It doesn't matter--none of
|
||||
your application code will break as long as you preserve keep the
|
||||
interface. That's a powerful idea and it's one of the reasons why
|
||||
you might consider inheritance for something like this.
|
||||
|
||||
That said, designing object oriented programs can be extremely
|
||||
difficult. For more information, you should probably look for books
|
||||
on the topic of design patterns (although understanding what happened
|
||||
in this exercise will take you pretty far in terms of using objects in
|
||||
a practically useful way).
|
||||
|
||||
[Contents](../Contents) \| [Previous (4.1 Classes)](01_Class) \| [Next (4.3 Special methods)](03_Special_methods)
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
[Contents](../Contents) \| [Previous (4.2 Inheritance)](02_Inheritance) \| [Next (4.4 Exceptions)](04_Defining_exceptions)
|
||||
|
||||
# 4.3 Special Methods
|
||||
|
||||
Various parts of Python's behavior can be customized via special or magic methods.
|
||||
This section introduces that idea.
|
||||
Various parts of Python's behavior can be customized via special or so-called "magic" methods.
|
||||
This section introduces that idea. In addition dynamic attribute access and bound methods
|
||||
are discussed.
|
||||
|
||||
### Introduction
|
||||
|
||||
Classes may define special methods. These have special meaning to the Python interpreter.
|
||||
They are always preceded and followed by `__`. For example `__init__`.
|
||||
Classes may define special methods. These have special meaning to the
|
||||
Python interpreter. They are always preceded and followed by
|
||||
`__`. For example `__init__`.
|
||||
|
||||
```python
|
||||
class Stock(object):
|
||||
@@ -49,7 +53,8 @@ for programmers.
|
||||
>>>
|
||||
```
|
||||
|
||||
Those functions, `str()` and `repr()`, use a pair of special methods in the class to get the string to be printed.
|
||||
Those functions, `str()` and `repr()`, use a pair of special methods
|
||||
in the class to produce the string to be displayed.
|
||||
|
||||
```python
|
||||
class Date(object):
|
||||
@@ -68,19 +73,19 @@ class Date(object):
|
||||
```
|
||||
|
||||
*Note: The convention for `__repr__()` is to return a string that,
|
||||
when fed to `eval()`., will recreate the underlying object. If this
|
||||
when fed to `eval()`, will recreate the underlying object. If this
|
||||
is not possible, some kind of easily readable representation is used
|
||||
instead.*
|
||||
|
||||
### Special Methods for Mathematics
|
||||
|
||||
Mathematical operators are just calls to special methods.
|
||||
Mathematical operators involve calls to the following methods.
|
||||
|
||||
```python
|
||||
a + b a.__add__(b)
|
||||
a - b a.__sub__(b)
|
||||
a * b a.__mul__(b)
|
||||
a / b a.__div__(b)
|
||||
a / b a.__truediv__(b)
|
||||
a // b a.__floordiv__(b)
|
||||
a % b a.__mod__(b)
|
||||
a << b a.__lshift__(b)
|
||||
@@ -108,7 +113,7 @@ del x[a] x.__delitem__(a)
|
||||
You can use them in your classes.
|
||||
|
||||
```python
|
||||
class Sequence(object):
|
||||
class Sequence:
|
||||
def __len__(self):
|
||||
...
|
||||
def __getitem__(self,a):
|
||||
@@ -204,33 +209,6 @@ x = getattr(obj, 'x', None)
|
||||
|
||||
### Exercise 4.9: Better output for printing objects
|
||||
|
||||
All Python objects have two string representations. The first
|
||||
representation is created by string conversion via `str()`
|
||||
(which is called by `print`). The string representation is
|
||||
usually a nicely formatted version of the object meant for humans.
|
||||
The second representation is a code representation of the object
|
||||
created by `repr()` (or by viewing a value in the
|
||||
interactive shell). The code representation typically shows you the
|
||||
code that you have to type to get the object.
|
||||
|
||||
The two representations of an object are often different. For example,
|
||||
you can see the difference by trying the following:
|
||||
|
||||
```python
|
||||
>>> from datetime import date
|
||||
>>> d = date(2017, 4, 9)
|
||||
>>> print(d) # Nice output
|
||||
2017-04-09
|
||||
>>> print(repr(d)) # Representation output
|
||||
datetime.date(2017, 4, 9)
|
||||
>>> print(f'{d!r}') # Alternate way to get repr() string
|
||||
datetime.date(2017, 4, 9)
|
||||
>>>
|
||||
```
|
||||
|
||||
Both kinds of string conversions can be redefined in a class if it
|
||||
defines the `__str__()` and `__repr__()` methods.
|
||||
|
||||
Modify the `Stock` object that you defined in `stock.py`
|
||||
so that the `__repr__()` method produces more useful output. For
|
||||
example:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
[Contents](../Contents) \| [Previous (4.3 Special methods)](03_Special_methods) \| [Next (5 Object Model)](../05_Object_model/00_Overview)
|
||||
|
||||
# 4.4 Defining Exceptions
|
||||
|
||||
User defined exceptions are defined by classes.
|
||||
@@ -8,6 +10,7 @@ class NetworkError(Exception):
|
||||
```
|
||||
|
||||
**Exceptions always inherit from `Exception`.**
|
||||
|
||||
Usually they are empty classes. Use `pass` for the body.
|
||||
|
||||
You can also make a hierarchy of your exceptions.
|
||||
|
||||
Reference in New Issue
Block a user