diff --git a/Notes/01_Introduction/02_Hello_world.md b/Notes/01_Introduction/02_Hello_world.md index 784a850..f2889cf 100644 --- a/Notes/01_Introduction/02_Hello_world.md +++ b/Notes/01_Introduction/02_Hello_world.md @@ -291,10 +291,8 @@ The `if` statement is used to execute a conditional: ```python if a > b: - # a is greater than b print('Computer says no') else: - # a is lower or equal to b print('Computer says yes') ``` @@ -302,13 +300,10 @@ You can check for multiple conditions by adding extra checks using `elif`. ```python if a > b: - # a is greater than b print('Computer says no') elif a == b: - # a is equal to b print('Computer says yes') else: - # a is lower to b print('Computer says maybe') ``` diff --git a/Notes/01_Introduction/06_Files.md b/Notes/01_Introduction/06_Files.md index fbcf449..4147af2 100644 --- a/Notes/01_Introduction/06_Files.md +++ b/Notes/01_Introduction/06_Files.md @@ -66,7 +66,7 @@ with open(filename, 'rt') as file: # Process the line ``` -### Common Idioms for Write to a File +### Common Idioms for Writing to a File Write string data. diff --git a/Notes/02_Working_with_data/07_Objects.md b/Notes/02_Working_with_data/07_Objects.md index 72d1cf8..28426a2 100644 --- a/Notes/02_Working_with_data/07_Objects.md +++ b/Notes/02_Working_with_data/07_Objects.md @@ -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. ![Shallow copy](shallow.png) diff --git a/Notes/04_Classes_objects/01_Class.md b/Notes/04_Classes_objects/01_Class.md index 7523c58..e57ede4 100644 --- a/Notes/04_Classes_objects/01_Class.md +++ b/Notes/04_Classes_objects/01_Class.md @@ -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`. diff --git a/Notes/04_Classes_objects/02_Inheritance.md b/Notes/04_Classes_objects/02_Inheritance.md index 7b9b5b6..5638abb 100644 --- a/Notes/04_Classes_objects/02_Inheritance.md +++ b/Notes/04_Classes_objects/02_Inheritance.md @@ -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) diff --git a/Notes/04_Classes_objects/03_Special_methods.md b/Notes/04_Classes_objects/03_Special_methods.md index 2f24bbb..301535e 100644 --- a/Notes/04_Classes_objects/03_Special_methods.md +++ b/Notes/04_Classes_objects/03_Special_methods.md @@ -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: diff --git a/Notes/04_Classes_objects/04_Defining_exceptions.md b/Notes/04_Classes_objects/04_Defining_exceptions.md index a235fe9..9e4f115 100644 --- a/Notes/04_Classes_objects/04_Defining_exceptions.md +++ b/Notes/04_Classes_objects/04_Defining_exceptions.md @@ -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.