[ Index | Exercise 5.6 | Exercise 6.2 ]
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 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:
>>> 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:
>>> 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:
>>> 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.
In earlier exercises, you defined a class representing a stock like this:
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:
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:
>>> 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
>>>
Modify the Structure
class so that it produces a nice
representation when repr()
is used. For example:
>>> s = Stock('GOOG', 100, 490.1)
>>> s
Stock('GOOG',100,490.1)
>>>
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:
>>> 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
>>>
Create a new file stock.py
(or delete all of your previous code). Start over by defining Stock
as follows:
# 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 | Index | Exercise 5.6 | Exercise 6.2 ]
>>>
Advanced Python Mastery
...
A course by dabeaz
...
Copyright 2007-2023
. This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License