[ Index | Exercise 9.2 | Exercise 9.4 ]
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:
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:
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 exportStructure
reader.py
should export all of the variousread_csv_as_*()
functionstableformat.py
exportscreate_formatter()
andprint_table()
Now, in the __init__.py
file, unify all of the submodules like this:
# 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:
# 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)
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:
# 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.
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 theTableFormatter
base class, mixins, and various functions.formats/text.py
- Contains theTextTableFormatter
class.formats/csv.py
- Contains theCSVTableFormatter
class.formats/html.py
- Contains theHTMLTableFormatter
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:
# 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 | Index | Exercise 9.2 | Exercise 9.4 ]
>>>
Advanced Python Mastery
...
A course by dabeaz
...
Copyright 2007-2023
. This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License