Initial commit
This commit is contained in:
195
Exercises/ex6_1.md
Normal file
195
Exercises/ex6_1.md
Normal file
@@ -0,0 +1,195 @@
|
||||
\[ [Index](index.md) | [Exercise 5.6](ex5_6.md) | [Exercise 6.2](ex6_2.md) \]
|
||||
|
||||
# Exercise 6.1
|
||||
|
||||
*Objectives:*
|
||||
|
||||
- Learn more about function argument passing conventions
|
||||
|
||||
*Files Created:* `structure.py`, `stock.py`
|
||||
|
||||
|
||||
**IMPORTANT NOTE**
|
||||
|
||||
This exercise is going to start a long road of rewriting the `stock.py` file in a more
|
||||
sane way. Before doing anything, copy your work in `stock.py` to a new file
|
||||
`orig_stock.py`.
|
||||
|
||||
We're going to recreate the `Stock` class from scratch using some new techniques.
|
||||
Make sure you have your unit tests from link:ex5_4.html[Exercise 5.4] handy. You'll want those.
|
||||
|
||||
If you define a function, you probably already know that it can be
|
||||
called using a mix of positional or keyword arguments. For example:
|
||||
|
||||
```python
|
||||
>>> def foo(x, y, z):
|
||||
return x + y + z
|
||||
|
||||
>>> foo(1, 2, 3)
|
||||
6
|
||||
>>> foo(1, z=3, y=2)
|
||||
6
|
||||
>>>
|
||||
```
|
||||
|
||||
You may also know that you can pass sequences and dictionaries as
|
||||
function arguments using the * and ** syntax. For example:
|
||||
|
||||
```python
|
||||
>>> args = (1, 2, 3)
|
||||
>>> foo(*args)
|
||||
6
|
||||
>>> kwargs = {'y':2, 'z':3 }
|
||||
>>> foo(1,**kwargs)
|
||||
6
|
||||
>>>
|
||||
```
|
||||
|
||||
In addition to that, you can write functions that accept any number of
|
||||
positional or keyword arguments using the * and ** syntax. For
|
||||
example:
|
||||
|
||||
```python
|
||||
>>> def foo(*args):
|
||||
print(args)
|
||||
|
||||
>>> foo(1,2)
|
||||
(1, 2)
|
||||
>>> foo(1,2,3,4,5)
|
||||
(1, 2, 3, 4, 5)
|
||||
>>> foo()
|
||||
()
|
||||
>>>
|
||||
>>> def bar(**kwargs):
|
||||
print(kwargs)
|
||||
|
||||
>>> bar(x=1,y=2)
|
||||
{'y': 2, 'x': 1}
|
||||
>>> bar(x=1,y=2,z=3)
|
||||
{'y': 2, 'x': 1, 'z': 3}
|
||||
>>> bar()
|
||||
{}
|
||||
>>>
|
||||
```
|
||||
|
||||
Variable argument functions are sometimes useful as a technique for
|
||||
reducing or simplifying the amount of code you need to type. In this
|
||||
exercise, we'll explore that idea for simple data structures.
|
||||
|
||||
## (a) Simplified Data Structures
|
||||
|
||||
In earlier exercises, you defined a class representing a stock like
|
||||
this:
|
||||
|
||||
```python
|
||||
class Stock:
|
||||
def __init__(self,name,shares,price):
|
||||
self.name = name
|
||||
self.shares = shares
|
||||
self.price = price
|
||||
```
|
||||
|
||||
Focus on the `__init__()` method---isn't that a lot of
|
||||
code to type each time you want to populate a structure? What if you
|
||||
had to define dozens of such structures in your program?
|
||||
|
||||
In a file `structure.py`, define a base class
|
||||
`Structure` that allows the user to define simple
|
||||
data structures as follows:
|
||||
|
||||
```python
|
||||
class Stock(Structure):
|
||||
_fields = ('name','shares','price')
|
||||
|
||||
class Date(Structure):
|
||||
_fields = ('year', 'month', 'day')
|
||||
```
|
||||
|
||||
The `Structure` class should define an `__init__()`
|
||||
method that takes any number of arguments and which looks for the
|
||||
presence of a `_fields` class variable. Have the method
|
||||
populate the instance from the attribute names in `_fields`
|
||||
and values passed to `__init__()`.
|
||||
|
||||
Here is some sample code to test your implementation:
|
||||
|
||||
```python
|
||||
>>> s = Stock('GOOG',100,490.1)
|
||||
>>> s.name
|
||||
'GOOG'
|
||||
>>> s.shares
|
||||
100
|
||||
>>> s.price
|
||||
490.1
|
||||
>>> s = Stock('AA',50)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Expected 3 arguments
|
||||
>>>
|
||||
```
|
||||
|
||||
## (b) Making a Useful Representation
|
||||
|
||||
Modify the `Structure` class so that it produces a nice
|
||||
representation when `repr()` is used. For example:
|
||||
|
||||
```python
|
||||
>>> s = Stock('GOOG', 100, 490.1)
|
||||
>>> s
|
||||
Stock('GOOG',100,490.1)
|
||||
>>>
|
||||
```
|
||||
|
||||
## (c) Restricting Attribute Names
|
||||
|
||||
Give the `Structure` class a `__setattr__()` method that restricts
|
||||
the allowed set of attributes to those listed in the `_fields` variable.
|
||||
However, it should still allow any "private" attribute (e.g., name starting
|
||||
with `_` to be set).
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
>>> s = Stock('GOOG',100,490.1)
|
||||
>>> s.shares = 50
|
||||
>>> s.share = 50
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
File "structure.py", line 13, in __setattr__
|
||||
raise AttributeError('No attribute %s' % name)
|
||||
AttributeError: No attribute share
|
||||
>>> s._shares = 100 # Private attribute. OK
|
||||
>>>
|
||||
```
|
||||
|
||||
## (d) Starting Over
|
||||
|
||||
Create a new file `stock.py` (or delete all of your previous code). Start over by defining `Stock` as follows:
|
||||
|
||||
```python
|
||||
# stock.py
|
||||
|
||||
from structure import Structure
|
||||
|
||||
class Stock(Structure):
|
||||
_fields = ('name', 'shares', 'price')
|
||||
|
||||
@property
|
||||
def cost(self):
|
||||
return self.shares * self.price
|
||||
|
||||
def sell(self, nshares):
|
||||
self.shares -= nshares
|
||||
```
|
||||
|
||||
Once you've done this, run your `teststock.py` unit tests. You should get a lot of failures, but at least a
|
||||
handful of the tests should pass.
|
||||
|
||||
\[ [Solution](soln6_1.md) | [Index](index.md) | [Exercise 5.6](ex5_6.md) | [Exercise 6.2](ex6_2.md) \]
|
||||
|
||||
----
|
||||
`>>>` Advanced Python Mastery
|
||||
`...` A course by [dabeaz](https://www.dabeaz.com)
|
||||
`...` Copyright 2007-2023
|
||||
|
||||
. This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)
|
||||
Reference in New Issue
Block a user