209 lines
5.9 KiB
Markdown
209 lines
5.9 KiB
Markdown
|
\[ [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
|
||
|
|
||
|
. This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)
|