Added solution code

This commit is contained in:
David Beazley
2020-05-27 17:03:35 -05:00
parent 960d4fa2fa
commit 5b6f15db17
136 changed files with 5828 additions and 350 deletions

View File

@@ -363,16 +363,22 @@ Frameworks / libraries sometimes use it for advanced features involving composit
## Exercises
In Section 4, you defined a class `Stock` that represented a holding of stock.
In this exercise, we will use that class.
In this exercise, we will use that class. Restart the interpreter and make a
few instances:
```python
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> goog = Stock('GOOG',100,490.10)
>>> ibm = Stock('IBM',50, 91.23)
>>>
```
### Exercise 5.1: Representation of Instances
At the interactive shell, inspect the underlying dictionaries of the two instances you created:
```python
>>> from stock import Stock
>>> goog = Stock('GOOG',100,490.10)
>>> ibm = Stock('IBM',50, 91.23)
>>> goog.__dict__
... look at the output ...
>>> ibm.__dict__
@@ -393,12 +399,14 @@ Try setting a new attribute on one of the above instances:
>>>
```
In the above output, youll notice that the `goog` instance has a attribute `date` whereas the `ibm` instance does not.
In the above output, you'll notice that the `goog` instance has a
attribute `date` whereas the `ibm` instance does not. It is important
to note that Python really doesn't place any restrictions on
attributes. For example, the attributes of an instance are not
limited to those set up in the `__init__()` method.
It is important to note that Python really doesnt place any restrictions on attributes.
The attributes of an instance are not limited to those set up in the `__init__()` method.
Instead of setting an attribute, try placing a new value directly into the `__dict__` object:
Instead of setting an attribute, try placing a new value directly into
the `__dict__` object:
```python
>>> goog.__dict__['time'] = '9:45am'
@@ -408,21 +416,22 @@ Instead of setting an attribute, try placing a new value directly into the `__di
```
Here, you really notice the fact that an instance is just a layer on
top of a dictionary. *Note: it should be emphasized that direct
manipulation of the dictionary is uncommonyou should always write
your code to use the (.) syntax.*
top of a dictionary. Note: it should be emphasized that direct
manipulation of the dictionary is uncommon--you should always write
your code to use the (.) syntax.
### Exercise 5.3: The role of classes
The definitions that make up a class definition are shared by all instances of that class.
Notice, that all instances have a link back to their associated class:
The definitions that make up a class definition are shared by all
instances of that class. Notice, that all instances have a link back
to their associated class:
```python
>>> goog.__class__
... look at output ...
>>> ibm.__class__
... look at output ...
>>>
>>>
```
Try calling a method on the instances:
@@ -435,7 +444,7 @@ Try calling a method on the instances:
>>>
```
Notice that the name *cost* is not defined in either `goog.__dict__`
Notice that the name 'cost' is not defined in either `goog.__dict__`
or `ibm.__dict__`. Instead, it is being supplied by the class
dictionary. Try this:
@@ -455,7 +464,9 @@ Try calling the `cost()` method directly through the dictionary:
>>>
```
Notice how you are calling the function defined in the class definition and how the `self` argument gets the instance.
Notice how you are calling the function defined in the class
definition and how the `self` argument gets the instance.
Try adding a new attribute to the `Stock` class:
```python
@@ -482,21 +493,22 @@ However, notice that it is not part of the instance dictionary:
```
The reason you can access the `foo` attribute on instances is that
Python always checks the class dictionary if it cant find something
on the instance itself.
Python always checks the class dictionary if it can't find something
on the instance itself.
This part of the exercise illustrates something known as a class
Note: This part of the exercise illustrates something known as a class
variable. Suppose, for instance, you have a class like this:
```python
class Foo(object):
a = 13 # Class variable
def __init__(self,b):
self.b = b # Instance variable
a = 13 # Class variable
def __init__(self,b):
self.b = b # Instance variable
```
In this class, the variable `a`, assigned in the body of the class itself, is a *class variable*.
It is shared by all of the instances that get created.
In this class, the variable `a`, assigned in the body of the
class itself, is a "class variable." It is shared by all of the
instances that get created. For example:
```python
>>> f = Foo(10)
@@ -517,10 +529,10 @@ It is shared by all of the instances that get created.
>>>
```
### Exercise 5.4: Bound Methods
### Exercise 5.4: Bound methods
A subtle feature of Python is that invoking a method actually involves
two steps and something known as a bound method.
two steps and something known as a bound method. For example:
```python
>>> s = goog.sell
@@ -529,11 +541,12 @@ two steps and something known as a bound method.
>>> s(25)
>>> goog.shares
75
>>>
>>>
```
Bound methods actually contain all of the pieces needed to call a method.
For instance, they keep a record of the function implementing the method:
Bound methods actually contain all of the pieces needed to call a
method. For instance, they keep a record of the function implementing
the method:
```python
>>> s.__func__
@@ -549,8 +562,8 @@ This is the same value as found in the `Stock` dictionary.
>>>
```
Take a close look at both references do `0x10049af50`. They are both the same in `s` and `Stock.__dict__['sell']`.
Bound methods also record the instance, which is the `self` argument.
Bound methods also record the instance, which is the `self`
argument.
```python
>>> s.__self__
@@ -558,8 +571,8 @@ Stock('GOOG',75,490.1)
>>>
```
When you invoke the function using `()` all of the pieces come together.
For example, calling `s(25)` actually does this:
When you invoke the function using `()` all of the pieces come
together. For example, calling `s(25)` actually does this:
```python
>>> s.__func__(s.__self__, 25) # Same as s(25)
@@ -572,7 +585,7 @@ For example, calling `s(25)` actually does this:
Make a new class that inherits from `Stock`.
```python
```
>>> class NewStock(Stock):
def yow(self):
print('Yow!')
@@ -603,7 +616,7 @@ they will be searched for attributes.
>>>
```
Heres how the `cost()` method of instance `n` above would be found:
Here's how the `cost()` method of instance `n` above would be found:
```python
>>> for cls in n.__class__.__mro__:

