Controlling Symbols and Combining Submodules

PythonPythonBeginner
Practice Now

This tutorial is from open-source community. Access the source code

Introduction

Objectives:

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

Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("`Python`")) -.-> python/BasicConceptsGroup(["`Basic Concepts`"]) linux(("`Linux`")) -.-> linux/FileandDirectoryManagementGroup(["`File and Directory Management`"]) linux(("`Linux`")) -.-> linux/BasicFileOperationsGroup(["`Basic File Operations`"]) python(("`Python`")) -.-> python/ControlFlowGroup(["`Control Flow`"]) python(("`Python`")) -.-> python/DataStructuresGroup(["`Data Structures`"]) python(("`Python`")) -.-> python/FunctionsGroup(["`Functions`"]) python(("`Python`")) -.-> python/ModulesandPackagesGroup(["`Modules and Packages`"]) python(("`Python`")) -.-> python/ObjectOrientedProgrammingGroup(["`Object-Oriented Programming`"]) python(("`Python`")) -.-> python/ErrorandExceptionHandlingGroup(["`Error and Exception Handling`"]) python/BasicConceptsGroup -.-> python/comments("`Comments`") linux/FileandDirectoryManagementGroup -.-> linux/cd("`Directory Changing`") linux/FileandDirectoryManagementGroup -.-> linux/mkdir("`Directory Creating`") linux/BasicFileOperationsGroup -.-> linux/mv("`File Moving/Renaming`") linux/BasicFileOperationsGroup -.-> linux/rm("`File Removing`") python/BasicConceptsGroup -.-> python/booleans("`Booleans`") python/ControlFlowGroup -.-> python/conditional_statements("`Conditional Statements`") python/ControlFlowGroup -.-> python/for_loops("`For Loops`") python/ControlFlowGroup -.-> python/list_comprehensions("`List Comprehensions`") python/DataStructuresGroup -.-> python/lists("`Lists`") python/DataStructuresGroup -.-> python/tuples("`Tuples`") python/FunctionsGroup -.-> python/function_definition("`Function Definition`") python/FunctionsGroup -.-> python/default_arguments("`Default Arguments`") python/ModulesandPackagesGroup -.-> python/importing_modules("`Importing Modules`") python/ModulesandPackagesGroup -.-> python/using_packages("`Using Packages`") python/ModulesandPackagesGroup -.-> python/standard_libraries("`Common Standard Libraries`") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("`Classes and Objects`") python/ObjectOrientedProgrammingGroup -.-> python/constructor("`Constructor`") python/ObjectOrientedProgrammingGroup -.-> python/encapsulation("`Encapsulation`") python/ErrorandExceptionHandlingGroup -.-> python/raising_exceptions("`Raising Exceptions`") python/FunctionsGroup -.-> python/build_in_functions("`Build-in Functions`") subgraph Lab Skills python/comments -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} linux/cd -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} linux/mkdir -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} linux/mv -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} linux/rm -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/booleans -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/conditional_statements -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/for_loops -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/list_comprehensions -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/lists -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/tuples -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/function_definition -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/default_arguments -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/importing_modules -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/using_packages -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/standard_libraries -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/classes_objects -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/constructor -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/encapsulation -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/raising_exceptions -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} python/build_in_functions -.-> lab-132530{{"`Controlling Symbols and Combining Submodules`"}} end

Preparation

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:

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:

## 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('portfolio.csv', Stock)
    formatter = create_formatter('text')
    print_table(portfolio, ['name','shares','price'], formatter)

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:

## 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('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.

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).

mkdir tableformat

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

mv tableformat.py tableformat/formatter.py

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:

## 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('portfolio.csv', Stock)
    formatter = create_formatter('text')
    print_table(portfolio, ['name','shares','price'], formatter)

Summary

Congratulations! You have completed the Controlling Symbols and Combining Submodules lab. You can practice more labs in LabEx to improve your skills.

Other Python Tutorials you may like