This commit is contained in:
David Beazley
2020-05-28 11:25:14 -05:00
parent 7a1cccb847
commit cc157243fa
7 changed files with 131 additions and 114 deletions

View File

@@ -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`.

View File

@@ -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)

View File

@@ -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:

View File

@@ -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.