View File

@@ -252,10 +252,10 @@ day-to-day coding.
## Exercises
### Exercise 5.6: Simple properties
### Exercise 5.6: Simple Properties
Properties are a useful way to add "computed attributes" to an object.
In Exercise 4.1, you created an object `Stock`. Notice that on your
In `stock.py`, you created an object `Stock`. Notice that on your
object there is a slight inconsistency in how different kinds of data
are extracted:
@@ -271,17 +271,22 @@ are extracted:
>>>
```
Specifically, notice how you have to add the extra `()` to `cost` because it is a method.
You can get rid of the extra `()` on `cost()` if you turn it into a property.
Specifically, notice how you have to add the extra () to `cost` because it is a method.
You can get rid of the extra () on `cost()` if you turn it into a property.
Take your `Stock` class and modify it so that the cost calculation works like this:
```python
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> s = Stock('GOOG', 100, 490.1)
>>> s.cost
49010.0
>>>
```
Try calling `s.cost()` as a function and observe that it doesnt work now that `cost` has been defined as a property.
Try calling `s.cost()` as a function and observe that it
doesn't work now that `cost` has been defined as a property.
```python
>>> s.cost()
@@ -289,14 +294,19 @@ Try calling `s.cost()` as a function and observe that it doesnt work now that
>>>
```
Making this change will likely break your earlier `pcost.py` program.
You might need to go back and get rid of the `()` on the `cost()` method.
### Exercise 5.7: Properties and Setters
Modify the `shares` attribute so that the value is stored in a private
attribute and that a pair of property functions are used to ensure
that it is always set to an integer value.
Here is an example of the expected behavior:
Modify the `shares` attribute so that the value is stored in a
private attribute and that a pair of property functions are used to ensure
that it is always set to an integer value. Here is an example of the expected
behavior:
```python
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> s = Stock('GOOG',100,490.10)
>>> s.shares = 50
>>> s.shares = 'a lot'
@@ -306,12 +316,13 @@ TypeError: expected an integer
>>>
```
### Exercise 5.8: Adding slots
### Exercise 5.8: Adding slots
Modify the `Stock` class so that it has a `__slots__` attribute.
Then, verify that new attributes cant be added:
Modify the `Stock` class so that it has a `__slots__` attribute. Then,
verify that new attributes can't be added:
```python
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> s = Stock('GOOG', 100, 490.10)
>>> s.name
@@ -321,8 +332,9 @@ Then, verify that new attributes cant be added:
>>>
```
When you use `__slots__`, Python actually uses a more efficient internal representation of objects.
What happens if you try to inspect the underlying dictionary of `s` above?
When you use `__slots__`, Python actually uses a more efficient
internal representation of objects. What happens if you try to
inspect the underlying dictionary of `s` above?
```python
>>> s.__dict__