python-mastery/Exercises/ex9_3.md

209 lines
5.9 KiB
Markdown
Raw Normal View History

2023-07-17 03:21:00 +02:00
\[ [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/)