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

@@ -291,10 +291,8 @@ The `if` statement is used to execute a conditional:
```python ```python
if a > b: if a > b:
# a is greater than b
print('Computer says no') print('Computer says no')
else: else:
# a is lower or equal to b
print('Computer says yes') print('Computer says yes')
``` ```
@@ -302,13 +300,10 @@ You can check for multiple conditions by adding extra checks using `elif`.
```python ```python
if a > b: if a > b:
# a is greater than b
print('Computer says no') print('Computer says no')
elif a == b: elif a == b:
# a is equal to b
print('Computer says yes') print('Computer says yes')
else: else:
# a is lower to b
print('Computer says maybe') print('Computer says maybe')
``` ```

View File

@@ -66,7 +66,7 @@ with open(filename, 'rt') as file:
# Process the line # Process the line
``` ```
### Common Idioms for Write to a File ### Common Idioms for Writing to a File
Write string data. Write string data.

View File

@@ -139,7 +139,7 @@ True
>>> >>>
``` ```
For example, the inner list `[100, 101]` is being shared. For example, the inner list `[100, 101, 102]` is being shared.
This is known as a shallow copy. Here is a picture. This is known as a shallow copy. Here is a picture.
![Shallow copy](shallow.png) ![Shallow copy](shallow.png)

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 # 4.1 Classes
This section introduces the class statement and the idea of creating new objects.
### Object Oriented (OO) programming ### 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: An *object* consists of:
* Data. Attributes * 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. You have already been using some OO during this course.
For example with Lists. For example, manipulating a list.
```python ```python
>>> nums = [1, 2, 3] >>> nums = [1, 2, 3]
@@ -24,14 +29,14 @@ For example with Lists.
`nums` is an *instance* of a list. `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 ### The `class` statement
Use the `class` statement to define a new object. Use the `class` statement to define a new object.
```python ```python
class Player(object): class Player:
def __init__(self, x, y): def __init__(self, x, y):
self.x = x self.x = x
self.y = y 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 ### Instance Data
@@ -77,7 +83,7 @@ Each instance has its own local data.
This data is initialized by the `__init__()`. This data is initialized by the `__init__()`.
```python ```python
class Player(object): class Player:
def __init__(self, x, y): def __init__(self, x, y):
# Any value stored on `self` is instance data # Any value stored on `self` is instance data
self.x = x 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. Instance methods are functions applied to instances of an object.
```python ```python
class Player(object): class Player:
... ...
# `move` is a method # `move` is a method
def move(self, dx, dy): 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 By convention, the instance is called `self`. However, the actual name
used is unimportant. The object is always passed as the first 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`. `self`.
### Class Scoping ### Class Scoping
Classes do not define a scope. Classes do not define a scope of names.
```python ```python
class Player(object): class Player:
... ...
def move(self, dx, dy): def move(self, dx, dy):
self.x += dx self.x += dx
@@ -132,13 +138,15 @@ class Player(object):
self.move(-amt, 0) # YES. Calls method `move` from above. 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 ## Exercises
Note: For this exercise you want to have fully working code from earlier Starting with this set of exercises, we start to make a series of
exercises. If things are broken look at the solution code for Exercise 3.18. changes to existing code from previous sctions. It is critical that
You can find this code in the `Solutions/3_18` directory. 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 ### 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 One thing to emphasize here is that the class `Stock` acts like a
factory for creating instances of objects. Basically, you call factory for creating instances of objects. Basically, you call
it as a function and it creates a new object for you. Also, it needs it as a function and it creates a new object for you. Also, it must
to be emphasized that each object is distinct---they each have their be emphasized that each object is distinct---they each have their
own data that is separate from other objects that have been created. 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 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 ### Exercise 4.3: Creating a list of instances
Try these steps to make a list of Stock instances and compute the total Try these steps to make a list of Stock instances from a list of
cost: dictionaries. Then compute the total cost:
```python ```python
>>> import fileparse >>> import fileparse
@@ -258,10 +266,11 @@ cost:
### Exercise 4.4: Using your class ### Exercise 4.4: Using your class
Modify the `read_portfolio()` function in the `report.py` program so that it Modify the `read_portfolio()` function in the `report.py` program so
reads a portfolio into a list of `Stock` instances. Once you have done that, that it reads a portfolio into a list of `Stock` instances as just
fix all of the code in `report.py` and `pcost.py` so that it works with shown in Exercise 4.3. Once you have done that, fix all of the code
`Stock` instances instead of dictionaries. 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 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`. 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 # 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 ### Introduction
@@ -10,13 +13,13 @@ Inheritance is used to specialize existing objects:
class Parent: 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 new class `Child` is called a derived class or subclass. The
The `Parent` class is known as base class or superclass. `Parent` class is known as base class or superclass. `Parent` is
`Parent` is specified in `()` after the class name, `class Child(Parent):`. specified in `()` after the class name, `class Child(Parent):`.
### Extending ### Extending
@@ -33,7 +36,7 @@ In the end you are **extending existing code**.
Suppose that this is your starting class: Suppose that this is your starting class:
```python ```python
class Stock(object): class Stock:
def __init__(self, name, shares, price): def __init__(self, name, shares, price):
self.name = name self.name = name
self.shares = shares self.shares = shares
@@ -61,9 +64,11 @@ Usage example.
```python ```python
>>> s = MyStock('GOOG', 100, 490.1) >>> s = MyStock('GOOG', 100, 490.1)
>>> s.sell(25) >>> s.sell(25)
>>> s.shares 75 >>> s.shares
75
>>> s.panic() >>> 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 ## Overriding
Sometimes a class extends an existing method, but it wants to use the original implementation. Sometimes a class extends an existing method, but it wants to use the
For this, use `super()`: original implementation inside the redefinition. For this, use `super()`:
```python ```python
class Stock(object): class Stock:
... ...
def cost(self): def cost(self):
return self.shares * self.price return self.shares * self.price
@@ -107,7 +112,7 @@ class MyStock(Stock):
Use `super()` to call the previous version. Use `super()` to call the previous version.
*Caution: Python 2 is different.* *Caution: In Python 2, the syntax was more verbose.*
```python ```python
actual_cost = super(MyStock, self).cost() actual_cost = super(MyStock, self).cost()
@@ -115,10 +120,10 @@ actual_cost = super(MyStock, self).cost()
### `__init__` and inheritance ### `__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 ```python
class Stock(object): class Stock:
def __init__(self, name, shares, price): def __init__(self, name, shares, price):
self.name = name self.name = name
self.shares = shares self.shares = shares
@@ -134,14 +139,15 @@ class MyStock(Stock):
return self.factor * super().cost() 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 ### Using Inheritance
Inheritance is sometimes used to organize related objects. Inheritance is sometimes used to organize related objects.
```python ```python
class Shape(object): class Shape:
... ...
class Circle(Shape): class Circle(Shape):
@@ -151,8 +157,10 @@ class Rectangle(Shape):
... ...
``` ```
Think of a logical hierarchy or taxonomy. However, a more common usage is Think of a logical hierarchy or taxonomy. However, a more common (and
related to making reusable or extensible code: 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 ```python
class CustomHandler(TCPHandler): class CustomHandler(TCPHandler):
@@ -162,14 +170,14 @@ class CustomHandler(TCPHandler):
``` ```
The base class contains some general purpose code. 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 ### "is a" relationship
Inheritance establishes a type relationship. Inheritance establishes a type relationship.
```python ```python
class Shape(object): class Shape:
... ...
class Circle(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 ### `object` base class
@@ -198,25 +207,29 @@ class Shape(object):
`object` is the parent of all objects in Python. `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 ### Multiple Inheritance
You can inherit from multiple classes by specifying them in the definition of the class. You can inherit from multiple classes by specifying them in the definition of the class.
```python ```python
class Mother(object): class Mother:
... ...
class Father(object): class Father:
... ...
class Child(Mother, 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. The class `Child` inherits features from both parents. There are some
We're not going to explore multiple inheritance further in this course. 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 ## Exercises
@@ -271,7 +284,7 @@ following class:
```python ```python
# tableformat.py # tableformat.py
class TableFormatter(object): class TableFormatter:
def headings(self, headers): def headings(self, headers):
''' '''
Emit the table headings. 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 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 Modify the `print_report()` function so that it accepts a
as input and invokes methods on it to produce the output. For example, like this: `TableFormatter` object as input and invokes methods on it to produce
the output. For example, like this:
```python ```python
# report.py # report.py
@@ -341,12 +356,13 @@ Run this new code:
``` ```
It should immediately crash with a `NotImplementedError` exception. That's not 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 ### Exercise 4.6: Using Inheritance to Produce Different Output
The `TableFormatter` class you defined in part (a) is meant to be extended via inheritance. The `TableFormatter` class you defined in part (a) is meant to be
In fact, that's the whole idea. To illustrate, define a class `TextTableFormatter` like this: extended via inheritance. In fact, that's the whole idea. To
illustrate, define a class `TextTableFormatter` like this:
```python ```python
# tableformat.py # 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 matter what kind of `TableFormatter` you actually gave it. This
behavior is sometimes referred to as "polymorphism." behavior is sometimes referred to as "polymorphism."
One potential problem is figuring out how to allow a user to pick One potential problem is figuring out how to allow a user to pick out
out the formatter that they want. Direct use of the class names the formatter that they want. Direct use of the class names such as
such as `TextTableFormatter` is often annoying. Thus, you `TextTableFormatter` is often annoying. Thus, you might consider some
might consider some simplified approach. Perhaps you embed an `if-`statement simplified approach. Perhaps you embed an `if-`statement into the
into the code like this: code like this:
```python ```python
def portfolio_report(portfoliofile, pricefile, fmt='txt'): 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 be better to move that code to a general purpose function somewhere
else. else.
In the `tableformat.py` file, add a In the `tableformat.py` file, add a function `create_formatter(name)`
function `create_formatter(name)` that allows a user to create a that allows a user to create a formatter given an output name such as
formatter given an output name such as `'txt'`, `'csv'`, or `'html'`. `'txt'`, `'csv'`, or `'html'`. Modify `portfolio_report()` so that it
Modify `portfolio_report()` so that it looks like this: looks like this:
```python ```python
def portfolio_report(portfoliofile, pricefile, fmt='txt'): 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 class. You're then told to fill in various methods that implement
various bits of functionality. various bits of functionality.
That said, designing object oriented programs can be extremely difficult. Another somewhat deeper concept is the idea of "owning your
For more information, you should probably look for books on the topic of abstractions." In the exercises, we defined *our own class* for
design patterns (although understanding what happened in this exercise formatting a table. You may look at your code and tell yourself "I should
will take you pretty far in terms of using most library modules). 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) [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 # 4.3 Special Methods
Various parts of Python's behavior can be customized via special or magic methods. Various parts of Python's behavior can be customized via special or so-called "magic" methods.
This section introduces that idea. This section introduces that idea. In addition dynamic attribute access and bound methods
are discussed.
### Introduction ### Introduction
Classes may define special methods. These have special meaning to the Python interpreter. Classes may define special methods. These have special meaning to the
They are always preceded and followed by `__`. For example `__init__`. Python interpreter. They are always preceded and followed by
`__`. For example `__init__`.
```python ```python
class Stock(object): 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 ```python
class Date(object): class Date(object):
@@ -68,19 +73,19 @@ class Date(object):
``` ```
*Note: The convention for `__repr__()` is to return a string that, *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 is not possible, some kind of easily readable representation is used
instead.* instead.*
### Special Methods for Mathematics ### Special Methods for Mathematics
Mathematical operators are just calls to special methods. Mathematical operators involve calls to the following methods.
```python ```python
a + b a.__add__(b) a + b a.__add__(b)
a - b a.__sub__(b) a - b a.__sub__(b)
a * b a.__mul__(b) a * b a.__mul__(b)
a / b a.__div__(b) a / b a.__truediv__(b)
a // b a.__floordiv__(b) a // b a.__floordiv__(b)
a % b a.__mod__(b) a % b a.__mod__(b)
a << b a.__lshift__(b) a << b a.__lshift__(b)
@@ -108,7 +113,7 @@ del x[a] x.__delitem__(a)
You can use them in your classes. You can use them in your classes.
```python ```python
class Sequence(object): class Sequence:
def __len__(self): def __len__(self):
... ...
def __getitem__(self,a): def __getitem__(self,a):
@@ -204,33 +209,6 @@ x = getattr(obj, 'x', None)
### Exercise 4.9: Better output for printing objects ### 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` Modify the `Stock` object that you defined in `stock.py`
so that the `__repr__()` method produces more useful output. For so that the `__repr__()` method produces more useful output. For
example: 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 # 4.4 Defining Exceptions
User defined exceptions are defined by classes. User defined exceptions are defined by classes.
@@ -8,6 +10,7 @@ class NetworkError(Exception):
``` ```
**Exceptions always inherit from `Exception`.** **Exceptions always inherit from `Exception`.**
Usually they are empty classes. Use `pass` for the body. Usually they are empty classes. Use `pass` for the body.
You can also make a hierarchy of your exceptions. You can also make a hierarchy of your exceptions.