\[ [Index](index.md) | [Exercise 9.2](ex9_2.md) | [Exercise 9.4](ex9_4.md) \]

# Exercise 9.3

*Objectives:*

- Learn about controlling symbols and combining submodules
- Learn about module splitting

One potentially annoying aspect of packages is that they complicate
import statements.   For example, in the `stock.py` program, you now
have import statements such as the following:

```python
from structly.structure import Structure
from structly.reader import read_csv_as_instances
from structly.tableformat import create_formatter, print_table
```

If the package is meant to be used as a unified whole, it might be
more sane (and easier) to consolidate everything into a single top
level package.   Let's do that:

## (a) Controlling Exported Symbols

Modify all of the submodules in the `structly` package so that they explicitly
define an `__all__` variable which exports selected symbols.  Specifically:

- `structure.py` should export `Structure`
- `reader.py` should export all of the various `read_csv_as_*()` functions
- `tableformat.py` exports `create_formatter()` and `print_table()`

Now, in the `__init__.py` file, unify all of the submodules like this:

```python
# structly/__init__.py

from .structure import *
from .reader import *
from .tableformat import *
```

Once you have done this, you should be able to import everything from 
a single logical module:

```python
# stock.py

from structly import Structure

class Stock(Structure):
    name = String()
    shares = PositiveInteger()
    price = PositiveFloat()
    
    @property
    def cost(self):
        return self.shares * self.price

    def sell(self, nshares: PositiveInteger):
        self.shares -= nshares

if __name__ == '__main__':
    from structly import read_csv_as_instances, create_formatter, print_table
    portfolio = read_csv_as_instances('Data/portfolio.csv', Stock)
    formatter = create_formatter('text')
    print_table(portfolio, ['name','shares','price'], formatter)
```

## (b) Exporting Everything

In the `structly/__init__.py`, define an `__all__` variable that contains all
exported symbols.   Once you've done this, you should be able to simplify the
`stock.py` file further:

```python
# stock.py

from structly import *

class Stock(Structure):
    name = String()
    shares = PositiveInteger()
    price = PositiveFloat()
    
    @property
    def cost(self):
        return self.shares * self.price

    def sell(self, nshares: PositiveInteger):
        self.shares -= nshares

if __name__ == '__main__':
    portfolio = read_csv_as_instances('Data/portfolio.csv', Stock)
    formatter = create_formatter('text')
    print_table(portfolio, ['name','shares','price'], formatter)
```

As an aside, use of the `from module import *` statement is generally frowned upon
the Python community--especially if you're not sure what you're doing.  That said,
there are situations where it often makes sense. For example, if a package defines
a large number of commonly used symbols or constants it might be useful to use it.


## (c) Module Splitting

The file `structly/tableformat.py` contains code for creating tables in different
formats. Specifically:

- A `TableFormatter` base class.
- A `TextTableFormatter` class.
- A `CSVTableFormatter` class.
- A `HTMLTableFormatter` class.

Instead of having all of these classes in a single `.py`
file, maybe it would make sense to move each concrete formatter to
its own file.  To do this, we're going to split the `tableformat.py`
file into parts.  Follow these instructions carefully:

First, remove the `structly/__pycache__` directory. 

```
% cd structly
% rm -rf __pycache__
```

Next, create the directory `structly/tableformat`.  This directory
must have exactly the same name as the module it is replacing
(`tableformat.py`).

```
bash % mkdir tableformat
bash %
```

Move the original `tableformat.py` file into the new
`tableformat` directory and rename it to `formatter.py`. 

```
bash % mv tableformat.py tableformat/formatter.py
bash %
```

In the `tableformat` directory, split the
`tableformat.py` code into the following files and directories:

- `formatter.py` - Contains the `TableFormatter` base class, mixins, and various functions.
- `formats/text.py` - Contains the `TextTableFormatter` class.
- `formats/csv.py` - Contains the `CSVTableFormatter` class.
- `formats/html.py` - Contains the `HTMLTableFormatter` class.

Add an `__init__.py` file to the `tableformat/` and `tableformat/formats`
directories.  Have the `tableformat/__init__.py` export the same
symbols that the original `tableformat.py` file exported.

After you have made all of these changes, you should have a package
structure that looks like this:

```
structly/
      __init__.py
      validate.py
      reader.py
      structure.py
      tableformat/
           __init__.py
           formatter.py
           formats/
               __init__.py
               text.py
               csv.py
               html.py
```

To users, everything should work exactly as it did before.  For example, your
prior `stock.py` file should work:

```python
# stock.py

from structly import *

class Stock(Structure):
    name = String()
    shares = PositiveInteger()
    price = PositiveFloat()
    
    @property
    def cost(self):
        return self.shares * self.price

    def sell(self, nshares):
        self.shares -= nshares

if __name__ == '__main__':
    portfolio = read_csv_as_instances('Data/portfolio.csv', Stock)
    formatter = create_formatter('text')
    print_table(portfolio, ['name','shares','price'], formatter)
```

\[ [Solution](soln9_3.md) | [Index](index.md) | [Exercise 9.2](ex9_2.md) | [Exercise 9.4](ex9_4.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/)
