\[ [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 [Exercise 5.4](ex5_4.md) 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  

![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)
