master 93dca856b41c cached
219 files
685.9 KB
238.8k tokens
681 symbols
1 requests
Download .txt
Showing preview only (739K chars total). Download the full file or copy to clipboard to get everything.
Repository: dabeaz-course/practical-python
Branch: master
Commit: 93dca856b41c
Files: 219
Total size: 685.9 KB

Directory structure:
gitextract_7n8e5kpg/

├── .gitignore
├── LICENSE.md
├── Notes/
│   ├── 00_Setup.md
│   ├── 01_Introduction/
│   │   ├── 00_Overview.md
│   │   ├── 01_Python.md
│   │   ├── 02_Hello_world.md
│   │   ├── 03_Numbers.md
│   │   ├── 04_Strings.md
│   │   ├── 05_Lists.md
│   │   ├── 06_Files.md
│   │   └── 07_Functions.md
│   ├── 02_Working_with_data/
│   │   ├── 00_Overview.md
│   │   ├── 01_Datatypes.md
│   │   ├── 02_Containers.md
│   │   ├── 03_Formatting.md
│   │   ├── 04_Sequences.md
│   │   ├── 05_Collections.md
│   │   ├── 06_List_comprehension.md
│   │   └── 07_Objects.md
│   ├── 03_Program_organization/
│   │   ├── 00_Overview.md
│   │   ├── 01_Script.md
│   │   ├── 02_More_functions.md
│   │   ├── 03_Error_checking.md
│   │   ├── 04_Modules.md
│   │   ├── 05_Main_module.md
│   │   └── 06_Design_discussion.md
│   ├── 04_Classes_objects/
│   │   ├── 00_Overview.md
│   │   ├── 01_Class.md
│   │   ├── 02_Inheritance.md
│   │   ├── 03_Special_methods.md
│   │   └── 04_Defining_exceptions.md
│   ├── 05_Object_model/
│   │   ├── 00_Overview.md
│   │   ├── 01_Dicts_revisited.md
│   │   └── 02_Classes_encapsulation.md
│   ├── 06_Generators/
│   │   ├── 00_Overview.md
│   │   ├── 01_Iteration_protocol.md
│   │   ├── 02_Customizing_iteration.md
│   │   ├── 03_Producers_consumers.md
│   │   └── 04_More_generators.md
│   ├── 07_Advanced_Topics/
│   │   ├── 00_Overview.md
│   │   ├── 01_Variable_arguments.md
│   │   ├── 02_Anonymous_function.md
│   │   ├── 03_Returning_functions.md
│   │   ├── 04_Function_decorators.md
│   │   └── 05_Decorated_methods.md
│   ├── 08_Testing_debugging/
│   │   ├── 00_Overview.md
│   │   ├── 01_Testing.md
│   │   ├── 02_Logging.md
│   │   └── 03_Debugging.md
│   ├── 09_Packages/
│   │   ├── 00_Overview.md
│   │   ├── 01_Packages.md
│   │   ├── 02_Third_party.md
│   │   ├── 03_Distribution.md
│   │   └── TheEnd.md
│   ├── Contents.md
│   └── InstructorNotes.md
├── README.md
├── Solutions/
│   ├── 1_10/
│   │   └── mortgage.py
│   ├── 1_27/
│   │   └── pcost.py
│   ├── 1_33/
│   │   └── pcost.py
│   ├── 1_5/
│   │   └── bounce.py
│   ├── 2_11/
│   │   └── report.py
│   ├── 2_16/
│   │   ├── pcost.py
│   │   └── report.py
│   ├── 2_7/
│   │   └── report.py
│   ├── 3_10/
│   │   └── fileparse.py
│   ├── 3_14/
│   │   ├── fileparse.py
│   │   ├── pcost.py
│   │   └── report.py
│   ├── 3_16/
│   │   ├── fileparse.py
│   │   ├── pcost.py
│   │   └── report.py
│   ├── 3_18/
│   │   ├── fileparse.py
│   │   ├── pcost.py
│   │   └── report.py
│   ├── 3_2/
│   │   └── report.py
│   ├── 3_7/
│   │   └── fileparse.py
│   ├── 4_10/
│   │   ├── fileparse.py
│   │   ├── pcost.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   └── tableformat.py
│   ├── 4_4/
│   │   ├── fileparse.py
│   │   ├── pcost.py
│   │   ├── report.py
│   │   └── stock.py
│   ├── 5_8/
│   │   ├── fileparse.py
│   │   ├── pcost.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   └── tableformat.py
│   ├── 6_12/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   ├── tableformat.py
│   │   └── ticker.py
│   ├── 6_15/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   ├── tableformat.py
│   │   └── ticker.py
│   ├── 6_3/
│   │   ├── fileparse.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   └── tableformat.py
│   ├── 6_7/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   └── tableformat.py
│   ├── 7_10/
│   │   └── timethis.py
│   ├── 7_11/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   ├── tableformat.py
│   │   ├── ticker.py
│   │   ├── timethis.py
│   │   └── typedproperty.py
│   ├── 7_4/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   ├── tableformat.py
│   │   └── ticker.py
│   ├── 7_9/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   ├── tableformat.py
│   │   ├── ticker.py
│   │   └── typedproperty.py
│   ├── 8_1/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   ├── tableformat.py
│   │   ├── test_stock.py
│   │   ├── ticker.py
│   │   ├── timethis.py
│   │   └── typedproperty.py
│   ├── 8_2/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   ├── tableformat.py
│   │   ├── test_stock.py
│   │   ├── ticker.py
│   │   ├── timethis.py
│   │   └── typedproperty.py
│   ├── 9_3/
│   │   └── porty-app/
│   │       ├── README.txt
│   │       ├── portfolio.csv
│   │       ├── porty/
│   │       │   ├── __init__.py
│   │       │   ├── fileparse.py
│   │       │   ├── follow.py
│   │       │   ├── pcost.py
│   │       │   ├── portfolio.py
│   │       │   ├── report.py
│   │       │   ├── stock.py
│   │       │   ├── tableformat.py
│   │       │   ├── test_stock.py
│   │       │   ├── ticker.py
│   │       │   └── typedproperty.py
│   │       ├── prices.csv
│   │       └── print-report.py
│   ├── 9_5/
│   │   └── porty-app/
│   │       ├── MANIFEST.in
│   │       ├── README.txt
│   │       ├── portfolio.csv
│   │       ├── porty/
│   │       │   ├── __init__.py
│   │       │   ├── fileparse.py
│   │       │   ├── follow.py
│   │       │   ├── pcost.py
│   │       │   ├── portfolio.py
│   │       │   ├── report.py
│   │       │   ├── stock.py
│   │       │   ├── tableformat.py
│   │       │   ├── test_stock.py
│   │       │   ├── ticker.py
│   │       │   └── typedproperty.py
│   │       ├── prices.csv
│   │       ├── print-report.py
│   │       └── setup.py
│   └── README.md
├── Work/
│   ├── Data/
│   │   ├── dowstocks.csv
│   │   ├── missing.csv
│   │   ├── portfolio.csv
│   │   ├── portfolio2.csv
│   │   ├── portfolioblank.csv
│   │   ├── portfoliodate.csv
│   │   ├── prices.csv
│   │   └── stocksim.py
│   ├── README.md
│   ├── bounce.py
│   ├── fileparse.py
│   ├── mortgage.py
│   ├── pcost.py
│   └── report.py
├── _config.yml
└── _layouts/
    └── default.html

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/


================================================
FILE: LICENSE.md
================================================
## creative commons

# Attribution-ShareAlike 4.0 International

Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.

### Using Creative Commons Public Licenses

Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.

* __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors).

* __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees).

## Creative Commons Attribution-ShareAlike 4.0 International Public License

By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.

### Section 1 – Definitions.

a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.

b. __Adapter's License__ means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.

c. __BY-SA Compatible License__ means a license listed at [creativecommons.org/compatiblelicenses](http://creativecommons.org/compatiblelicenses), approved by Creative Commons as essentially the equivalent of this Public License.

d. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.

e. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.

f. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.

g. __License Elements__ means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike.

h. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License.

i. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.

j. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License.

k. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.

l. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.

m. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.

### Section 2 – Scope.

a. ___License grant.___

    1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:

        A. reproduce and Share the Licensed Material, in whole or in part; and

        B. produce, reproduce, and Share Adapted Material.

    2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.

    3. __Term.__ The term of this Public License is specified in Section 6(a).

    4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.

    5. __Downstream recipients.__

        A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.

        B. __Additional offer from the Licensor – Adapted Material. Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply.

        C. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.

    6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).

b. ___Other rights.___

    1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.

    2. Patent and trademark rights are not licensed under this Public License.

    3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties.

### Section 3 – License Conditions.

Your exercise of the Licensed Rights is expressly made subject to the following conditions.

a. ___Attribution.___

    1. If You Share the Licensed Material (including in modified form), You must:

        A. retain the following if it is supplied by the Licensor with the Licensed Material:

            i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);

            ii. a copyright notice;

            iii. a notice that refers to this Public License;

            iv. a notice that refers to the disclaimer of warranties;

            v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;

        B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and

        C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.

    2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.

    3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.

b. ___ShareAlike.___

In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply.

1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License.

2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material.

3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply.

### Section 4 – Sui Generis Database Rights.

Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:

a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database;

b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and

c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.

For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.

### Section 5 – Disclaimer of Warranties and Limitation of Liability.

a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__

b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__

c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.

### Section 6 – Term and Termination.

a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.

b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:

    1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or

    2. upon express reinstatement by the Licensor.

    For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.

c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.

d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.

### Section 7 – Other Terms and Conditions.

a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.

b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.t stated herein are separate from and independent of the terms and conditions of this Public License.

### Section 8 – Interpretation.

a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.

b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.

c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.

d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.

```
Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.

Creative Commons may be contacted at creativecommons.org
```


================================================
FILE: Notes/00_Setup.md
================================================
# Course Setup and Overview

Welcome to Practical Python Programming!   This page has some important information
about course setup and logistics.

## Course Duration and Time Requirements

This course was originally given as an instructor-led in-person
training that spanned 3 to 4 days.  To complete the course in its
entirety, you should minimally plan on committing 25-35 hours of work.
Most participants find the material to be quite challenging without
peeking at solution code (see below).

## Setup and Python Installation

You need nothing more than a basic Python 3.6 installation or newer.
There is no dependency on any particular operating system, editor,
IDE, or extra Python-related tooling.  There are no third-party
dependencies.

That said, most of this course involves learning how to write scripts
and small programs that involve data read from files.  Therefore, you
need to make sure you're in an environment where you can easily work
with files.  This includes using an editor to create Python programs
and being able to run those programs from the shell/terminal.

You might be inclined to work on this course using a more interactive
environment such as Jupyter Notebooks. **I DO NOT ADVISE THIS!**
Although notebooks are great for experimentation, many of the
exercises in this course teach concepts related to program
organization.  This includes working with functions, modules, import
statements, and refactoring of programs whose source code spans
multiple files.  In my experience, it is hard to replicate this kind
of working environment in notebooks.

## Forking/Cloning the Course Repository

To prepare your environment for the course, I recommend creating your
own fork of the course GitHub repo at
[https://github.com/dabeaz-course/practical-python](https://github.com/dabeaz-course/practical-python).
Once you are done, you can clone it to your local machine:

```
bash % git clone https://github.com/yourname/practical-python
bash % cd practical-python
bash %
```

Do all of your work within the `practical-python/` directory.  If you
commit your solution code back to your fork of the repository, it will
keep all of your code together in one place and you'll have a nice
historical record of your work when you're done.

If you don't want to create a personal fork or don't have a GitHub account,
you can still clone the course directory to your machine:

```
bash % git clone https://github.com/dabeaz-course/practical-python
bash % cd practical-python
bash %
```

With this option, you just won't be able to commit code changes except
to the local copy on your machine.

## Coursework Layout

Do all of your coding work in the `Work/` directory.  Within that
directory, there is a `Data/` directory.  The `Data/` directory
contains a variety of datafiles and other scripts used during the
course. You will frequently have to access files located in `Data/`.
Course exercises are written with the assumption that you are creating
programs in the `Work/` directory.

## Course Order

Course material should be completed in section order, starting with
section 1.  Course exercises in later sections build upon code written in
earlier sections.  Many of the later exercises involve minor refactoring
of existing code.

## Solution Code

The `Solutions/` directory contains full solution code to selected
exercises.  Feel free to look at this if you need a hint.  To get the
most out of the course however, you should try to create your own
solutions first.

[Contents](Contents.md) \| [Next (1 Introduction to Python)](01_Introduction/00_Overview.md)











================================================
FILE: Notes/01_Introduction/00_Overview.md
================================================
[Contents](../Contents.md) \| [Next (2 Working With Data)](../02_Working_with_data/00_Overview.md)

## 1. Introduction to Python

The goal of this first section is to introduce some Python basics from
the ground up.  Starting with nothing, you'll learn how to edit, run,
and debug small programs. Ultimately, you'll write a short script that
reads a CSV data file and performs a simple calculation.

* [1.1 Introducing Python](01_Python.md)
* [1.2 A First Program](02_Hello_world.md)
* [1.3 Numbers](03_Numbers.md)
* [1.4 Strings](04_Strings.md)
* [1.5 Lists](05_Lists.md)
* [1.6 Files](06_Files.md)
* [1.7 Functions](07_Functions.md)

[Contents](../Contents.md) \| [Next (2 Working With Data)](../02_Working_with_data/00_Overview.md)


================================================
FILE: Notes/01_Introduction/01_Python.md
================================================
[Contents](../Contents.md) \| [Next (1.2 A First Program)](02_Hello_world.md)

# 1.1 Python

### What is Python?

Python is an interpreted high level programming language.  It is often classified as a
["scripting language"](https://en.wikipedia.org/wiki/Scripting_language) and
is considered similar to languages such as Perl, Tcl, or Ruby.  The syntax
of Python is loosely inspired by elements of C programming.

Python was created by Guido van Rossum around 1990 who named it in honor of Monty Python.

### Where to get Python?

[Python.org](https://www.python.org/) is where you obtain Python.  For the purposes of this course, you
only need a basic installation.  I recommend installing Python 3.6 or newer. Python 3.6 is used in the notes
and solutions.

### Why was Python created?

In the words of Python's creator:

> My original motivation for creating Python was the perceived need
> for a higher level language in the Amoeba [Operating Systems]
> project. I realized that the development of system administration
> utilities in C was taking too long. Moreover, doing these things in
> the Bourne shell wouldn't work for a variety of reasons. ... So,
> there was a need for a language that would bridge the gap between C
> and the shell.
>
> - Guido van Rossum

### Where is Python on my Machine?

Although there are many environments in which you might run Python,
Python is typically installed on your machine as a program that runs
from the terminal or command shell. From the terminal, you should be
able to type `python` like this:

```
bash $ python
Python 3.8.1 (default, Feb 20 2020, 09:29:22)
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> print("hello world")
hello world
>>>
```

If you are new to using the shell or a terminal, you should probably
stop, finish a short tutorial on that first, and then return here.

Although there are many non-shell environments where you can code
Python, you will be a stronger Python programmer if you are able to
run, debug, and interact with Python at the terminal.  This is
Python's native environment.  If you are able to use Python here, you
will be able to use it everywhere else.

## Exercises

### Exercise 1.1: Using Python as a Calculator

On your machine, start Python and use it as a calculator to solve the
following problem.

Lucky Larry bought 75 shares of Google stock at a price of $235.14 per
share. Today, shares of Google are priced at $711.25. Using Python’s
interactive mode as a calculator, figure out how much profit Larry would
make if he sold all of his shares.

```python
>>> (711.25 - 235.14) * 75
35708.25
>>>
```

Pro-tip: Use the underscore (\_) variable to use the result of the last
calculation. For example, how much profit does Larry make after his evil
broker takes their 20% cut?

```python
>>> _ * 0.80
28566.600000000002
>>>
```

### Exercise 1.2: Getting help

Use the `help()` command to get help on the `abs()` function. Then use
`help()` to get help on the `round()` function. Type `help()` just by
itself with no value to enter the interactive help viewer.

One caution with `help()` is that it doesn’t work for basic Python
statements such as `for`, `if`, `while`, and so forth (i.e., if you type
`help(for)` you’ll get a syntax error). You can try putting the help
topic in quotes such as `help("for")` instead. If that doesn’t work,
you’ll have to turn to an internet search.

Followup: Go to <http://docs.python.org> and find the documentation for
the `abs()` function (hint: it’s found under the library reference
related to built-in functions).

### Exercise 1.3: Cutting and Pasting

This course is structured as a series of traditional web pages where
you are encouraged to try interactive Python code samples **by typing
them out by hand.** If you are learning Python for the first time,
this "slow approach" is encouraged.  You will get a better feel for
the language by slowing down, typing things in, and thinking about
what you are doing.

If you must "cut and paste" code samples, select code
starting after the `>>>` prompt and going up to, but not any further
than the first blank line or the next `>>>` prompt (whichever appears
first). Select "copy" from the browser, go to the Python window, and
select "paste" to copy it into the Python shell. To get the code to
run, you may have to hit "Return" once after you’ve pasted it in.

Use cut-and-paste to execute the Python statements in this session:

```python
>>> 12 + 20
32
>>> (3 + 4
         + 5 + 6)
18
>>> for i in range(5):
        print(i)

0
1
2
3
4
>>>
```

Warning: It is never possible to paste more than one Python command
(statements that appear after `>>>`) to the basic Python shell at a
time. You have to paste each command one at a time.

Now that you've done this, just remember that you will get more out of
the class by typing in code slowly and thinking about it--not cut and pasting.

### Exercise 1.4: Where is My Bus?

Note: This was a whimsical example that was a real crowd-pleaser when
I taught this course in my office.  You could query the bus and then
literally watch it pass by the window out front.  Sadly, APIs rarely live
forever and it seems that this one has now ridden off into the sunset. --Dave

Update: GitHub user @asett has suggested the following modified code might work,
but you'll have to provide your own API key (available [here](https://www.transitchicago.com/developers/bustracker/)).

```python
import urllib.request
u = urllib.request.urlopen('http://www.ctabustracker.com/bustime/api/v2/getpredictions?key=ADD_YOUR_API_KEY_HERE&rt=22&stpid=14791')
from xml.etree.ElementTree import parse
doc = parse(u)
print("Arrival time in minutes:")
for pt in doc.findall('.//prdctdn'):
        print(pt.text)
```

(Original exercise example follows below)

Try something more advanced and type these statements to find out how
long people waiting on the corner of Clark street and Balmoral in
Chicago will have to wait for the next northbound CTA \#22 bus:

```python
>>> import urllib.request
>>> u = urllib.request.urlopen('http://ctabustracker.com/bustime/map/getStopPredictions.jsp?stop=14791&route=22')
>>> from xml.etree.ElementTree import parse
>>> doc = parse(u)
>>> for pt in doc.findall('.//pt'):
        print(pt.text)

6 MIN
18 MIN
28 MIN
>>>
```

Yes, you just downloaded a web page, parsed an XML document, and
extracted some useful information in about 6 lines of code. The data
you accessed is actually feeding the website
<http://ctabustracker.com/bustime/home.jsp>. Try it again and watch
the predictions change.

Note: This service only reports arrival times within the next 30 minutes.
If you're in a different timezone and it happens to be 3am in Chicago, you
might not get any output.  You use the tracker link above to double check.

If the first import statement `import urllib.request` fails, you’re
probably using Python 2. For this course, you need to make sure you’re
using Python 3.6 or newer. Go to <https://www.python.org> to download
it if you need it.

If your work environment requires the use of an HTTP proxy server, you may need
to set the `HTTP_PROXY` environment variable to make this part of the
exercise work. For example:

```python
>>> import os
>>> os.environ['HTTP_PROXY'] = 'http://yourproxy.server.com'
>>>
```

If you can't make this work, don't worry about it.  The rest of this course
has nothing to do with parsing XML.

[Contents](../Contents.md) \| [Next (1.2 A First Program)](02_Hello_world.md)



================================================
FILE: Notes/01_Introduction/02_Hello_world.md
================================================
[Contents](../Contents.md) \| [Previous (1.1 Python)](01_Python.md) \| [Next (1.3 Numbers)](03_Numbers.md)

# 1.2 A First Program

This section discusses the creation of your first program, running the
interpreter, and some basic debugging.

### Running Python

Python programs always run inside an interpreter.

The interpreter is a "console-based" application that normally runs
from a command shell.

```bash
python3
Python 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
```

Expert programmers usually have no problem using the interpreter in
this way, but it's not so user-friendly for beginners.  You may be using
an environment that provides a different interface to Python.  That's fine,
but learning how to run Python terminal is still a useful skill to know.

### Interactive Mode

When you start Python, you get an *interactive* mode where you can experiment.

If you start typing statements, they will run immediately. There is no
edit/compile/run/debug cycle.

```python
>>> print('hello world')
hello world
>>> 37*42
1554
>>> for i in range(5):
...     print(i)
...
0
1
2
3
4
>>>
```

This so-called *read-eval-print-loop* (or REPL) is very useful for debugging and exploration.

**STOP**: If you can't figure out how to interact with Python, stop what you're doing
and figure out how to do it.  If you're using an IDE, it might be hidden behind a
menu option or other window.  Many parts of this course assume that you can
interact with the interpreter.

Let's take a closer look at the elements of the REPL:

- `>>>` is the interpreter prompt for starting a new statement.
- `...` is the interpreter prompt for continuing a statement. Enter a blank line to finish typing and run what you've entered.

The `...` prompt may or may not be shown depending on your environment. For this course,
it is shown as blanks to make it easier to cut/paste code samples.

The underscore `_` holds the last result.

```python
>>> 37 * 42
1554
>>> _ * 2
3108
>>> _ + 50
3158
>>>
```

*This is only true in the interactive mode.* You never use `_` in a program.

### Creating programs

Programs are put in `.py` files.

```python
# hello.py
print('hello world')
```

You can create these files with your favorite text editor.

### Running Programs

To execute a program, run it in the terminal with the `python` command.
For example, in command-line Unix:

```bash
bash % python hello.py
hello world
bash %
```

Or from the Windows shell:

```
C:\SomeFolder>hello.py
hello world

C:\SomeFolder>c:\python36\python hello.py
hello world
```

Note: On Windows, you may need to specify a full path to the Python interpreter such as `c:\python36\python`.
However, if Python is installed in its usual way, you might be able to just type the name of the program
such as `hello.py`.

### A Sample Program

Let's solve the following problem:

> One morning, you go out and place a dollar bill on the sidewalk by the Sears tower in Chicago.
> Each day thereafter, you go out double the number of bills.
> How long does it take for the stack of bills to exceed the height of the tower?

Here's a solution:

```python
# sears.py
bill_thickness = 0.11 * 0.001 # Meters (0.11 mm)
sears_height = 442 # Height (meters)
num_bills = 1
day = 1

while num_bills * bill_thickness < sears_height:
    print(day, num_bills, num_bills * bill_thickness)
    day = day + 1
    num_bills = num_bills * 2

print('Number of days', day)
print('Number of bills', num_bills)
print('Final height', num_bills * bill_thickness)
```

When you run it, you get the following output:

```bash
bash % python3 sears.py
1 1 0.00011
2 2 0.00022
3 4 0.00044
4 8 0.00088
5 16 0.00176
6 32 0.00352
...
21 1048576 115.34336
22 2097152 230.68672
Number of days 23 
Number of bills 4194304 
Final height 461.37344
```

Using this program as a guide, you can learn a number of important core concepts about Python.

### Statements

A python program is a sequence of statements:

```python
a = 3 + 4
b = a * 2
print(b)
```

Each statement is terminated by a newline. Statements are executed one after the other until control reaches the end of the file.

### Comments

Comments are text that will not be executed.

```python
a = 3 + 4
# This is a comment
b = a * 2
print(b)
```

Comments are denoted by `#` and extend to the end of the line.

### Variables

A variable is a name for a value. You can use letters (lower and
upper-case) from a to z. As well as the character underscore `_`.
Numbers can also be part of the name of a variable, except as the
first character.

```python
height = 442 # valid
_height = 442 # valid
height2 = 442 # valid
2height = 442 # invalid
```

### Types

Variables do not need to be declared with the type of the value.  The type
is associated with the value on the right hand side, not name of the variable.

```python
height = 442           # An integer
height = 442.0         # Floating point
height = 'Really tall' # A string
```

Python is dynamically typed. The perceived "type" of a variable might change
as a program executes depending on the current value assigned to it.

### Case Sensitivity

Python is case sensitive. Upper and lower-case letters are considered different letters.
These are all different variables:

```python
name = 'Jake'
Name = 'Elwood'
NAME = 'Guido'
```

Language statements are always lower-case.

```python
while x < 0:   # OK
WHILE x < 0:   # ERROR
```

### Looping

The `while` statement executes a loop.

```python
while num_bills * bill_thickness < sears_height:
    print(day, num_bills, num_bills * bill_thickness)
    day = day + 1
    num_bills = num_bills * 2

print('Number of days', day)
```

The statements indented below the `while` will execute as long as the expression after the `while` is `true`.

### Indentation

Indentation is used to denote groups of statements that go together.
Consider the previous example:

```python
while num_bills * bill_thickness < sears_height:
    print(day, num_bills, num_bills * bill_thickness)
    day = day + 1
    num_bills = num_bills * 2

print('Number of days', day)
```

Indentation groups the following statements together as the operations that repeat:

```python
    print(day, num_bills, num_bills * bill_thickness)
    day = day + 1
    num_bills = num_bills * 2
```

Because the `print()` statement at the end is not indented, it
does not belong to the loop. The empty line is just for
readability. It does not affect the execution.

### Indentation best practices

* Use spaces instead of tabs.
* Use 4 spaces per level.
* Use a Python-aware editor.

Python's only requirement is that indentation within the same block
be consistent.   For example, this is an error:

```python
while num_bills * bill_thickness < sears_height:
    print(day, num_bills, num_bills * bill_thickness)
        day = day + 1 # ERROR
    num_bills = num_bills * 2
```

### Conditionals

The `if` statement is used to execute a conditional:

```python
if a > b:
    print('Computer says no')
else:
    print('Computer says yes')
```

You can check for multiple conditions by adding extra checks using `elif`.

```python
if a > b:
    print('Computer says no')
elif a == b:
    print('Computer says yes')
else:
    print('Computer says maybe')
```

### Printing

The `print` function produces a single line of text with the values passed.

```python
print('Hello world!') # Prints the text 'Hello world!'
```

You can use variables. The text printed will be the value of the variable, not the name.

```python
x = 100
print(x) # Prints the text '100'
```

If you pass more than one value to `print` they are separated by spaces.

```python
name = 'Jake'
print('My name is', name) # Print the text 'My name is Jake'
```

`print()` always puts a newline at the end.

```python
print('Hello')
print('My name is', 'Jake')
```

This prints:

```code
Hello
My name is Jake
```

The extra newline can be suppressed:

```python
print('Hello', end=' ')
print('My name is', 'Jake')
```

This code will now print:

```code
Hello My name is Jake
```

### User input

To read a line of typed user input, use the `input()` function:

```python
name = input('Enter your name:')
print('Your name is', name)
```

`input` prints a prompt to the user and returns their response.
This is useful for small programs, learning exercises or simple debugging.
It is not widely used for real programs.

### pass statement

Sometimes you need to specify an empty code block. The keyword `pass` is used for it.

```python
if a > b:
    pass
else:
    print('Computer says false')
```

This is also called a "no-op" statement. It does nothing. It serves as a placeholder for statements, possibly to be added later.

## Exercises

This is the first set of exercises where you need to create Python
files and run them.  From this point forward, it is assumed that you
are editing files in the `practical-python/Work/` directory.  To help
you locate the proper place, a number of empty starter files have
been created with the appropriate filenames.  Look for the file
`Work/bounce.py` that's used in the first exercise.

### Exercise 1.5: The Bouncing Ball

A rubber ball is dropped from a height of 100 meters and each time it
hits the ground, it bounces back up to 3/5 the height it fell.  Write
a program `bounce.py` that prints a table showing the height of the
first 10 bounces.

Your program should make a table that looks something like this:

```code
1 60.0
2 36.0
3 21.599999999999998
4 12.959999999999999
5 7.775999999999999
6 4.6655999999999995
7 2.7993599999999996
8 1.6796159999999998
9 1.0077695999999998
10 0.6046617599999998
```

*Note: You can clean up the output a bit if you use the round() function. Try using it to round the output to 4 digits.*

```code
1 60.0
2 36.0
3 21.6
4 12.96
5 7.776
6 4.6656
7 2.7994
8 1.6796
9 1.0078
10 0.6047
```

### Exercise 1.6: Debugging

The following code fragment contains code from the Sears tower problem.  It also has a bug in it.

```python
# sears.py

bill_thickness = 0.11 * 0.001    # Meters (0.11 mm)
sears_height   = 442             # Height (meters)
num_bills      = 1
day            = 1

while num_bills * bill_thickness < sears_height:
    print(day, num_bills, num_bills * bill_thickness)
    day = days + 1
    num_bills = num_bills * 2

print('Number of days', day)
print('Number of bills', num_bills)
print('Final height', num_bills * bill_thickness)
```

Copy and paste the code that appears above in a new program called `sears.py`.
When you run the code you will get an error message that causes the
program to crash like this:

```code
Traceback (most recent call last):
  File "sears.py", line 10, in <module>
    day = days + 1
NameError: name 'days' is not defined
```

Reading error messages is an important part of Python code. If your program
crashes, the very last line of the traceback message is the actual reason why the
the program crashed. Above that, you should see a fragment of source code and then
an identifying filename and line number.

* Which line is the error?
* What is the error?
* Fix the error
* Run the program successfully


[Contents](../Contents.md) \| [Previous (1.1 Python)](01_Python.md) \| [Next (1.3 Numbers)](03_Numbers.md)


================================================
FILE: Notes/01_Introduction/03_Numbers.md
================================================
[Contents](../Contents.md) \| [Previous (1.2 A First Program)](02_Hello_world.md) \| [Next (1.4 Strings)](04_Strings.md)

# 1.3 Numbers

This section discusses mathematical calculations.

### Types of Numbers

Python has 4 types of numbers:

* Booleans
* Integers
* Floating point
* Complex (imaginary numbers)

### Booleans (bool)

Booleans have two values: `True`, `False`.

```python
a = True
b = False
```

Numerically, they're evaluated as integers with value `1`, `0`.

```python
c = 4 + True # 5
d = False
if d == 0:
    print('d is False')
```

*But, don't write code like that. It would be odd.*

### Integers (int)

Signed values of arbitrary size and base:

```python
a = 37
b = -299392993727716627377128481812241231
c = 0x7fa8      # Hexadecimal
d = 0o253       # Octal
e = 0b10001111  # Binary
```

Common operations:

```
x + y      Add
x - y      Subtract
x * y      Multiply
x / y      Divide (produces a float)
x // y     Floor Divide (produces an integer)
x % y      Modulo (remainder)
x ** y     Power
x << n     Bit shift left
x >> n     Bit shift right
x & y      Bit-wise AND
x | y      Bit-wise OR
x ^ y      Bit-wise XOR
~x         Bit-wise NOT
abs(x)     Absolute value
```

### Floating point (float)

Use a decimal or exponential notation to specify a floating point value:

```python
a = 37.45
b = 4e5 # 4 x 10**5 or 400,000
c = -1.345e-10
```

Floats are represented as double precision using the native CPU representation [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754).
This is the same as the `double` type in the programming language C.

> 17 digits of precision  
> Exponent from -308 to 308

Be aware that floating point numbers are inexact when representing decimals.

```python
>>> a = 2.1 + 4.2
>>> a == 6.3
False
>>> a
6.300000000000001
>>>
```

This is **not a Python issue**, but the underlying floating point hardware on the CPU.

Common Operations:

```
x + y      Add
x - y      Subtract
x * y      Multiply
x / y      Divide
x // y     Floor Divide
x % y      Modulo
x ** y     Power
abs(x)     Absolute Value
```

These are the same operators as Integers, except for the bit-wise operators.
Additional math functions are found in the `math` module.

```python
import math
a = math.sqrt(x)
b = math.sin(x)
c = math.cos(x)
d = math.tan(x)
e = math.log(x)
```


### Comparisons

The following comparison / relational operators work with numbers:

```
x < y      Less than
x <= y     Less than or equal
x > y      Greater than
x >= y     Greater than or equal
x == y     Equal to
x != y     Not equal to
```

You can form more complex boolean expressions using

`and`, `or`, `not`

Here are a few examples:

```python
if b >= a and b <= c:
    print('b is between a and c')

if not (b < a or b > c):
    print('b is still between a and c')
```

### Converting Numbers

The type name can be used to convert values:

```python
a = int(x)    # Convert x to integer
b = float(x)  # Convert x to float
```

Try it out.

```python
>>> a = 3.14159
>>> int(a)
3
>>> b = '3.14159' # It also works with strings containing numbers
>>> float(b)
3.14159
>>>
```

## Exercises

Reminder: These exercises assume you are working in the `practical-python/Work` directory. Look
for the file `mortgage.py`.

### Exercise 1.7: Dave's mortgage

Dave has decided to take out a 30-year fixed rate mortgage of $500,000
with Guido’s Mortgage, Stock Investment, and Bitcoin trading
corporation.  The interest rate is 5% and the monthly payment is
$2684.11.

Here is a program that calculates the total amount that Dave will have
to pay over the life of the mortgage:

```python
# mortgage.py

principal = 500000.0
rate = 0.05
payment = 2684.11
total_paid = 0.0

while principal > 0:
    principal = principal * (1+rate/12) - payment
    total_paid = total_paid + payment

print('Total paid', total_paid)
```

Enter this program and run it. You should get an answer of `966,279.6`.

### Exercise 1.8: Extra payments

Suppose Dave pays an extra $1000/month for the first 12 months of the mortgage?

Modify the program to incorporate this extra payment and have it print the total amount paid along with the number of months required.

When you run the new program, it should report a total payment of `929,965.62` over 342 months.

### Exercise 1.9: Making an Extra Payment Calculator

Modify the program so that extra payment information can be more generally handled.
Make it so that the user can set these variables:

```python
extra_payment_start_month = 61
extra_payment_end_month = 108
extra_payment = 1000
```

Make the program look at these variables and calculate the total paid appropriately.

How much will Dave pay if he pays an extra $1000/month for 4 years starting after the first
five years have already been paid?

### Exercise 1.10: Making a table

Modify the program to print out a table showing the month, total paid so far, and the remaining principal.
The output should look something like this:

```bash
1 2684.11 499399.22
2 5368.22 498795.94
3 8052.33 498190.15
4 10736.44 497581.83
5 13420.55 496970.98
...
308 874705.88 3478.83
309 877389.99 809.21
310 880074.1 -1871.53
Total paid 880074.1
Months 310
```

### Exercise 1.11: Bonus

While you’re at it, fix the program to correct for the overpayment that occurs in the last month.

### Exercise 1.12: A Mystery

`int()` and `float()` can be used to convert numbers.  For example,

```python
>>> int("123")
123
>>> float("1.23")
1.23
>>>
```

With that in mind, can you explain this behavior?

```python
>>> bool("False")
True
>>>
```

[Contents](../Contents.md) \| [Previous (1.2 A First Program)](02_Hello_world.md) \| [Next (1.4 Strings)](04_Strings.md)


================================================
FILE: Notes/01_Introduction/04_Strings.md
================================================
[Contents](../Contents.md) \| [Previous (1.3 Numbers)](03_Numbers.md) \| [Next (1.5 Lists)](05_Lists.md)

# 1.4 Strings

This section introduces ways to work with text.

### Representing Literal Text

String literals are written in programs with quotes.

```python
# Single quote
a = 'Yeah but no but yeah but...'

# Double quote
b = "computer says no"

# Triple quotes
c = '''
Look into my eyes, look into my eyes, the eyes, the eyes, the eyes,
not around the eyes,
don't look around the eyes,
look into my eyes, you're under.
'''
```

Normally strings may only span a single line. Triple quotes capture all text enclosed across multiple lines
including all formatting.

There is no difference between using single (') versus double (")
quotes. *However, the same type of quote used to start a string must be used to
terminate it*.

### String escape codes

Escape codes are used to represent control characters and characters that can't be easily typed
directly at the keyboard.  Here are some common escape codes:

```
'\n'      Line feed
'\r'      Carriage return
'\t'      Tab
'\''      Literal single quote
'\"'      Literal double quote
'\\'      Literal backslash
```

### String Representation

Each character in a string is stored internally as a so-called Unicode "code-point" which is
an integer.  You can specify an exact code-point value using the following escape sequences:

```python
a = '\xf1'          # a = 'ñ'
b = '\u2200'        # b = '∀'
c = '\U0001D122'    # c = '𝄢'
d = '\N{FOR ALL}'   # d = '∀'
```

The [Unicode Character Database](https://unicode.org/charts) is a reference for all
available character codes.

### String Indexing

Strings work like an array for accessing individual characters. You use an integer index, starting at 0.
Negative indices specify a position relative to the end of the string.

```python
a = 'Hello world'
b = a[0]          # 'H'
c = a[4]          # 'o'
d = a[-1]         # 'd' (end of string)
```

You can also slice or select substrings specifying a range of indices with `:`.

```python
d = a[:5]     # 'Hello'
e = a[6:]     # 'world'
f = a[3:8]    # 'lo wo'
g = a[-5:]    # 'world'
```

The character at the ending index is not included.  Missing indices assume the beginning or ending of the string.

### String operations

Concatenation, length, membership and replication.

```python
# Concatenation (+)
a = 'Hello' + 'World'   # 'HelloWorld'
b = 'Say ' + a          # 'Say HelloWorld'

# Length (len)
s = 'Hello'
len(s)                  # 5

# Membership test (`in`, `not in`)
t = 'e' in s            # True
f = 'x' in s            # False
g = 'hi' not in s       # True

# Replication (s * n)
rep = s * 5             # 'HelloHelloHelloHelloHello'
```

### String methods

Strings have methods that perform various operations with the string data.

Example: stripping any leading / trailing white space.

```python
s = '  Hello '
t = s.strip()     # 'Hello'
```

Example: Case conversion.

```python
s = 'Hello'
l = s.lower()     # 'hello'
u = s.upper()     # 'HELLO'
```

Example: Replacing text.

```python
s = 'Hello world'
t = s.replace('Hello' , 'Hallo')   # 'Hallo world'
```

**More string methods:**

Strings have a wide variety of other methods for testing and manipulating the text data.
This is a small sample of methods:

```python
s.endswith(suffix)     # Check if string ends with suffix
s.find(t)              # First occurrence of t in s
s.index(t)             # First occurrence of t in s
s.isalpha()            # Check if characters are alphabetic
s.isdigit()            # Check if characters are numeric
s.islower()            # Check if characters are lower-case
s.isupper()            # Check if characters are upper-case
s.join(slist)          # Join a list of strings using s as delimiter
s.lower()              # Convert to lower case
s.replace(old,new)     # Replace text
s.rfind(t)             # Search for t from end of string
s.rindex(t)            # Search for t from end of string
s.split([delim])       # Split string into list of substrings
s.startswith(prefix)   # Check if string starts with prefix
s.strip()              # Strip leading/trailing space
s.upper()              # Convert to upper case
```

### String Mutability

Strings are "immutable" or read-only.
Once created, the value can't be changed.

```python
>>> s = 'Hello World'
>>> s[1] = 'a'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>>
```

**All operations and methods that manipulate string data, always create new strings.**

### String Conversions

Use `str()` to convert any value to a string. The result is a string holding the
same text that would have been produced by the `print()` statement.

```python
>>> x = 42
>>> str(x)
'42'
>>>
```

### Byte Strings

A string of 8-bit bytes, commonly encountered with low-level I/O, is written as follows:

```python
data = b'Hello World\r\n'
```

By putting a little b before the first quotation, you specify that it is a byte string as opposed to a text string.

Most of the usual string operations work.

```python
len(data)                         # 13
data[0:5]                         # b'Hello'
data.replace(b'Hello', b'Cruel')  # b'Cruel World\r\n'
```

Indexing is a bit different because it returns byte values as integers.

```python
data[0]   # 72 (ASCII code for 'H')
```

Conversion to/from text strings.

```python
text = data.decode('utf-8') # bytes -> text
data = text.encode('utf-8') # text -> bytes
```

The `'utf-8'` argument specifies a character encoding.  Other common
values include `'ascii'` and `'latin1'`.

### Raw Strings

Raw strings are string literals with an uninterpreted backslash. They
are specified by prefixing the initial quote with a lowercase "r".

```python
>>> rs = r'c:\newdata\test' # Raw (uninterpreted backslash)
>>> rs
'c:\\newdata\\test'
```

The string is the literal text enclosed inside, exactly as typed.
This is useful in situations where the backslash has special
significance. Example: filename, regular expressions, etc.

### f-Strings

A string with formatted expression substitution.

```python
>>> name = 'IBM'
>>> shares = 100
>>> price = 91.1
>>> a = f'{name:>10s} {shares:10d} {price:10.2f}'
>>> a
'       IBM        100      91.10'
>>> b = f'Cost = ${shares*price:0.2f}'
>>> b
'Cost = $9110.00'
>>>
```

**Note: This requires Python 3.6 or newer.**  The meaning of the format codes
is covered later.

## Exercises

In these exercises, you'll experiment with operations on Python's
string type.  You should do this at the Python interactive prompt
where you can easily see the results.  Important note:

> In exercises where you are supposed to interact with the interpreter,
> `>>>` is the interpreter prompt that you get when Python wants
> you to type a new statement.  Some statements in the exercise span
> multiple lines--to get these statements to run, you may have to hit
> 'return' a few times.  Just a reminder that you *DO NOT* type
> the `>>>` when working these examples.

Start by defining a string containing a series of stock ticker symbols like this:

```python
>>> symbols = 'AAPL,IBM,MSFT,YHOO,SCO'
>>>
```

### Exercise 1.13: Extracting individual characters and substrings

Strings are arrays of characters. Try extracting a few characters:

```python
>>> symbols[0]
?
>>> symbols[1]
?
>>> symbols[2]
?
>>> symbols[-1]        # Last character
?
>>> symbols[-2]        # Negative indices are from end of string
?
>>>
```

In Python, strings are read-only.

Verify this by trying to change the first character of `symbols` to a lower-case 'a'.

```python
>>> symbols[0] = 'a'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>>
```

### Exercise 1.14: String concatenation

Although string data is read-only, you can always reassign a variable
to a newly created string.

Try the following statement which concatenates a new symbol "GOOG" to
the end of `symbols`:

```python
>>> symbols = symbols + 'GOOG'
>>> symbols
'AAPL,IBM,MSFT,YHOO,SCOGOOG'
>>>
```

Oops!  That's not what you wanted. Fix it so that the `symbols` variable holds the value `'AAPL,IBM,MSFT,YHOO,SCO,GOOG'`.

```python
>>> symbols = ?
>>> symbols
'AAPL,IBM,MSFT,YHOO,SCO,GOOG'
>>>
```

Add `'HPQ'` to the front the string:

```python
>>> symbols = ?
>>> symbols
'HPQ,AAPL,IBM,MSFT,YHOO,SCO,GOOG'
>>>
```

In these examples, it might look like the original string is being
modified, in an apparent violation of strings being read only.  Not
so. Operations on strings create an entirely new string each
time. When the variable name `symbols` is reassigned, it points to the
newly created string.  Afterwards, the old string is destroyed since
it's not being used anymore.

### Exercise 1.15: Membership testing (substring testing)

Experiment with the `in` operator to check for substrings.  At the
interactive prompt, try these operations:

```python
>>> 'IBM' in symbols
?
>>> 'AA' in symbols
True
>>> 'CAT' in symbols
?
>>>
```

*Why did the check for `'AA'` return `True`?*

### Exercise 1.16: String Methods

At the Python interactive prompt, try experimenting with some of the string methods.

```python
>>> symbols.lower()
?
>>> symbols
?
>>>
```

Remember, strings are always read-only.  If you want to save the result of an operation, you need to place it in a variable:

```python
>>> lowersyms = symbols.lower()
>>>
```

Try some more operations:

```python
>>> symbols.find('MSFT')
?
>>> symbols[13:17]
?
>>> symbols = symbols.replace('SCO','DOA')
>>> symbols
?
>>> name = '   IBM   \n'
>>> name = name.strip()    # Remove surrounding whitespace
>>> name
?
>>>
```

### Exercise 1.17: f-strings

Sometimes you want to create a string and embed the values of
variables into it.

To do that, use an f-string. For example:

```python
>>> name = 'IBM'
>>> shares = 100
>>> price = 91.1
>>> f'{shares} shares of {name} at ${price:0.2f}'
'100 shares of IBM at $91.10'
>>>
```

Modify the `mortgage.py` program from [Exercise 1.10](03_Numbers.md) to create its output using f-strings.
Try to make it so that output is nicely aligned.


### Exercise 1.18: Regular Expressions

One limitation of the basic string operations is that they don't
support any kind of advanced pattern matching.  For that, you
need to turn to Python's `re` module and regular expressions.
Regular expression handling is a big topic, but here is a short
example:

```python
>>> text = 'Today is 3/27/2018. Tomorrow is 3/28/2018.'
>>> # Find all occurrences of a date
>>> import re
>>> re.findall(r'\d+/\d+/\d+', text)
['3/27/2018', '3/28/2018']
>>> # Replace all occurrences of a date with replacement text
>>> re.sub(r'(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text)
'Today is 2018-3-27. Tomorrow is 2018-3-28.'
>>>
```

For more information about the `re` module, see the official documentation at
[https://docs.python.org/library/re.html](https://docs.python.org/3/library/re.html).


### Commentary

As you start to experiment with the interpreter, you often want to
know more about the operations supported by different objects.  For
example, how do you find out what operations are available on a
string?

Depending on your Python environment, you might be able to see a list
of available methods via tab-completion.  For example, try typing
this:

```python
>>> s = 'hello world'
>>> s.<tab key>
>>>
```

If hitting tab doesn't do anything, you can fall back to the
builtin-in `dir()` function.  For example:

```python
>>> s = 'hello'
>>> dir(s)
['__add__', '__class__', '__contains__', ..., 'find', 'format',
'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace',
'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition',
'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit',
'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase',
'title', 'translate', 'upper', 'zfill']
>>>
```

`dir()` produces a list of all operations that can appear after the `(.)`.
Use the `help()` command to get more information about a specific operation:

```python
>>> help(s.upper)
Help on built-in function upper:

upper(...)
    S.upper() -> string

    Return a copy of the string S converted to uppercase.
>>>
```

[Contents](../Contents.md) \| [Previous (1.3 Numbers)](03_Numbers.md) \| [Next (1.5 Lists)](05_Lists.md)


================================================
FILE: Notes/01_Introduction/05_Lists.md
================================================
[Contents](../Contents.md) \| [Previous (1.4 Strings)](04_Strings.md) \| [Next (1.6 Files)](06_Files.md)

# 1.5 Lists

This section introduces lists, Python's primary type for holding an ordered collection of values.

### Creating a List

Use square brackets to define a list literal:

```python
names = [ 'Elwood', 'Jake', 'Curtis' ]
nums = [ 39, 38, 42, 65, 111]
```

Sometimes lists are created by other methods.  For example, a string can be split into a
list using the `split()` method:

```python
>>> line = 'GOOG,100,490.10'
>>> row = line.split(',')
>>> row
['GOOG', '100', '490.10']
>>>
```

### List operations

Lists can hold items of any type. Add a new item using `append()`:

```python
names.append('Murphy')    # Adds at end
names.insert(2, 'Aretha') # Inserts in middle
```

Use `+` to concatenate lists:

```python
s = [1, 2, 3]
t = ['a', 'b']
s + t           # [1, 2, 3, 'a', 'b']
```

Lists are indexed by integers. Starting at 0.

```python
names = [ 'Elwood', 'Jake', 'Curtis' ]

names[0]  # 'Elwood'
names[1]  # 'Jake'
names[2]  # 'Curtis'
```

Negative indices count from the end.

```python
names[-1] # 'Curtis'
```

You can change any item in a list.

```python
names[1] = 'Joliet Jake'
names                     # [ 'Elwood', 'Joliet Jake', 'Curtis' ]
```

Length of the list.

```python
names = ['Elwood','Jake','Curtis']
len(names)  # 3
```

Membership test (`in`, `not in`).

```python
'Elwood' in names       # True
'Britney' not in names  # True
```

Replication (`s * n`).

```python
s = [1, 2, 3]
s * 3   # [1, 2, 3, 1, 2, 3, 1, 2, 3]
```

### List Iteration and Search

Use `for` to iterate over the list contents.

```python
for name in names:
    # use name
    # e.g. print(name)
    ...
```

This is similar to a `foreach` statement from other programming languages.

To find the position of something quickly, use `index()`.

```python
names = ['Elwood','Jake','Curtis']
names.index('Curtis')   # 2
```

If the element is present more than once, `index()` will return the index of the first occurrence.

If the element is not found, it will raise a `ValueError` exception.

### List Removal

You can remove items either by element value or by index:

```python
# Using the value
names.remove('Curtis')

# Using the index
del names[1]
```

Removing an item does not create a hole.  Other items will move down
to fill the space vacated.  If there are more than one occurrence of
the element, `remove()` will remove only the first occurrence.

### List Sorting

Lists can be sorted "in-place".

```python
s = [10, 1, 7, 3]
s.sort()                    # [1, 3, 7, 10]

# Reverse order
s = [10, 1, 7, 3]
s.sort(reverse=True)        # [10, 7, 3, 1]

# It works with any ordered data
s = ['foo', 'bar', 'spam']
s.sort()                    # ['bar', 'foo', 'spam']
```

Use `sorted()` if you'd like to make a new list instead:

```python
t = sorted(s)               # s unchanged, t holds sorted values
```

### Lists and Math

*Caution: Lists were not designed for math operations.*

```python
>>> nums = [1, 2, 3, 4, 5]
>>> nums * 2
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
>>> nums + [10, 11, 12, 13, 14]
[1, 2, 3, 4, 5, 10, 11, 12, 13, 14]
```

Specifically, lists don't represent vectors/matrices as in MATLAB, Octave, R, etc.
However, there are some packages to help you with that (e.g. [numpy](https://numpy.org)).

## Exercises

In this exercise, we experiment with Python's list datatype. In the last section,
you worked with strings containing stock symbols.

```python
>>> symbols = 'HPQ,AAPL,IBM,MSFT,YHOO,DOA,GOOG'
```

Split it into a list of names using the `split()` operation of strings:

```python
>>> symlist = symbols.split(',')
```

### Exercise 1.19: Extracting and reassigning list elements

Try a few lookups:

```python
>>> symlist[0]
'HPQ'
>>> symlist[1]
'AAPL'
>>> symlist[-1]
'GOOG'
>>> symlist[-2]
'DOA'
>>>
```

Try reassigning one value:

```python
>>> symlist[2] = 'AIG'
>>> symlist
['HPQ', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'DOA', 'GOOG']
>>>
```

Take a few slices:

```python
>>> symlist[0:3]
['HPQ', 'AAPL', 'AIG']
>>> symlist[-2:]
['DOA', 'GOOG']
>>>
```

Create an empty list and append an item to it.

```python
>>> mysyms = []
>>> mysyms.append('GOOG')
>>> mysyms
['GOOG']
```

You can reassign a portion of a list to another list. For example:

```python
>>> symlist[-2:] = mysyms
>>> symlist
['HPQ', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'GOOG']
>>>
```

When you do this, the list on the left-hand-side (`symlist`) will be resized as appropriate to make the right-hand-side (`mysyms`) fit.
For instance, in the above example, the last two items of `symlist` got replaced by the single item in the list `mysyms`.

### Exercise 1.20: Looping over list items

The `for` loop works by looping over data in a sequence such as a list.
Check this out by typing the following loop and watching what happens:

```python
>>> for s in symlist:
        print('s =', s)
# Look at the output
```

### Exercise 1.21: Membership tests

Use the `in` or `not in` operator to check if `'AIG'`,`'AA'`, and `'CAT'` are in the list of symbols.

```python
>>> # Is 'AIG' IN the `symlist`?
True
>>> # Is 'AA' IN the `symlist`?
False
>>> # Is 'CAT' NOT IN the `symlist`?
True
>>>
```

### Exercise 1.22: Appending, inserting, and deleting items

Use the `append()` method to add the symbol `'RHT'` to end of `symlist`.

```python
>>> # append 'RHT'
>>> symlist
['HPQ', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'GOOG', 'RHT']
>>>
```

Use the `insert()` method to insert the symbol `'AA'` as the second item in the list.

```python
>>> # Insert 'AA' as the second item in the list
>>> symlist
['HPQ', 'AA', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'GOOG', 'RHT']
>>>
```

Use the `remove()` method to remove `'MSFT'` from the list.

```python
>>> # Remove 'MSFT'
>>> symlist
['HPQ', 'AA', 'AAPL', 'AIG', 'YHOO', 'GOOG', 'RHT']
>>>
```

Append a duplicate entry for `'YHOO'` at the end of the list.

*Note: it is perfectly fine for a list to have duplicate values.*

```python
>>> # Append 'YHOO'
>>> symlist
['HPQ', 'AA', 'AAPL', 'AIG', 'YHOO', 'GOOG', 'RHT', 'YHOO']
>>>
```

Use the `index()` method to find the first position of `'YHOO'` in the list.

```python
>>> # Find the first index of 'YHOO'
4
>>> symlist[4]
'YHOO'
>>>
```

Count how many times `'YHOO'` is in the list:

```python
>>> symlist.count('YHOO')
2
>>>
```

Remove the first occurrence of `'YHOO'`.

```python
>>> # Remove first occurrence 'YHOO'
>>> symlist
['HPQ', 'AA', 'AAPL', 'AIG', 'GOOG', 'RHT', 'YHOO']
>>>
```

Just so you know, there is no method to find or remove all occurrences of an item.
However, we'll see an elegant way to do this in section 2.

### Exercise 1.23: Sorting

Want to sort a list?  Use the `sort()` method. Try it out:

```python
>>> symlist.sort()
>>> symlist
['AA', 'AAPL', 'AIG', 'GOOG', 'HPQ', 'RHT', 'YHOO']
>>>
```

Want to sort in reverse? Try this:

```python
>>> symlist.sort(reverse=True)
>>> symlist
['YHOO', 'RHT', 'HPQ', 'GOOG', 'AIG', 'AAPL', 'AA']
>>>
```

Note: Sorting a list modifies its contents 'in-place'.  That is, the elements of the list are shuffled around, but no new list is created as a result.

### Exercise 1.24: Putting it all back together

Want to take a list of strings and join them together into one string?
Use the `join()` method of strings like this (note: this looks funny at first).

```python
>>> a = ','.join(symlist)
>>> a
'YHOO,RHT,HPQ,GOOG,AIG,AAPL,AA'
>>> b = ':'.join(symlist)
>>> b
'YHOO:RHT:HPQ:GOOG:AIG:AAPL:AA'
>>> c = ''.join(symlist)
>>> c
'YHOORHTHPQGOOGAIGAAPLAA'
>>>
```

### Exercise 1.25: Lists of anything

Lists can contain any kind of object, including other lists (e.g., nested lists).
Try this out:

```python
>>> nums = [101, 102, 103]
>>> items = ['spam', symlist, nums]
>>> items
['spam', ['YHOO', 'RHT', 'HPQ', 'GOOG', 'AIG', 'AAPL', 'AA'], [101, 102, 103]]
```

Pay close attention to the above output. `items` is a list with three elements.
The first element is a string, but the other two elements are lists.

You can access items in the nested lists by using multiple indexing operations.

```python
>>> items[0]
'spam'
>>> items[0][0]
's'
>>> items[1]
['YHOO', 'RHT', 'HPQ', 'GOOG', 'AIG', 'AAPL', 'AA']
>>> items[1][1]
'RHT'
>>> items[1][1][2]
'T'
>>> items[2]
[101, 102, 103]
>>> items[2][1]
102
>>>
```

Even though it is technically possible to make very complicated list
structures, as a general rule, you want to keep things simple.
Usually lists hold items that are all the same kind of value.  For
example, a list that consists entirely of numbers or a list of text
strings.  Mixing different kinds of data together in the same list is
often a good way to make your head explode so it's best avoided.

[Contents](../Contents.md) \| [Previous (1.4 Strings)](04_Strings.md) \| [Next (1.6 Files)](06_Files.md)


================================================
FILE: Notes/01_Introduction/06_Files.md
================================================
[Contents](../Contents.md) \| [Previous (1.5 Lists)](05_Lists.md) \| [Next (1.7 Functions)](07_Functions.md)

# 1.6 File Management

Most programs need to read input from somewhere. This section discusses file access.

### File Input and Output

Open a file.

```python
f = open('foo.txt', 'rt')     # Open for reading (text)
g = open('bar.txt', 'wt')     # Open for writing (text)
```

Read all of the data.

```python
data = f.read()

# Read only up to 'maxbytes' bytes
data = f.read([maxbytes])
```

Write some text.

```python
g.write('some text')
```

Close when you are done.

```python
f.close()
g.close()
```

Files should be properly closed and it's an easy step to forget.
Thus, the preferred approach is to use the `with` statement like this.

```python
with open(filename, 'rt') as file:
    # Use the file `file`
    ...
    # No need to close explicitly
...statements
```

This automatically closes the file when control leaves the indented code block.

### Common Idioms for Reading File Data

Read an entire file all at once as a string.

```python
with open('foo.txt', 'rt') as file:
    data = file.read()
    # `data` is a string with all the text in `foo.txt`
```

Read a file line-by-line by iterating.

```python
with open(filename, 'rt') as file:
    for line in file:
        # Process the line
```

### Common Idioms for Writing to a File

Write string data.

```python
with open('outfile', 'wt') as out:
    out.write('Hello World\n')
    ...
```

Redirect the print function.

```python
with open('outfile', 'wt') as out:
    print('Hello World', file=out)
    ...
```

## Exercises

These exercises depend on a file `Data/portfolio.csv`.  The file
contains a list of lines with information on a portfolio of stocks.
It is assumed that you are working in the `practical-python/Work/`
directory.  If you're not sure, you can find out where Python thinks
it's running by doing this:

```python
>>> import os
>>> os.getcwd()
'/Users/beazley/Desktop/practical-python/Work' # Output vary
>>>
```

### Exercise 1.26: File Preliminaries

First, try reading the entire file all at once as a big string:

```python
>>> with open('Data/portfolio.csv', 'rt') as f:
        data = f.read()

>>> data
'name,shares,price\n"AA",100,32.20\n"IBM",50,91.10\n"CAT",150,83.44\n"MSFT",200,51.23\n"GE",95,40.37\n"MSFT",50,65.10\n"IBM",100,70.44\n'
>>> print(data)
name,shares,price
"AA",100,32.20
"IBM",50,91.10
"CAT",150,83.44
"MSFT",200,51.23
"GE",95,40.37
"MSFT",50,65.10
"IBM",100,70.44
>>>
```

In the above example, it should be noted that Python has two modes of
output.  In the first mode where you type `data` at the prompt, Python
shows you the raw string representation including quotes and escape
codes.  When you type `print(data)`, you get the actual formatted
output of the string.

Although reading a file all at once is simple, it is often not the
most appropriate way to do it—especially if the file happens to be
huge or if contains lines of text that you want to handle one at a
time.

To read a file line-by-line, use a for-loop like this:

```python
>>> with open('Data/portfolio.csv', 'rt') as f:
        for line in f:
            print(line, end='')

name,shares,price
"AA",100,32.20
"IBM",50,91.10
...
>>>
```

When you use this code as shown, lines are read until the end of the
file is reached at which point the loop stops.

On certain occasions, you might want to manually read or skip a
*single* line of text (e.g., perhaps you want to skip the first line
of column headers).

```python
>>> f = open('Data/portfolio.csv', 'rt')
>>> headers = next(f)
>>> headers
'name,shares,price\n'
>>> for line in f:
    print(line, end='')

"AA",100,32.20
"IBM",50,91.10
...
>>> f.close()
>>>
```

`next()` returns the next line of text in the file. If you were to call it repeatedly, you would get successive lines.
However, just so you know, the `for` loop already uses `next()` to obtain its data.
Thus, you normally wouldn’t call it directly unless you’re trying to explicitly skip or read a single line as shown.

Once you’re reading lines of a file, you can start to perform more processing such as splitting.
For example, try this:

```python
>>> f = open('Data/portfolio.csv', 'rt')
>>> headers = next(f).split(',')
>>> headers
['name', 'shares', 'price\n']
>>> for line in f:
    row = line.split(',')
    print(row)

['"AA"', '100', '32.20\n']
['"IBM"', '50', '91.10\n']
...
>>> f.close()
```

*Note: In these examples, `f.close()` is being called explicitly because the `with` statement isn’t being used.*

### Exercise 1.27: Reading a data file

Now that you know how to read a file, let’s write a program to perform a simple calculation.

The columns in `portfolio.csv` correspond to the stock name, number of
shares, and purchase price of a single stock holding.  Write a program called
`pcost.py` that opens this file, reads all lines, and calculates how
much it cost to purchase all of the shares in the portfolio.

*Hint: to convert a string to an integer, use `int(s)`. To convert a string to a floating point, use `float(s)`.*

Your program should print output such as the following:

```bash
Total cost 44671.15
```

### Exercise 1.28: Other kinds of "files"

What if you wanted to read a non-text file such as a gzip-compressed
datafile?  The builtin `open()` function won’t help you here, but
Python has a library module `gzip` that can read gzip compressed
files.

Try it:

```python
>>> import gzip
>>> with gzip.open('Data/portfolio.csv.gz', 'rt') as f:
        for line in f:
            print(line, end='')

... look at the output ...
>>>
```

Note: Including the file mode of `'rt'` is critical here.  If you forget that,
you'll get byte strings instead of normal text strings.

### Commentary:  Shouldn't we being using Pandas for this?

Data scientists are quick to point out that libraries like
[Pandas](https://pandas.pydata.org) already have a function for
reading CSV files.  This is true--and it works pretty well.
However, this is not a course on learning Pandas. Reading files
is a more general problem than the specifics of CSV files.
The main reason we're working with a CSV file is that it's a
familiar format to most coders and it's relatively easy to work with
directly--illustrating many Python features in the process.
So, by all means use Pandas when you go back to work.  For the
rest of this course however, we're going to stick with standard
Python functionality.

[Contents](../Contents.md) \| [Previous (1.5 Lists)](05_Lists.md) \| [Next (1.7 Functions)](07_Functions.md)


================================================
FILE: Notes/01_Introduction/07_Functions.md
================================================
[Contents](../Contents.md) \| [Previous (1.6 Files)](06_Files.md) \| [Next (2.0 Working with Data)](../02_Working_with_data/00_Overview.md)

# 1.7 Functions

As your programs start to get larger, you'll want to get organized.  This section
briefly introduces functions and library modules.  Error handling with exceptions is also introduced.

### Custom Functions

Use functions for code you want to reuse. Here is a function definition:

```python
def sumcount(n):
    '''
    Returns the sum of the first n integers
    '''
    total = 0
    while n > 0:
        total += n
        n -= 1
    return total
```

To call a function.

```python
a = sumcount(100)
```

A function is a series of statements that perform some task and return a result.
The `return` keyword is needed to explicitly specify the return value of the function.

### Library Functions

Python comes with a large standard library.
Library modules are accessed using `import`.
For example:

```python
import math
x = math.sqrt(10)

import urllib.request
u = urllib.request.urlopen('http://www.python.org/')
data = u.read()
```

We will cover libraries and modules in more detail later.

### Errors and exceptions

Functions report errors as exceptions.  An exception causes a function to abort and may
cause your entire program to stop if unhandled.

Try this in your python REPL.

```python
>>> int('N/A')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'N/A'
>>>
```

For debugging purposes, the message describes what happened, where the error occurred,
and a traceback showing the other function calls that led to the failure.

### Catching and Handling Exceptions

Exceptions can be caught and handled.

To catch, use the `try - except` statement.

```python
for line in file:
    fields = line.split(',')
    try:
        shares = int(fields[1])
    except ValueError:
        print("Couldn't parse", line)
    ...
```

The name `ValueError` must match the kind of error you are trying to catch.

It is often difficult to know exactly what kinds of errors might occur
in advance depending on the operation being performed.  For better or
for worse, exception handling often gets added *after* a program has
unexpectedly crashed (i.e., "oh, we forgot to catch that error. We
should handle that!").

### Raising Exceptions

To raise an exception, use the `raise` statement.

```python
raise RuntimeError('What a kerfuffle')
```

This will cause the program to abort with an exception traceback. Unless caught by a `try-except` block.

```bash
% python3 foo.py
Traceback (most recent call last):
  File "foo.py", line 21, in <module>
    raise RuntimeError("What a kerfuffle")
RuntimeError: What a kerfuffle
```

## Exercises

### Exercise 1.29: Defining a function

Try defining a simple function:

```python
>>> def greeting(name):
        'Issues a greeting'
        print('Hello', name)

>>> greeting('Guido')
Hello Guido
>>> greeting('Paula')
Hello Paula
>>>
```

If the first statement of a function is a string, it serves as documentation.
Try typing a command such as `help(greeting)` to see it displayed.

### Exercise 1.30: Turning a script into a function

Take the code you wrote for the `pcost.py` program in [Exercise 1.27](06_Files.md)
and turn it into a function `portfolio_cost(filename)`.  This
function takes a filename as input, reads the portfolio data in that
file, and returns the total cost of the portfolio as a float.

To use your function, change your program so that it looks something
like this:

```python
def portfolio_cost(filename):
    ...
    # Your code here
    ...

cost = portfolio_cost('Data/portfolio.csv')
print('Total cost:', cost)
```

When you run your program, you should see the same output as before.
After you’ve run your program, you can also call your function
interactively by typing this:

```bash
bash $ python3 -i pcost.py
```

This will allow you to call your function from the interactive mode.

```python
>>> portfolio_cost('Data/portfolio.csv')
44671.15
>>>
```

Being able to experiment with your code interactively is useful for
testing and debugging.

### Exercise 1.31: Error handling

What happens if you try your function on a file with some missing fields?

```python
>>> portfolio_cost('Data/missing.csv')
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "pcost.py", line 11, in portfolio_cost
    nshares    = int(fields[1])
ValueError: invalid literal for int() with base 10: ''
>>>
```

At this point, you’re faced with a decision. To make the program work
you can either sanitize the original input file by eliminating bad
lines or you can modify your code to handle the bad lines in some
manner.

Modify the `pcost.py` program to catch the exception, print a warning
message, and continue processing the rest of the file.

### Exercise 1.32: Using a library function

Python comes with a large standard library of useful functions.  One
library that might be useful here is the `csv` module. You should use
it whenever you have to work with CSV data files.  Here is an example
of how it works:

```python
>>> import csv
>>> f = open('Data/portfolio.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> headers
['name', 'shares', 'price']
>>> for row in rows:
        print(row)

['AA', '100', '32.20']
['IBM', '50', '91.10']
['CAT', '150', '83.44']
['MSFT', '200', '51.23']
['GE', '95', '40.37']
['MSFT', '50', '65.10']
['IBM', '100', '70.44']
>>> f.close()
>>>
```

One nice thing about the `csv` module is that it deals with a variety
of low-level details such as quoting and proper comma splitting.  In
the above output, you’ll notice that it has stripped the double-quotes
away from the names in the first column.

Modify your `pcost.py` program so that it uses the `csv` module for
parsing and try running earlier examples.

### Exercise 1.33: Reading from the command line

In the `pcost.py` program, the name of the input file has been hardwired into the code:

```python
# pcost.py

def portfolio_cost(filename):
    ...
    # Your code here
    ...

cost = portfolio_cost('Data/portfolio.csv')
print('Total cost:', cost)
```

That’s fine for learning and testing, but in a real program you
probably wouldn’t do that.

Instead, you might pass the name of the file in as an argument to a
script. Try changing the bottom part of the program as follows:

```python
# pcost.py
import sys

def portfolio_cost(filename):
    ...
    # Your code here
    ...

if len(sys.argv) == 2:
    filename = sys.argv[1]
else:
    filename = 'Data/portfolio.csv'

cost = portfolio_cost(filename)
print('Total cost:', cost)
```

`sys.argv` is a list that contains passed arguments on the command line (if any).

To run your program, you’ll need to run Python from the
terminal.

For example, from bash on Unix:

```bash
bash % python3 pcost.py Data/portfolio.csv
Total cost: 44671.15
bash %
```

[Contents](../Contents.md) \| [Previous (1.6 Files)](06_Files.md) \| [Next (2.0 Working with Data)](../02_Working_with_data/00_Overview.md)

================================================
FILE: Notes/02_Working_with_data/00_Overview.md
================================================
[Contents](../Contents.md) \| [Prev (1 Introduction to Python)](../01_Introduction/00_Overview.md) \| [Next (3 Program Organization)](../03_Program_organization/00_Overview.md)

# 2. Working With Data

To write useful programs, you need to be able to work with data.
This section introduces Python's core data structures of tuples,
lists, sets, and dictionaries and discusses common data handling
idioms.  The last part of this section dives a little deeper
into Python's underlying object model.

* [2.1 Datatypes and Data Structures](01_Datatypes.md)
* [2.2 Containers](02_Containers.md)
* [2.3 Formatted Output](03_Formatting.md)
* [2.4 Sequences](04_Sequences.md)
* [2.5 Collections module](05_Collections.md)
* [2.6 List comprehensions](06_List_comprehension.md)
* [2.7 Object model](07_Objects.md)

[Contents](../Contents.md) \| [Prev (1 Introduction to Python)](../01_Introduction/00_Overview.md) \| [Next (3 Program Organization)](../03_Program_organization/00_Overview.md)


================================================
FILE: Notes/02_Working_with_data/01_Datatypes.md
================================================
[Contents](../Contents.md) \| [Previous (1.6 Files)](../01_Introduction/06_Files.md) \| [Next (2.2 Containers)](02_Containers.md)

# 2.1 Datatypes and Data structures

This section introduces data structures in the form of tuples and dictionaries.

### Primitive Datatypes

Python has a few primitive types of data:

* Integers
* Floating point numbers
* Strings (text)

We learned about these in the introduction.

### None type

```python
email_address = None
```

`None` is often used as a placeholder for optional or missing value.  It
evaluates as `False` in conditionals.

```python
if email_address:
    send_email(email_address, msg)
```

### Data Structures

Real programs have more complex data. For example information about a stock holding:

```code
100 shares of GOOG at $490.10
```

This is an "object" with three parts:

* Name or symbol of the stock ("GOOG", a string)
* Number of shares (100, an integer)
* Price (490.10 a float)

### Tuples

A tuple is a collection of values grouped together.

Example:

```python
s = ('GOOG', 100, 490.1)
```

Sometimes the `()` are omitted in the syntax.

```python
s = 'GOOG', 100, 490.1
```

Special cases (0-tuple, 1-tuple).

```python
t = ()            # An empty tuple
w = ('GOOG', )    # A 1-item tuple
```

Tuples are often used to represent *simple* records or structures.
Typically, it is a single *object* of multiple parts. A good analogy: *A tuple is like a single row in a database table.*

Tuple contents are ordered (like an array).

```python
s = ('GOOG', 100, 490.1)
name = s[0]                 # 'GOOG'
shares = s[1]               # 100
price = s[2]                # 490.1
```

However, the contents can't be modified.

```python
>>> s[1] = 75
TypeError: object does not support item assignment
```

You can, however, make a new tuple based on a current tuple.

```python
s = (s[0], 75, s[2])
```

### Tuple Packing

Tuples are more about packing related items together into a single *entity*.

```python
s = ('GOOG', 100, 490.1)
```

The tuple is then easy to pass around to other parts of a program as a single object.

### Tuple Unpacking

To use the tuple elsewhere, you can unpack its parts into variables.

```python
name, shares, price = s
print('Cost', shares * price)
```

The number of variables on the left must match the tuple structure.

```python
name, shares = s     # ERROR
Traceback (most recent call last):
...
ValueError: too many values to unpack
```

### Tuples vs. Lists

Tuples look like read-only lists. However, tuples are most often used
for a *single item* consisting of multiple parts.  Lists are usually a
collection of distinct items, usually all of the same type.

```python
record = ('GOOG', 100, 490.1)       # A tuple representing a record in a portfolio

symbols = [ 'GOOG', 'AAPL', 'IBM' ]  # A List representing three stock symbols
```

### Dictionaries

A dictionary is mapping of keys to values.  It's also sometimes called a hash table or
associative array.  The keys serve as indices for accessing values.

```python
s = {
    'name': 'GOOG',
    'shares': 100,
    'price': 490.1
}
```

### Common operations

To get values from a dictionary use the key names.

```python
>>> print(s['name'], s['shares'])
GOOG 100
>>> s['price']
490.10
>>>
```

To add or modify values assign using the key names.

```python
>>> s['shares'] = 75
>>> s['date'] = '6/6/2007'
>>>
```

To delete a value use the `del` statement.

```python
>>> del s['date']
>>>
```

### Why dictionaries?

Dictionaries are useful when there are *many* different values and those values
might be modified or manipulated.  Dictionaries make your code more readable.

```python
s['price']
# vs
s[2]
```

## Exercises

In the last few exercises, you wrote a program that read a datafile
`Data/portfolio.csv`. Using the `csv` module, it is easy to read the
file row-by-row.

```python
>>> import csv
>>> f = open('Data/portfolio.csv')
>>> rows = csv.reader(f)
>>> next(rows)
['name', 'shares', 'price']
>>> row = next(rows)
>>> row
['AA', '100', '32.20']
>>>
```

Although reading the file is easy, you often want to do more with the
data than read it.  For instance, perhaps you want to store it and
start performing some calculations on it.  Unfortunately, a raw "row"
of data doesn’t give you enough to work with. For example, even a
simple math calculation doesn’t work:

```python
>>> row = ['AA', '100', '32.20']
>>> cost = row[1] * row[2]
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'str'
>>>
```

To do more, you typically want to interpret the raw data in some way
and turn it into a more useful kind of object so that you can work
with it later.  Two simple options are tuples or dictionaries.

### Exercise 2.1: Tuples

At the interactive prompt, create the following tuple that represents
the above row, but with the numeric columns converted to proper
numbers:

```python
>>> t = (row[0], int(row[1]), float(row[2]))
>>> t
('AA', 100, 32.2)
>>>
```

Using this, you can now calculate the total cost by multiplying the
shares and the price:

```python
>>> cost = t[1] * t[2]
>>> cost
3220.0000000000005
>>>
```

Is math broken in Python? What’s the deal with the answer of
3220.0000000000005?

This is an artifact of the floating point hardware on your computer
only being able to accurately represent decimals in Base-2, not
Base-10.  For even simple calculations involving base-10 decimals,
small errors are introduced. This is normal, although perhaps a bit
surprising if you haven’t seen it before.

This happens in all programming languages that use floating point
decimals, but it often gets hidden when printing. For example:

```python
>>> print(f'{cost:0.2f}')
3220.00
>>>
```

Tuples are read-only. Verify this by trying to change the number of
shares to 75.

```python
>>> t[1] = 75
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>>
```

Although you can’t change tuple contents, you can always create a
completely new tuple that replaces the old one.

```python
>>> t = (t[0], 75, t[2])
>>> t
('AA', 75, 32.2)
>>>
```

Whenever you reassign an existing variable name like this, the old
value is discarded.  Although the above assignment might look like you
are modifying the tuple, you are actually creating a new tuple and
throwing the old one away.

Tuples are often used to pack and unpack values into variables. Try
the following:

```python
>>> name, shares, price = t
>>> name
'AA'
>>> shares
75
>>> price
32.2
>>>
```

Take the above variables and pack them back into a tuple

```python
>>> t = (name, 2*shares, price)
>>> t
('AA', 150, 32.2)
>>>
```

### Exercise 2.2: Dictionaries as a data structure

An alternative to a tuple is to create a dictionary instead.

```python
>>> d = {
        'name' : row[0],
        'shares' : int(row[1]),
        'price'  : float(row[2])
    }
>>> d
{'name': 'AA', 'shares': 100, 'price': 32.2 }
>>>
```

Calculate the total cost of this holding:

```python
>>> cost = d['shares'] * d['price']
>>> cost
3220.0000000000005
>>>
```

Compare this example with the same calculation involving tuples
above. Change the number of shares to 75.

```python
>>> d['shares'] = 75
>>> d
{'name': 'AA', 'shares': 75, 'price': 32.2 }
>>>
```

Unlike tuples, dictionaries can be freely modified. Add some
attributes:

```python
>>> d['date'] = (6, 11, 2007)
>>> d['account'] = 12345
>>> d
{'name': 'AA', 'shares': 75, 'price':32.2, 'date': (6, 11, 2007), 'account': 12345}
>>>
```

### Exercise 2.3: Some additional dictionary operations

If you turn a dictionary into a list, you’ll get all of its keys:

```python
>>> list(d)
['name', 'shares', 'price', 'date', 'account']
>>>
```

Similarly, if you use the `for` statement to iterate on a dictionary,
you will get the keys:

```python
>>> for k in d:
        print('k =', k)

k = name
k = shares
k = price
k = date
k = account
>>>
```

Try this variant that performs a lookup at the same time:

```python
>>> for k in d:
        print(k, '=', d[k])

name = AA
shares = 75
price = 32.2
date = (6, 11, 2007)
account = 12345
>>>
```

You can also obtain all of the keys using the `keys()` method:

```python
>>> keys = d.keys()
>>> keys
dict_keys(['name', 'shares', 'price', 'date', 'account'])
>>>
```

`keys()` is a bit unusual in that it returns a special `dict_keys` object.

This is an overlay on the original dictionary that always gives you
the current keys—even if the dictionary changes. For example, try
this:

```python
>>> del d['account']
>>> keys
dict_keys(['name', 'shares', 'price', 'date'])
>>>
```

Carefully notice that the `'account'` disappeared from `keys` even
though you didn’t call `d.keys()` again.

A more elegant way to work with keys and values together is to use the
`items()` method. This gives you `(key, value)` tuples:

```python
>>> items = d.items()
>>> items
dict_items([('name', 'AA'), ('shares', 75), ('price', 32.2), ('date', (6, 11, 2007))])
>>> for k, v in d.items():
        print(k, '=', v)

name = AA
shares = 75
price = 32.2
date = (6, 11, 2007)
>>>
```

If you have tuples such as `items`, you can create a dictionary using
the `dict()` function. Try it:

```python
>>> items
dict_items([('name', 'AA'), ('shares', 75), ('price', 32.2), ('date', (6, 11, 2007))])
>>> d = dict(items)
>>> d
{'name': 'AA', 'shares': 75, 'price':32.2, 'date': (6, 11, 2007)}
>>>
```

[Contents](../Contents.md) \| [Previous (1.6 Files)](../01_Introduction/06_Files.md) \| [Next (2.2 Containers)](02_Containers.md)


================================================
FILE: Notes/02_Working_with_data/02_Containers.md
================================================
[Contents](../Contents.md) \| [Previous (2.1 Datatypes)](01_Datatypes.md) \| [Next (2.3 Formatting)](03_Formatting.md)

# 2.2 Containers

This section discusses lists, dictionaries, and sets.

### Overview

Programs often have to work with many objects.

* A portfolio of stocks
* A table of stock prices

There are three main choices to use.

* Lists. Ordered data.
* Dictionaries. Unordered data.
* Sets. Unordered collection of unique items.

### Lists as a Container

Use a list when the order of the data matters. Remember that lists can hold any kind of object.
For example, a list of tuples.

```python
portfolio = [
    ('GOOG', 100, 490.1),
    ('IBM', 50, 91.3),
    ('CAT', 150, 83.44)
]

portfolio[0]            # ('GOOG', 100, 490.1)
portfolio[2]            # ('CAT', 150, 83.44)
```

### List construction

Building a list from scratch.

```python
records = []  # Initial empty list

# Use .append() to add more items
records.append(('GOOG', 100, 490.10))
records.append(('IBM', 50, 91.3))
...
```

An example when reading records from a file.

```python
records = []  # Initial empty list

with open('Data/portfolio.csv', 'rt') as f:
    next(f) # Skip header
    for line in f:
        row = line.split(',')
        records.append((row[0], int(row[1]), float(row[2])))
```

### Dicts as a Container

Dictionaries are useful if you want fast random lookups (by key name).  For
example, a dictionary of stock prices:

```python
prices = {
   'GOOG': 513.25,
   'CAT': 87.22,
   'IBM': 93.37,
   'MSFT': 44.12
}
```

Here are some simple lookups:

```python
>>> prices['IBM']
93.37
>>> prices['GOOG']
513.25
>>>
```

### Dict Construction

Example of building a dict from scratch.

```python
prices = {} # Initial empty dict

# Insert new items
prices['GOOG'] = 513.25
prices['CAT'] = 87.22
prices['IBM'] = 93.37
```

An example populating the dict from the contents of a file.

```python
prices = {} # Initial empty dict

with open('Data/prices.csv', 'rt') as f:
    for line in f:
        row = line.split(',')
        prices[row[0]] = float(row[1])
```

Note: If you try this on the `Data/prices.csv` file, you'll find that
it almost works--there's a blank line at the end that causes it to
crash.  You'll need to figure out some way to modify the code to
account for that (see Exercise 2.6).

### Dictionary Lookups

You can test the existence of a key.

```python
if key in d:
    # YES
else:
    # NO
```

You can look up a value that might not exist and provide a default value in case it doesn't.

```python
name = d.get(key, default)
```

An example:

```python
>>> prices.get('IBM', 0.0)
93.37
>>> prices.get('SCOX', 0.0)
0.0
>>>
```

### Composite keys

Almost any type of value can be used as a dictionary key in Python. A dictionary key must be of a type that is immutable.
For example, tuples:

```python
holidays = {
  (1, 1) : 'New Years',
  (3, 14) : 'Pi day',
  (9, 13) : "Programmer's day",
}
```

Then to access:

```python
>>> holidays[3, 14]
'Pi day'
>>>
```

*Neither a list, a set, nor another dictionary can serve as a dictionary key, because lists, sets, and dictionaries are mutable.*

### Sets

Sets are collection of unordered unique items.

```python
tech_stocks = { 'IBM','AAPL','MSFT' }
# Alternative syntax
tech_stocks = set(['IBM', 'AAPL', 'MSFT'])
```

Sets are useful for membership tests.

```python
>>> tech_stocks
set(['AAPL', 'IBM', 'MSFT'])
>>> 'IBM' in tech_stocks
True
>>> 'FB' in tech_stocks
False
>>>
```

Sets are also useful for duplicate elimination.

```python
names = ['IBM', 'AAPL', 'GOOG', 'IBM', 'GOOG', 'YHOO']

unique = set(names)
# unique = set(['IBM', 'AAPL','GOOG','YHOO'])
```

Additional set operations:

```python
unique.add('CAT')        # Add an item
unique.remove('YHOO')    # Remove an item

s1 = { 'a', 'b', 'c'}
s2 = { 'c', 'd' }
s1 | s2                 # Set union { 'a', 'b', 'c', 'd' }
s1 & s2                 # Set intersection { 'c' }
s1 - s2                 # Set difference { 'a', 'b' }
```

## Exercises

In these exercises, you start building one of the major programs used
for the rest of this course.  Do your work in the file `Work/report.py`.

### Exercise 2.4: A list of tuples

The file `Data/portfolio.csv` contains a list of stocks in a
portfolio.  In [Exercise 1.30](../01_Introduction/07_Functions.md), you
wrote a function `portfolio_cost(filename)` that read this file and
performed a simple calculation.

Your code should have looked something like this:

```python
# pcost.py

import csv

def portfolio_cost(filename):
    '''Computes the total cost (shares*price) of a portfolio file'''
    total_cost = 0.0

    with open(filename, 'rt') as f:
        rows = csv.reader(f)
        headers = next(rows)
        for row in rows:
            nshares = int(row[1])
            price = float(row[2])
            total_cost += nshares * price
    return total_cost
```

Using this code as a rough guide, create a new file `report.py`.  In
that file, define a function `read_portfolio(filename)` that opens a
given portfolio file and reads it into a list of tuples.  To do this,
you’re going to make a few minor modifications to the above code.

First, instead of defining `total_cost = 0`, you’ll make a variable
that’s initially set to an empty list. For example:

```python
portfolio = []
```

Next, instead of totaling up the cost, you’ll turn each row into a
tuple exactly as you just did in the last exercise and append it to
this list. For example:

```python
for row in rows:
    holding = (row[0], int(row[1]), float(row[2]))
    portfolio.append(holding)
```

Finally, you’ll return the resulting `portfolio` list.

Experiment with your function interactively (just a reminder that in
order to do this, you first have to run the `report.py` program in the
interpreter):

*Hint: Use `-i` when executing the file in the terminal*

```python
>>> portfolio = read_portfolio('Data/portfolio.csv')
>>> portfolio
[('AA', 100, 32.2), ('IBM', 50, 91.1), ('CAT', 150, 83.44), ('MSFT', 200, 51.23),
    ('GE', 95, 40.37), ('MSFT', 50, 65.1), ('IBM', 100, 70.44)]
>>>
>>> portfolio[0]
('AA', 100, 32.2)
>>> portfolio[1]
('IBM', 50, 91.1)
>>> portfolio[1][1]
50
>>> total = 0.0
>>> for s in portfolio:
        total += s[1] * s[2]

>>> print(total)
44671.15
>>>
```

This list of tuples that you have created is very similar to a 2-D
array.  For example, you can access a specific column and row using a
lookup such as `portfolio[row][column]` where `row` and `column` are
integers.

That said, you can also rewrite the last for-loop using a statement like this:

```python
>>> total = 0.0
>>> for name, shares, price in portfolio:
            total += shares*price

>>> print(total)
44671.15
>>>
```

### Exercise 2.5: List of Dictionaries

Take the function you wrote in Exercise 2.4 and modify to represent each
stock in the portfolio with a dictionary instead of a tuple.  In this
dictionary use the field names of "name", "shares", and "price" to
represent the different columns in the input file.

Experiment with this new function in the same manner as you did in
Exercise 2.4.

```python
>>> portfolio = read_portfolio('Data/portfolio.csv')
>>> portfolio
[{'name': 'AA', 'shares': 100, 'price': 32.2}, {'name': 'IBM', 'shares': 50, 'price': 91.1},
    {'name': 'CAT', 'shares': 150, 'price': 83.44}, {'name': 'MSFT', 'shares': 200, 'price': 51.23},
    {'name': 'GE', 'shares': 95, 'price': 40.37}, {'name': 'MSFT', 'shares': 50, 'price': 65.1},
    {'name': 'IBM', 'shares': 100, 'price': 70.44}]
>>> portfolio[0]
{'name': 'AA', 'shares': 100, 'price': 32.2}
>>> portfolio[1]
{'name': 'IBM', 'shares': 50, 'price': 91.1}
>>> portfolio[1]['shares']
50
>>> total = 0.0
>>> for s in portfolio:
        total += s['shares']*s['price']

>>> print(total)
44671.15
>>>
```

Here, you will notice that the different fields for each entry are
accessed by key names instead of numeric column numbers.  This is
often preferred because the resulting code is easier to read later.

Viewing large dictionaries and lists can be messy. To clean up the
output for debugging, consider using the `pprint` function.

```python
>>> from pprint import pprint
>>> pprint(portfolio)
[{'name': 'AA', 'price': 32.2, 'shares': 100},
    {'name': 'IBM', 'price': 91.1, 'shares': 50},
    {'name': 'CAT', 'price': 83.44, 'shares': 150},
    {'name': 'MSFT', 'price': 51.23, 'shares': 200},
    {'name': 'GE', 'price': 40.37, 'shares': 95},
    {'name': 'MSFT', 'price': 65.1, 'shares': 50},
    {'name': 'IBM', 'price': 70.44, 'shares': 100}]
>>>
```

### Exercise 2.6: Dictionaries as a container

A dictionary is a useful way to keep track of items where you want to
look up items using an index other than an integer.  In the Python
shell, try playing with a dictionary:

```python
>>> prices = { }
>>> prices['IBM'] = 92.45
>>> prices['MSFT'] = 45.12
>>> prices
... look at the result ...
>>> prices['IBM']
92.45
>>> prices['AAPL']
... look at the result ...
>>> 'AAPL' in prices
False
>>>
```

The file `Data/prices.csv` contains a series of lines with stock prices.
The file looks something like this:

```csv
"AA",9.22
"AXP",24.85
"BA",44.85
"BAC",11.27
"C",3.72
...
```

Write a function `read_prices(filename)` that reads a set of prices
such as this into a dictionary where the keys of the dictionary are
the stock names and the values in the dictionary are the stock prices.

To do this, start with an empty dictionary and start inserting values
into it just as you did above. However, you are reading the values
from a file now.

We’ll use this data structure to quickly lookup the price of a given
stock name.

A few little tips that you’ll need for this part. First, make sure you
use the `csv` module just as you did before—there’s no need to
reinvent the wheel here.

```python
>>> import csv
>>> f = open('Data/prices.csv', 'r')
>>> rows = csv.reader(f)
>>> for row in rows:
        print(row)


['AA', '9.22']
['AXP', '24.85']
...
[]
>>>
```

The other little complication is that the `Data/prices.csv` file may
have some blank lines in it. Notice how the last row of data above is
an empty list—meaning no data was present on that line.

There’s a possibility that this could cause your program to die with
an exception.  Use the `try` and `except` statements to catch this as
appropriate.  Thought: would it be better to guard against bad data with
an `if`-statement instead?

Once you have written your `read_prices()` function, test it
interactively to make sure it works:

```python
>>> prices = read_prices('Data/prices.csv')
>>> prices['IBM']
106.28
>>> prices['MSFT']
20.89
>>>
```

### Exercise 2.7: Finding out if you can retire

Tie all of this work together by adding a few additional statements to
your `report.py` program that computes gain/loss. These statements
should take the list of stocks in Exercise 2.5 and the dictionary of
prices in Exercise 2.6 and compute the current value of the portfolio
along with the gain/loss.

[Contents](../Contents.md) \| [Previous (2.1 Datatypes)](01_Datatypes.md) \| [Next (2.3 Formatting)](03_Formatting.md)


================================================
FILE: Notes/02_Working_with_data/03_Formatting.md
================================================
[Contents](../Contents.md) \| [Previous (2.2 Containers)](02_Containers.md) \| [Next (2.4 Sequences)](04_Sequences.md)

# 2.3 Formatting

This section is a slight digression, but when you work with data, you
often want to produce structured output (tables, etc.). For example:

```code
      Name      Shares        Price
----------  ----------  -----------
        AA         100        32.20
       IBM          50        91.10
       CAT         150        83.44
      MSFT         200        51.23
        GE          95        40.37
      MSFT          50        65.10
       IBM         100        70.44
```

### String Formatting

One way to format string in Python 3.6+ is with `f-strings`.

```python
>>> name = 'IBM'
>>> shares = 100
>>> price = 91.1
>>> f'{name:>10s} {shares:>10d} {price:>10.2f}'
'       IBM        100      91.10'
>>>
```

The part `{expression:format}` is replaced.

It is commonly used with `print`.

```python
print(f'{name:>10s} {shares:>10d} {price:>10.2f}')
```

### Format codes

Format codes (after the `:` inside the `{}`) are similar to C `printf()`.  Common codes
include:

```code
d       Decimal integer
b       Binary integer
x       Hexadecimal integer
f       Float as [-]m.dddddd
e       Float as [-]m.dddddde+-xx
g       Float, but selective use of E notation
s       String
c       Character (from integer)
```

Common modifiers adjust the field width and decimal precision.  This is a partial list:

```code
:>10d   Integer right aligned in 10-character field
:<10d   Integer left aligned in 10-character field
:^10d   Integer centered in 10-character field
:0.2f   Float with 2 digit precision
```

### Dictionary Formatting

You can use the `format_map()` method to apply string formatting to a dictionary of values:

```python
>>> s = {
    'name': 'IBM',
    'shares': 100,
    'price': 91.1
}
>>> '{name:>10s} {shares:10d} {price:10.2f}'.format_map(s)
'       IBM        100      91.10'
>>>
```

It uses the same codes as `f-strings` but takes the values from the
supplied dictionary.

### format() method

There is a method `format()` that can apply formatting to arguments or
keyword arguments.

```python
>>> '{name:>10s} {shares:10d} {price:10.2f}'.format(name='IBM', shares=100, price=91.1)
'       IBM        100      91.10'
>>> '{:>10s} {:10d} {:10.2f}'.format('IBM', 100, 91.1)
'       IBM        100      91.10'
>>>
```

Frankly, `format()` is a bit verbose. I prefer f-strings.

### C-Style Formatting

You can also use the formatting operator `%`.

```python
>>> 'The value is %d' % 3
'The value is 3'
>>> '%5d %-5d %10d' % (3,4,5)
'    3 4              5'
>>> '%0.2f' % (3.1415926,)
'3.14'
```

This requires a single item or a tuple on the right.  Format codes are
modeled after the C `printf()` as well.

*Note: This is the only formatting available on byte strings.*

```python
>>> b'%s has %d messages' % (b'Dave', 37)
b'Dave has 37 messages'
>>> b'%b has %d messages' % (b'Dave', 37)  # %b may be used instead of %s
b'Dave has 37 messages'
>>>
```

## Exercises

### Exercise 2.8: How to format numbers

A common problem with printing numbers is specifying the number of
decimal places. One way to fix this is to use f-strings. Try these
examples:

```python
>>> value = 42863.1
>>> print(value)
42863.1
>>> print(f'{value:0.4f}')
42863.1000
>>> print(f'{value:>16.2f}')
        42863.10
>>> print(f'{value:<16.2f}')
42863.10
>>> print(f'{value:*>16,.2f}')
*******42,863.10
>>>
```

Full documentation on the formatting codes used f-strings can be found
[here](https://docs.python.org/3/library/string.html#format-specification-mini-language). Formatting
is also sometimes performed using the `%` operator of strings.

```python
>>> print('%0.4f' % value)
42863.1000
>>> print('%16.2f' % value)
        42863.10
>>>
```

Documentation on various codes used with `%` can be found
[here](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting).

Although it’s commonly used with `print`, string formatting is not tied to printing.
If you want to save a formatted string. Just assign it to a variable.

```python
>>> f = '%0.4f' % value
>>> f
'42863.1000'
>>>
```

### Exercise 2.9: Collecting Data

In Exercise 2.7, you wrote a program called `report.py` that computed the gain/loss of a
stock portfolio.  In this exercise, you're going to start modifying it to produce a table like this:

```
      Name     Shares      Price     Change
---------- ---------- ---------- ----------
        AA        100       9.22     -22.98
       IBM         50     106.28      15.18
       CAT        150      35.46     -47.98
      MSFT        200      20.89     -30.34
        GE         95      13.48     -26.89
      MSFT         50      20.89     -44.21
       IBM        100     106.28      35.84
```

In this report, "Price" is the current share price of the stock and
"Change" is the change in the share price from the initial purchase
price.


In order to generate the above report, you’ll first want to collect
all of the data shown in the table.  Write a function `make_report()`
that takes a list of stocks and dictionary of prices as input and
returns a list of tuples containing the rows of the above table.

Add this function to your `report.py` file. Here’s how it should work
if you try it interactively:

```python
>>> portfolio = read_portfolio('Data/portfolio.csv')
>>> prices = read_prices('Data/prices.csv')
>>> report = make_report(portfolio, prices)
>>> for r in report:
        print(r)

('AA', 100, 9.22, -22.980000000000004)
('IBM', 50, 106.28, 15.180000000000007)
('CAT', 150, 35.46, -47.98)
('MSFT', 200, 20.89, -30.339999999999996)
('GE', 95, 13.48, -26.889999999999997)
...
>>>
```

### Exercise 2.10: Printing a formatted table

Redo the for-loop in Exercise 2.9, but change the print statement to
format the tuples.

```python
>>> for r in report:
        print('%10s %10d %10.2f %10.2f' % r)

          AA        100       9.22     -22.98
         IBM         50     106.28      15.18
         CAT        150      35.46     -47.98
        MSFT        200      20.89     -30.34
...
>>>
```

You can also expand the values and use f-strings. For example:

```python
>>> for name, shares, price, change in report:
        print(f'{name:>10s} {shares:>10d} {price:>10.2f} {change:>10.2f}')

          AA        100       9.22     -22.98
         IBM         50     106.28      15.18
         CAT        150      35.46     -47.98
        MSFT        200      20.89     -30.34
...
>>>
```

Take the above statements and add them to your `report.py` program.
Have your program take the output of the `make_report()` function and print a nicely formatted table as shown.

### Exercise 2.11: Adding some headers

Suppose you had a tuple of header names like this:

```python
headers = ('Name', 'Shares', 'Price', 'Change')
```

Add code to your program that takes the above tuple of headers and
creates a string where each header name is right-aligned in a
10-character wide field and each field is separated by a single space.

```python
'      Name     Shares      Price      Change'
```

Write code that takes the headers and creates the separator string between the headers and data to follow.
This string is just a bunch of "-" characters under each field name. For example:

```python
'---------- ---------- ---------- -----------'
```

When you’re done, your program should produce the table shown at the top of this exercise.

```
      Name     Shares      Price     Change
---------- ---------- ---------- ----------
        AA        100       9.22     -22.98
       IBM         50     106.28      15.18
       CAT        150      35.46     -47.98
      MSFT        200      20.89     -30.34
        GE         95      13.48     -26.89
      MSFT         50      20.89     -44.21
       IBM        100     106.28      35.84
```

### Exercise 2.12: Formatting Challenge

How would you modify your code so that the price includes the currency symbol ($) and the output looks like this:

```
      Name     Shares      Price     Change
---------- ---------- ---------- ----------
        AA        100      $9.22     -22.98
       IBM         50    $106.28      15.18
       CAT        150     $35.46     -47.98
      MSFT        200     $20.89     -30.34
        GE         95     $13.48     -26.89
      MSFT         50     $20.89     -44.21
       IBM        100    $106.28      35.84
```

[Contents](../Contents.md) \| [Previous (2.2 Containers)](02_Containers.md) \| [Next (2.4 Sequences)](04_Sequences.md)


================================================
FILE: Notes/02_Working_with_data/04_Sequences.md
================================================
[Contents](../Contents.md) \| [Previous (2.3 Formatting)](03_Formatting.md) \| [Next (2.5 Collections)](05_Collections.md)

# 2.4 Sequences

### Sequence Datatypes

Python has three *sequence* datatypes.

* String: `'Hello'`. A string is a sequence of characters.
* List: `[1, 4, 5]`.
* Tuple: `('GOOG', 100, 490.1)`.

All sequences are ordered, indexed by integers, and have a length.

```python
a = 'Hello'               # String
b = [1, 4, 5]             # List
c = ('GOOG', 100, 490.1)  # Tuple

# Indexed order
a[0]                      # 'H'
b[-1]                     # 5
c[1]                      # 100

# Length of sequence
len(a)                    # 5
len(b)                    # 3
len(c)                    # 3
```

Sequences can be replicated: `s * n`.

```python
>>> a = 'Hello'
>>> a * 3
'HelloHelloHello'
>>> b = [1, 2, 3]
>>> b * 2
[1, 2, 3, 1, 2, 3]
>>>
```

Sequences of the same type can be concatenated: `s + t`.

```python
>>> a = (1, 2, 3)
>>> b = (4, 5)
>>> a + b
(1, 2, 3, 4, 5)
>>>
>>> c = [1, 5]
>>> a + c
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate tuple (not "list") to tuple
```

### Slicing

Slicing means to take a subsequence from a sequence.
The syntax is `s[start:end]`. Where `start` and `end` are the indexes of the subsequence you want.

```python
a = [0,1,2,3,4,5,6,7,8]

a[2:5]    # [2,3,4]
a[-5:]    # [4,5,6,7,8]
a[:3]     # [0,1,2]
```

* Indices `start` and `end` must be integers.
* Slices do *not* include the end value. It is like a half-open interval from math.
* If indices are omitted, they default to the beginning or end of the list.

### Slice re-assignment

On lists, slices can be reassigned and deleted.

```python
# Reassignment
a = [0,1,2,3,4,5,6,7,8]
a[2:4] = [10,11,12]       # [0,1,10,11,12,4,5,6,7,8]
```

*Note: The reassigned slice doesn't need to have the same length.*

```python
# Deletion
a = [0,1,2,3,4,5,6,7,8]
del a[2:4]                # [0,1,4,5,6,7,8]
```

### Sequence Reductions

There are some common functions to reduce a sequence to a single value.

```python
>>> s = [1, 2, 3, 4]
>>> sum(s)
10
>>> min(s)
1
>>> max(s)
4
>>> t = ['Hello', 'World']
>>> max(t)
'World'
>>>
```

### Iteration over a sequence

The for-loop iterates over the elements in a sequence.

```python
>>> s = [1, 4, 9, 16]
>>> for i in s:
...     print(i)
...
1
4
9
16
>>>
```

On each iteration of the loop, you get a new item to work with.
This new value is placed into the iteration variable. In this example, the
iteration variable is `x`:

```python
for x in s:         # `x` is an iteration variable
    ...statements
```

On each iteration, the previous value of the iteration variable is overwritten (if any).
After the loop finishes, the variable retains the last value.

### break statement

You can use the `break` statement to break out of a loop early.

```python
for name in namelist:
    if name == 'Jake':
        break
    ...
    ...
statements
```

When the `break` statement executes, it exits the loop and moves
on the next `statements`.  The `break` statement only applies to the
inner-most loop. If this loop is within another loop, it will not
break the outer loop.

### continue statement

To skip one element and move to the next one, use the `continue` statement.

```python
for line in lines:
    if line == '\n':    # Skip blank lines
        continue
    # More statements
    ...
```

This is useful when the current item is not of interest or needs to be ignored in the processing.

### Looping over integers

If you need to count, use `range()`.

```python
for i in range(100):
    # i = 0,1,...,99
```

The syntax is `range([start,] end [,step])`

```python
for i in range(100):
    # i = 0,1,...,99
for j in range(10,20):
    # j = 10,11,..., 19
for k in range(10,50,2):
    # k = 10,12,...,48
    # Notice how it counts in steps of 2, not 1.
```

* The ending value is never included. It mirrors the behavior of slices.
* `start` is optional. Default `0`.
* `step` is optional. Default `1`.
* `range()` computes values as needed. It does not actually store a large range of numbers.

### enumerate() function

The `enumerate` function adds an extra counter value to iteration.

```python
names = ['Elwood', 'Jake', 'Curtis']
for i, name in enumerate(names):
    # Loops with i = 0, name = 'Elwood'
    # i = 1, name = 'Jake'
    # i = 2, name = 'Curtis'
```

The general form is `enumerate(sequence [, start = 0])`. `start` is optional.
A good example of using `enumerate()` is tracking line numbers while reading a file:

```python
with open(filename) as f:
    for lineno, line in enumerate(f, start=1):
        ...
```

In the end, `enumerate` is just a nice shortcut for:

```python
i = 0
for x in s:
    statements
    i += 1
```

Using `enumerate` is less typing and runs slightly faster.

### For and tuples

You can iterate with multiple iteration variables.

```python
points = [
  (1, 4),(10, 40),(23, 14),(5, 6),(7, 8)
]
for x, y in points:
    # Loops with x = 1, y = 4
    #            x = 10, y = 40
    #            x = 23, y = 14
    #            ...
```

When using multiple variables, each tuple is *unpacked* into a set of iteration variables.
The number of variables must match the number of items in each tuple.

### zip() function

The `zip` function takes multiple sequences and makes an iterator that combines them.

```python
columns = ['name', 'shares', 'price']
values = ['GOOG', 100, 490.1 ]
pairs = zip(columns, values)
# ('name','GOOG'), ('shares',100), ('price',490.1)
```

To get the result you must iterate. You can use multiple variables to unpack the tuples as shown earlier.

```python
for column, value in pairs:
    ...
```

A common use of `zip` is to create key/value pairs for constructing dictionaries.

```python
d = dict(zip(columns, values))
```

## Exercises

### Exercise 2.13: Counting

Try some basic counting examples:

```python
>>> for n in range(10):            # Count 0 ... 9
        print(n, end=' ')

0 1 2 3 4 5 6 7 8 9
>>> for n in range(10,0,-1):       # Count 10 ... 1
        print(n, end=' ')

10 9 8 7 6 5 4 3 2 1
>>> for n in range(0,10,2):        # Count 0, 2, ... 8
        print(n, end=' ')

0 2 4 6 8
>>>
```

### Exercise 2.14: More sequence operations

Interactively experiment with some of the sequence reduction operations.

```python
>>> data = [4, 9, 1, 25, 16, 100, 49]
>>> min(data)
1
>>> max(data)
100
>>> sum(data)
204
>>>
```

Try looping over the data.

```python
>>> for x in data:
        print(x)

4
9
...
>>> for n, x in enumerate(data):
        print(n, x)

0 4
1 9
2 1
...
>>>
```

Sometimes the `for` statement, `len()`, and `range()` get used by
novices in some kind of horrible code fragment that looks like it
emerged from the depths of a rusty C program.

```python
>>> for n in range(len(data)):
        print(data[n])

4
9
1
...
>>>
```

Don’t do that! Not only does reading it make everyone’s eyes bleed,
it’s inefficient with memory and it runs a lot slower.  Just use a
normal `for` loop if you want to iterate over data.  Use `enumerate()`
if you happen to need the index for some reason.

### Exercise 2.15: A practical enumerate() example

Recall that the file `Data/missing.csv` contains data for a stock
portfolio, but has some rows with missing data.  Using `enumerate()`,
modify your `pcost.py` program so that it prints a line number with
the warning message when it encounters bad input.

```python
>>> cost = portfolio_cost('Data/missing.csv')
Row 4: Couldn't convert: ['MSFT', '', '51.23']
Row 7: Couldn't convert: ['IBM', '', '70.44']
>>>
```

To do this, you’ll need to change a few parts of your code.

```python
...
for rowno, row in enumerate(rows, start=1):
    try:
        ...
    except ValueError:
        print(f'Row {rowno}: Bad row: {row}')
```

### Exercise 2.16: Using the zip() function

In the file `Data/portfolio.csv`, the first line contains column
headers. In all previous code, we’ve been discarding them.

```python
>>> f = open('Data/portfolio.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> headers
['name', 'shares', 'price']
>>>
```

However, what if you could use the headers for something useful? This
is where the `zip()` function enters the picture.  First try this to
pair the file headers with a row of data:

```python
>>> row = next(rows)
>>> row
['AA', '100', '32.20']
>>> list(zip(headers, row))
[ ('name', 'AA'), ('shares', '100'), ('price', '32.20') ]
>>>
```

Notice how `zip()` paired the column headers with the column values.
We’ve used `list()` here to turn the result into a list so that you
can see it. Normally, `zip()` creates an iterator that must be
consumed by a for-loop.

This pairing is an intermediate step to building a
dictionary. Now try this:

```python
>>> record = dict(zip(headers, row))
>>> record
{'price': '32.20', 'name': 'AA', 'shares': '100'}
>>>
```

This transformation is one of the most useful tricks to know about
when processing a lot of data files.  For example, suppose you wanted
to make the `pcost.py` program work with various input files, but
without regard for the actual column number where the name, shares,
and price appear.

Modify the `portfolio_cost()` function in `pcost.py` so that it looks like this:

```python
# pcost.py

def portfolio_cost(filename):
    ...
        for rowno, row in enumerate(rows, start=1):
            record = dict(zip(headers, row))
            try:
                nshares = int(record['shares'])
                price = float(record['price'])
                total_cost += nshares * price
            # This catches errors in int() and float() conversions above
            except ValueError:
                print(f'Row {rowno}: Bad row: {row}')
        ...
```

Now, try your function on a completely different data file
`Data/portfoliodate.csv` which looks like this:

```csv
name,date,time,shares,price
"AA","6/11/2007","9:50am",100,32.20
"IBM","5/13/2007","4:20pm",50,91.10
"CAT","9/23/2006","1:30pm",150,83.44
"MSFT","5/17/2007","10:30am",200,51.23
"GE","2/1/2006","10:45am",95,40.37
"MSFT","10/31/2006","12:05pm",50,65.10
"IBM","7/9/2006","3:15pm",100,70.44
```

```python
>>> portfolio_cost('Data/portfoliodate.csv')
44671.15
>>>
```

If you did it right, you’ll find that your program still works even
though the data file has a completely different column format than
before. That’s cool!

The change made here is subtle, but significant.  Instead of
`portfolio_cost()` being hardcoded to read a single fixed file format,
the new version reads any CSV file and picks the values of interest
out of it.  As long as the file has the required columns, the code will work.

Modify the `report.py` program you wrote in Section 2.3 so that it uses
the same technique to pick out column headers.

Try running the `report.py` program on the `Data/portfoliodate.csv`
file and see that it produces the same answer as before.

### Exercise 2.17: Inverting a dictionary

A dictionary maps keys to values. For example, a dictionary of stock prices.

```python
>>> prices = {
        'GOOG' : 490.1,
        'AA' : 23.45,
        'IBM' : 91.1,
        'MSFT' : 34.23
    }
>>>
```

If you use the `items()` method, you can get `(key,value)` pairs:

```python
>>> prices.items()
dict_items([('GOOG', 490.1), ('AA', 23.45), ('IBM', 91.1), ('MSFT', 34.23)])
>>>
```

However, what if you wanted to get a list of `(value, key)` pairs instead?
*Hint: use `zip()`.*

```python
>>> pricelist = list(zip(prices.values(),prices.keys()))
>>> pricelist
[(490.1, 'GOOG'), (23.45, 'AA'), (91.1, 'IBM'), (34.23, 'MSFT')]
>>>
```

Why would you do this? For one, it allows you to perform certain kinds
of data processing on the dictionary data.

```python
>>> min(pricelist)
(23.45, 'AA')
>>> max(pricelist)
(490.1, 'GOOG')
>>> sorted(pricelist)
[(23.45, 'AA'), (34.23, 'MSFT'), (91.1, 'IBM'), (490.1, 'GOOG')]
>>>
```

This also illustrates an important feature of tuples. When used in
comparisons, tuples are compared element-by-element starting with the
first item. Similar to how strings are compared
character-by-character.

`zip()` is often used in situations like this where you need to pair
up data from different places.  For example, pairing up the column
names with column values in order to make a dictionary of named
values.

Note that `zip()` is not limited to pairs. For example, you can use it
with any number of input lists:

```python
>>> a = [1, 2, 3, 4]
>>> b = ['w', 'x', 'y', 'z']
>>> c = [0.2, 0.4, 0.6, 0.8]
>>> list(zip(a, b, c))
[(1, 'w', 0.2), (2, 'x', 0.4), (3, 'y', 0.6), (4, 'z', 0.8))]
>>>
```

Also, be aware that `zip()` stops once the shortest input sequence is exhausted.

```python
>>> a = [1, 2, 3, 4, 5, 6]
>>> b = ['x', 'y', 'z']
>>> list(zip(a,b))
[(1, 'x'), (2, 'y'), (3, 'z')]
>>>
```

[Contents](../Contents.md) \| [Previous (2.3 Formatting)](03_Formatting.md) \| [Next (2.5 Collections)](05_Collections.md)


================================================
FILE: Notes/02_Working_with_data/05_Collections.md
================================================
[Contents](../Contents.md) \| [Previous (2.4 Sequences)](04_Sequences.md) \| [Next (2.6 List Comprehensions)](06_List_comprehension.md)

# 2.5 collections module

The `collections` module provides a number of useful objects for data handling.
This part briefly introduces some of these features.

### Example: Counting Things

Let's say you want to tabulate the total shares of each stock.

```python
portfolio = [
    ('GOOG', 100, 490.1),
    ('IBM', 50, 91.1),
    ('CAT', 150, 83.44),
    ('IBM', 100, 45.23),
    ('GOOG', 75, 572.45),
    ('AA', 50, 23.15)
]
```

There are two `IBM` entries and two `GOOG` entries in this list. The shares need to be combined together somehow.

### Counters

Solution: Use a `Counter`.

```python
from collections import Counter
total_shares = Counter()
for name, shares, price in portfolio:
    total_shares[name] += shares

total_shares['IBM']     # 150
```

### Example: One-Many Mappings

Problem: You want to map a key to multiple values.

```python
portfolio = [
    ('GOOG', 100, 490.1),
    ('IBM', 50, 91.1),
    ('CAT', 150, 83.44),
    ('IBM', 100, 45.23),
    ('GOOG', 75, 572.45),
    ('AA', 50, 23.15)
]
```

Like in the previous example, the key `IBM` should have two different tuples instead.

Solution: Use a `defaultdict`.

```python
from collections import defaultdict
holdings = defaultdict(list)
for name, shares, price in portfolio:
    holdings[name].append((shares, price))
holdings['IBM'] # [ (50, 91.1), (100, 45.23) ]
```

The `defaultdict` ensures that every time you access a key you get a default value.

### Example: Keeping a History

Problem: We want a history of the last N things.
Solution: Use a `deque`.

```python
from collections import deque

history = deque(maxlen=N)
with open(filename) as f:
    for line in f:
        history.append(line)
        ...
```

## Exercises

The `collections` module might be one of the most useful library
modules for dealing with special purpose kinds of data handling
problems such as tabulating and indexing.

In this exercise, we’ll look at a few simple examples.  Start by
running your `report.py` program so that you have the portfolio of
stocks loaded in the interactive mode.

```bash
bash % python3 -i report.py
```

### Exercise 2.18: Tabulating with Counters

Suppose you wanted to tabulate the total number of shares of each stock.
This is easy using `Counter` objects. Try it:

```python
>>> portfolio = read_portfolio('Data/portfolio.csv')
>>> from collections import Counter
>>> holdings = Counter()
>>> for s in portfolio:
        holdings[s['name']] += s['shares']

>>> holdings
Counter({'MSFT': 250, 'IBM': 150, 'CAT': 150, 'AA': 100, 'GE': 95})
>>>
```

Carefully observe how the multiple entries for `MSFT` and `IBM` in `portfolio` get combined into a single entry here.

You can use a Counter just like a dictionary to retrieve individual values:

```python
>>> holdings['IBM']
150
>>> holdings['MSFT']
250
>>>
```

If you want to rank the values, do this:

```python
>>> # Get three most held stocks
>>> holdings.most_common(3)
[('MSFT', 250), ('IBM', 150), ('CAT', 150)]
>>>
```

Let’s grab another portfolio of stocks and make a new Counter:

```python
>>> portfolio2 = read_portfolio('Data/portfolio2.csv')
>>> holdings2 = Counter()
>>> for s in portfolio2:
          holdings2[s['name']] += s['shares']

>>> holdings2
Counter({'HPQ': 250, 'GE': 125, 'AA': 50, 'MSFT': 25})
>>>
```

Finally, let’s combine all of the holdings doing one simple operation:

```python
>>> holdings
Counter({'MSFT': 250, 'IBM': 150, 'CAT': 150, 'AA': 100, 'GE': 95})
>>> holdings2
Counter({'HPQ': 250, 'GE': 125, 'AA': 50, 'MSFT': 25})
>>> combined = holdings + holdings2
>>> combined
Counter({'MSFT': 275, 'HPQ': 250, 'GE': 220, 'AA': 150, 'IBM': 150, 'CAT': 150})
>>>
```

This is only a small taste of what counters provide. However, if you
ever find yourself needing to tabulate values, you should consider
using one.

### Commentary: collections module

The `collections` module is one of the most useful library modules
in all of Python.  In fact, we could do an extended tutorial on just
that.  However, doing so now would also be a distraction.  For now,
put `collections` on your list of bedtime reading for later.

[Contents](../Contents.md) \| [Previous (2.4 Sequences)](04_Sequences.md) \| [Next (2.6 List Comprehensions)](06_List_comprehension.md)

================================================
FILE: Notes/02_Working_with_data/06_List_comprehension.md
================================================
[Contents](../Contents.md) \| [Previous (2.5 Collections)](05_Collections.md) \| [Next (2.7 Object Model)](07_Objects.md)

# 2.6 List Comprehensions

A common task is processing items in a list.  This section introduces list comprehensions,
a powerful tool for doing just that.

### Creating new lists

A list comprehension creates a new list by applying an operation to
each element of a sequence.

```python
>>> a = [1, 2, 3, 4, 5]
>>> b = [2*x for x in a ]
>>> b
[2, 4, 6, 8, 10]
>>>
```

Another example:

```python
>>> names = ['Elwood', 'Jake']
>>> a = [name.lower() for name in names]
>>> a
['elwood', 'jake']
>>>
```

The general syntax is: `[ <expression> for <variable_name> in <sequence> ]`.

### Filtering

You can also filter during the list comprehension.

```python
>>> a = [1, -5, 4, 2, -2, 10]
>>> b = [2*x for x in a if x > 0 ]
>>> b
[2, 8, 4, 20]
>>>
```

### Use cases

List comprehensions are hugely useful.  For example, you can collect values of a specific
dictionary fields:

```python
stocknames = [s['name'] for s in stocks]
```

You can perform database-like queries on sequences.

```python
a = [s for s in stocks if s['price'] > 100 and s['shares'] > 50 ]
```

You can also combine a list comprehension with a sequence reduction:

```python
cost = sum([s['shares']*s['price'] for s in stocks])
```

### General Syntax

```code
[ <expression> for <variable_name> in <sequence> if <condition>]
```

What it means:

```python
result = []
for variable_name in sequence:
    if condition:
        result.append(expression)
```

### Historical Digression

List comprehensions come from math (set-builder notation).

```code
a = [ x * x for x in s if x > 0 ] # Python

a = { x^2 | x ∈ s, x > 0 }         # Math
```

It is also implemented in several other languages. Most
coders probably aren't thinking about their math class though. So,
it's fine to view it as a cool list shortcut.

## Exercises

Start by running your `report.py` program so that you have the
portfolio of stocks loaded in the interactive mode.

```bash
bash % python3 -i report.py
```

Now, at the Python interactive prompt, type statements to perform the
operations described below.  These operations perform various kinds of
data reductions, transforms, and queries on the portfolio data.

### Exercise 2.19: List comprehensions

Try a few simple list comprehensions just to become familiar with the syntax.

```python
>>> nums = [1,2,3,4]
>>> squares = [ x * x for x in nums ]
>>> squares
[1, 4, 9, 16]
>>> twice = [ 2 * x for x in nums if x > 2 ]
>>> twice
[6, 8]
>>>
```

Notice how the list comprehensions are creating a new list with the
data suitably transformed or filtered.

### Exercise 2.20: Sequence Reductions

Compute the total cost of the portfolio using a single Python statement.

```python
>>> portfolio = read_portfolio('Data/portfolio.csv')
>>> cost = sum([ s['shares'] * s['price'] for s in portfolio ])
>>> cost
44671.15
>>>
```

After you have done that, show how you can compute the current value
of the portfolio using a single statement.

```python
>>> value = sum([ s['shares'] * prices[s['name']] for s in portfolio ])
>>> value
28686.1
>>>
```

Both of the above operations are an example of a map-reduction. The
list comprehension is mapping an operation across the list.

```python
>>> [ s['shares'] * s['price'] for s in portfolio ]
[3220.0000000000005, 4555.0, 12516.0, 10246.0, 3835.1499999999996, 3254.9999999999995, 7044.0]
>>>
```

The `sum()` function is then performing a reduction across the result:

```python
>>> sum(_)
44671.15
>>>
```

With this knowledge, you are now ready to go launch a big-data startup company.

### Exercise 2.21: Data Queries

Try the following examples of various data queries.

First, a list of all portfolio holdings with more than 100 shares.

```python
>>> more100 = [ s for s in portfolio if s['shares'] > 100 ]
>>> more100
[{'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 51.23, 'name': 'MSFT', 'shares': 200}]
>>>
```

All portfolio holdings for MSFT and IBM stocks.

```python
>>> msftibm = [ s for s in portfolio if s['name'] in {'MSFT','IBM'} ]
>>> msftibm
[{'price': 91.1, 'name': 'IBM', 'shares': 50}, {'price': 51.23, 'name': 'MSFT', 'shares': 200},
  {'price': 65.1, 'name': 'MSFT', 'shares': 50}, {'price': 70.44, 'name': 'IBM', 'shares': 100}]
>>>
```

A list of all portfolio holdings that cost more than $10000.

```python
>>> cost10k = [ s for s in portfolio if s['shares'] * s['price'] > 10000 ]
>>> cost10k
[{'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 51.23, 'name': 'MSFT', 'shares': 200}]
>>>
```

### Exercise 2.22: Data Extraction

Show how you could build a list of tuples `(name, shares)` where `name` and `shares` are taken from `portfolio`.

```python
>>> name_shares =[ (s['name'], s['shares']) for s in portfolio ]
>>> name_shares
[('AA', 100), ('IBM', 50), ('CAT', 150), ('MSFT', 200), ('GE', 95), ('MSFT', 50), ('IBM', 100)]
>>>
```

If you change the square brackets (`[`,`]`) to curly braces (`{`, `}`), you get something known as a set comprehension.
This gives you unique or distinct values.

For example, this determines the set of unique stock names that appear in `portfolio`:

```python
>>> names = { s['name'] for s in portfolio }
>>> names
{ 'AA', 'GE', 'IBM', 'MSFT', 'CAT' }
>>>
```

If you specify `key:value` pairs, you can build a dictionary.
For example, make a dictionary that maps the name of a stock to the total number of shares held.

```python
>>> holdings = { name: 0 for name in names }
>>> holdings
{'AA': 0, 'GE': 0, 'IBM': 0, 'MSFT': 0, 'CAT': 0}
>>>
```

This latter feature is known as a **dictionary comprehension**. Let’s tabulate:

```python
>>> for s in portfolio:
        holdings[s['name']] += s['shares']

>>> holdings
{ 'AA': 100, 'GE': 95, 'IBM': 150, 'MSFT':250, 'CAT': 150 }
>>>
```

Try this example that filters the `prices` dictionary down to only
those names that appear in the portfolio:

```python
>>> portfolio_prices = { name: prices[name] for name in names }
>>> portfolio_prices
{'AA': 9.22, 'GE': 13.48, 'IBM': 106.28, 'MSFT': 20.89, 'CAT': 35.46}
>>>
```

### Exercise 2.23: Extracting Data From CSV Files

Knowing how to use various combinations of list, set, and dictionary
comprehensions can be useful in various forms of data processing.
Here’s an example that shows how to extract selected columns from a
CSV file.

First, read a row of header information from a CSV file:

```python
>>> import csv
>>> f = open('Data/portfoliodate.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> headers
['name', 'date', 'time', 'shares', 'price']
>>>
```

Next, define a variable that lists the columns that you actually care about:

```python
>>> select = ['name', 'shares', 'price']
>>>
```

Now, locate the indices of the above columns in the source CSV file:

```python
>>> indices = [ headers.index(colname) for colname in select ]
>>> indices
[0, 3, 4]
>>>
```

Finally, read a row of data and turn it into a dictionary using a
dictionary comprehension:

```python
>>> row = next(rows)
>>> record = { colname: row[index] for colname, index in zip(select, indices) }   # dict-comprehension
>>> record
{'price': '32.20', 'name': 'AA', 'shares': '100'}
>>>
```

If you’re feeling comfortable with what just happened, read the rest
of the file:

```python
>>> portfolio = [ { colname: row[index] for colname, index in zip(select, indices) } for row in rows ]
>>> portfolio
[{'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'},
  {'price': '51.23', 'name': 'MSFT', 'shares': '200'}, {'price': '40.37', 'name': 'GE', 'shares': '95'},
  {'price': '65.10', 'name': 'MSFT', 'shares': '50'}, {'price': '70.44', 'name': 'IBM', 'shares': '100'}]
>>>
```

Oh my, you just reduced much of the `read_portfolio()` function to a single statement.

### Commentary

List comprehensions are commonly used in Python as an efficient means
for transforming, filtering, or collecting data.  Due to the syntax,
you don’t want to go overboard—try to keep each list comprehension as
simple as possible.  It’s okay to break things into multiple
steps. For example, it’s not clear that you would want to spring that
last example on your unsuspecting co-workers.

That said, knowing how to quickly manipulate data is a skill that’s
incredibly useful.  There are numerous situations where you might have
to solve some kind of one-off problem involving data imports, exports,
extraction, and so forth.  Becoming a guru master of list
comprehensions can substantially reduce the time spent devising a
solution.  Also, don't forget about the `collections` module.

[Contents](../Contents.md) \| [Previous (2.5 Collections)](05_Collections.md) \| [Next (2.7 Object Model)](07_Objects.md)


================================================
FILE: Notes/02_Working_with_data/07_Objects.md
================================================
[Contents](../Contents.md) \| [Previous (2.6 List Comprehensions)](06_List_comprehension.md) \| [Next (3 Program Organization)](../03_Program_organization/00_Overview.md)

# 2.7 Objects

This section introduces more details about Python's internal object model and
discusses some matters related to memory management, copying, and type checking.

### Assignment

Many operations in Python are related to *assigning* or *storing* values.

```python
a = value         # Assignment to a variable
s[n] = value      # Assignment to a list
s.append(value)   # Appending to a list
d['key'] = value  # Adding to a dictionary
```

*A caution: assignment operations **never make a copy** of the value being assigned.*
All assignments are merely reference copies (or pointer copies if you prefer).

### Assignment example

Consider this code fragment.

```python
a = [1,2,3]
b = a
c = [a,b]
```

A picture of the underlying memory operations. In this example, there
is only one list object `[1,2,3]`, but there are four different
references to it.

![References](references.png)

This means that modifying a value affects *all* references.

```python
>>> a.append(999)
>>> a
[1,2,3,999]
>>> b
[1,2,3,999]
>>> c
[[1,2,3,999], [1,2,3,999]]
>>>
```

Notice how a change in the original list shows up everywhere else
(yikes!).  This is because no copies were ever made. Everything is
pointing to the same thing.

### Reassigning values

Reassigning a value *never* overwrites the memory used by the previous value.

```python
a = [1,2,3]
b = a
a = [4,5,6]

print(a)      # [4, 5, 6]
print(b)      # [1, 2, 3]    Holds the original value
```

Remember: **Variables are names, not memory locations.**

### Some Dangers

If you don't know about this sharing, you will shoot yourself in the
foot at some point.  Typical scenario. You modify some data thinking
that it's your own private copy and it accidentally corrupts some data
in some other part of the program.

*Comment: This is one of the reasons why the primitive datatypes (int,
 float, string) are immutable (read-only).*

### Identity and References

Use the `is` operator to check if two values are exactly the same object.

```python
>>> a = [1,2,3]
>>> b = a
>>> a is b
True
>>>
```

`is` compares the object identity (an integer).  The identity can be
obtained using `id()`.

```python
>>> id(a)
3588944
>>> id(b)
3588944
>>>
```

Note: It is almost always better to use `==` for checking objects.  The behavior
of `is` is often unexpected:

```python
>>> a = [1,2,3]
>>> b = a
>>> c = [1,2,3]
>>> a is b
True
>>> a is c
False
>>> a == c
True
>>>
```

### Shallow copies

Lists and dicts have methods for copying.

```python
>>> a = [2,3,[100,101],4]
>>> b = list(a) # Make a copy
>>> a is b
False
```

It's a new list, but the list items are shared.

```python
>>> a[2].append(102)
>>> b[2]
[100,101,102]
>>>
>>> a[2] is b[2]
True
>>>
```

For example, the inner list `[100, 101, 102]` is being shared.
This is known as a shallow copy.  Here is a picture.

![Shallow copy](shallow.png)

### Deep copies

Sometimes you need to make a copy of an object and all the objects contained within it.
You can use the `copy` module for this:

```python
>>> a = [2,3,[100,101],4]
>>> import copy
>>> b = copy.deepcopy(a)
>>> a[2].append(102)
>>> b[2]
[100,101]
>>> a[2] is b[2]
False
>>>
```

### Names, Values, Types

Variable names do not have a *type*. It's only a name.
However, values *do* have an underlying type.

```python
>>> a = 42
>>> b = 'Hello World'
>>> type(a)
<type 'int'>
>>> type(b)
<type 'str'>
```

`type()` will tell you what it is. The type name is usually used as a function
that creates or converts a value to that type.

### Type Checking

How to tell if an object is a specific type.

```python
if isinstance(a, list):
    print('a is a list')
```

Checking for one of many possible types.

```python
if isinstance(a, (list,tuple)):
    print('a is a list or tuple')
```

*Caution: Don't go overboard with type checking. It can lead to
excessive code complexity.  Usually you'd only do it if doing
so would prevent common mistakes made by others using your code.
*

### Everything is an object

Numbers, strings, lists, functions, exceptions, classes, instances,
etc. are all objects.  It means that all objects that can be named can
be passed around as data, placed in containers, etc., without any
restrictions.  There are no *special* kinds of objects.  Sometimes it
is said that all objects are "first-class".

A simple example:

```python
>>> import math
>>> items = [abs, math, ValueError ]
>>> items
[<built-in function abs>,
  <module 'math' (builtin)>,
  <type 'exceptions.ValueError'>]
>>> items[0](-45)
45
>>> items[1].sqrt(2)
1.4142135623730951
>>> try:
        x = int('not a number')
    except items[2]:
        print('Failed!')
Failed!
>>>
```

Here, `items` is a list containing a function, a module and an
exception.  You can directly use the items in the list in place of the
original names:

```python
items[0](-45)       # abs
items[1].sqrt(2)    # math
except items[2]:    # ValueError
```

With great power comes responsibility.  Just because you can do that doesn't mean you should.

## Exercises

In this set of exercises, we look at some of the power that comes from first-class
objects.

### Exercise 2.24: First-class Data

In the file `Data/portfolio.csv`, we read data organized as columns that look like this:

```csv
name,shares,price
"AA",100,32.20
"IBM",50,91.10
...
```

In previous code, we used the `csv` module to read the file, but still
had to perform manual type conversions. For example:

```python
for row in rows:
    name   = row[0]
    shares = int(row[1])
    price  = float(row[2])
```

This kind of conversion can also be performed in a more clever manner
using some list basic operations.

Make a Python list that contains the names of the conversion functions
you would use to convert each column into the appropriate type:

```python
>>> types = [str, int, float]
>>>
```

The reason you can even create this list is that everything in Python
is *first-class*.  So, if you want to have a list of functions, that’s
fine.  The items in the list you created are functions for converting
a value `x` into a given type (e.g., `str(x)`, `int(x)`, `float(x)`).

Now, read a row of data from the above file:

```python
>>> import csv
>>> f = open('Data/portfolio.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> row = next(rows)
>>> row
['AA', '100', '32.20']
>>>
```

As noted, this row isn’t enough to do calculations because the types
are wrong. For example:

```python
>>> row[1] * row[2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'str'
>>>
```

However, maybe the data can be paired up with the types you specified
in `types`. For example:

```python
>>> types[1]
<type 'int'>
>>> row[1]
'100'
>>>
```

Try converting one of the values:

```python
>>> types[1](row[1])     # Same as int(row[1])
100
>>>
```

Try converting a different value:

```python
>>> types[2](row[2])     # Same as float(row[2])
32.2
>>>
```

Try the calculation with converted values:

```python
>>> types[1](row[1])*types[2](row[2])
3220.0000000000005
>>>
```

Zip the column types with the fields and look at the result:

```python
>>> r = list(zip(types, row))
>>> r
[(<type 'str'>, 'AA'), (<type 'int'>, '100'), (<type 'float'>,'32.20')]
>>>
```

You will notice that this has paired a type conversion with a
value. For example, `int` is paired with the value `'100'`.

The zipped list is useful if you want to perform conversions on all of
the values, one after the other. Try this:

```python
>>> converted = []
>>> for func, val in zip(types, row):
          converted.append(func(val))
...
>>> converted
['AA', 100, 32.2]
>>> converted[1] * converted[2]
3220.0000000000005
>>>
```

Make sure you understand what’s happening in the above code.  In the
loop, the `func` variable is one of the type conversion functions
(e.g., `str`, `int`, etc.) and the `val` variable is one of the values
like `'AA'`, `'100'`.  The expression `func(val)` is converting a
value (kind of like a type cast).

The above code can be compressed into a single list comprehension.

```python
>>> converted = [func(val) for func, val in zip(types, row)]
>>> converted
['AA', 100, 32.2]
>>>
```

### Exercise 2.25: Making dictionaries

Remember how the `dict()` function can easily make a dictionary if you
have a sequence of key names and values?  Let’s make a dictionary from
the column headers:

```python
>>> headers
['name', 'shares', 'price']
>>> converted
['AA', 100, 32.2]
>>> dict(zip(headers, converted))
{'price': 32.2, 'name': 'AA', 'shares': 100}
>>>
```

Of course, if you’re up on your list-comprehension fu, you can do the
whole conversion in a single step using a dict-comprehension:

```python
>>> { name: func(val) for name, func, val in zip(headers, types, row) }
{'price': 32.2, 'name': 'AA', 'shares': 100}
>>>
```

### Exercise 2.26: The Big Picture

Using the techniques in this exercise, you could write statements that
easily convert fields from just about any column-oriented datafile
into a Python dictionary.

Just to illustrate, suppose you read data from a different datafile like this:

```python
>>> f = open('Data/dowstocks.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> row = next(rows)
>>> headers
['name', 'price', 'date', 'time', 'change', 'open', 'high', 'low', 'volume']
>>> row
['AA', '39.48', '6/11/2007', '9:36am', '-0.18', '39.67', '39.69', '39.45', '181800']
>>>
```

Let’s convert the fields using a similar trick:

```python
>>> types = [str, float, str, str, float, float, float, float, int]
>>> converted = [func(val) for func, val in zip(types, row)]
>>> record = dict(zip(headers, converted))
>>> record
{'volume': 181800, 'name': 'AA', 'price': 39.48, 'high': 39.69,
'low': 39.45, 'time': '9:36am', 'date': '6/11/2007', 'open': 39.67,
'change': -0.18}
>>> record['name']
'AA'
>>> record['price']
39.48
>>>
```

Bonus: How would you modify this example to additionally parse the
`date` entry into a tuple such as `(6, 11, 2007)`?

Spend some time to ponder what you’ve done in this exercise. We’ll
revisit these ideas a little later.

[Contents](../Contents.md) \| [Previous (2.6 List Comprehensions)](06_List_comprehension.md) \| [Next (3 Program Organization)](../03_Program_organization/00_Overview.md)


================================================
FILE: Notes/03_Program_organization/00_Overview.md
================================================
[Contents](../Contents.md) \| [Prev (2 Working With Data)](../02_Working_with_data/00_Overview.md) \| [Next (4 Classes and Objects)](../04_Classes_objects/00_Overview.md)

# 3. Program Organization

So far, we've learned some Python basics and have written some short scripts.
However, as you start to write larger programs, you'll want to get organized.
This section dives into greater details on writing functions, handling errors,
and introduces modules.  By the end you should be able to write programs
that are subdivided into functions across multiple files. We'll also give
some useful code templates for writing more useful scripts.

* [3.1 Functions and Script Writing](01_Script.md)
* [3.2 More Detail on Functions](02_More_functions.md)
* [3.3 Exception Handling](03_Error_checking.md)
* [3.4 Modules](04_Modules.md)
* [3.5 Main module](05_Main_module.md)
* [3.6 Design Discussion about Embracing Flexibility](06_Design_discussion.md)

[Contents](../Contents.md) \| [Prev (2 Working With Data)](../02_Working_with_data/00_Overview.md) \| [Next (4 Classes and Objects)](../04_Classes_objects/00_Overview.md)



================================================
FILE: Notes/03_Program_organization/01_Script.md
================================================
[Contents](../Contents.md) \| [Previous (2.7 Object Model)](../02_Working_with_data/07_Objects.md) \| [Next (3.2 More on Functions)](02_More_functions.md)

# 3.1 Scripting

In this part we look more closely at the practice of writing Python
scripts.

### What is a Script?

A *script* is a program that runs a series of statements and stops.

```python
# program.py

statement1
statement2
statement3
...
```

We have mostly been writing scripts to this point.

### A Problem

If you write a useful script, it will grow in features and
functionality.  You may want to apply it to other related problems.
Over time, it might become a critical application.  And if you don't
take care, it might turn into a huge tangled mess.  So, let's get
organized.

### Defining Things

Names must always be defined before they get used later.

```python
def square(x):
    return x*x

a = 42
b = a + 2     # Requires that `a` is defined

z = square(b) # Requires `square` and `b` to be defined
```

**The order is important.**
You almost always put the definitions of variables and functions near the top.

### Defining Functions

It is a good idea to put all of the code related to a single *task* all in one place.
Use a function.

```python
def read_prices(filename):
    prices = {}
    with open(filename) as f:
        f_csv = csv.reader(f)
        for row in f_csv:
            prices[row[0]] = float(row[1])
    return prices
```

A function also simplifies repeated operations.

```python
oldprices = read_prices('oldprices.csv')
newprices = read_prices('newprices.csv')
```

### What is a Function?

A function is a named sequence of statements.

```python
def funcname(args):
  statement
  statement
  ...
  return result
```

*Any* Python statement can be used inside.

```python
def foo():
    import math
    print(math.sqrt(2))
    help(math)
```

There are no *special* statements in Python (which makes it easy to remember).

### Function Definition

Functions can be *defined* in any order.

```python
def foo(x):
    bar(x)

def bar(x):
    statements

# OR
def bar(x):
    statements

def foo(x):
    bar(x)
```

Functions must only be defined prior to actually being *used* (or called) during program execution.

```python
foo(3)        # foo must be defined already
```

Stylistically, it is probably more common to see functions defined in
a *bottom-up* fashion.

### Bottom-up Style

Functions are treated as building blocks.
The smaller/simpler blocks go first.

```python
# myprogram.py
def foo(x):
    ...

def bar(x):
    ...
    foo(x)          # Defined above
    ...

def spam(x):
    ...
    bar(x)          # Defined above
    ...

spam(42)            # Code that uses the functions appears at the end
```

Later functions build upon earlier functions.  Again, this is only
a point of style.  The only thing that matters in the above program
is that the call to `spam(42)` go last.

### Function Design

Ideally, functions should be a *black box*.
They should only operate on passed inputs and avoid global variables
and mysterious side-effects.  Your main goals: *Modularity* and *Predictability*.

### Doc Strings

It's good practice to include documentation in the form of a
doc-string.  Doc-strings are strings written immediately after the
name of the function. They feed `help()`, IDEs and other tools.

```python
def read_prices(filename):
    '''
    Read prices from a CSV file of name,price data
    '''
    prices = {}
    with open(filename) as f:
        f_csv = csv.reader(f)
        for row in f_csv:
            prices[row[0]] = float(row[1])
    return prices
```

A good practice for doc strings is to write a short one sentence
summary of what the function does.  If more information is needed,
include a short example of usage along with a more detailed
description of the arguments.

### Type Annotations

You can also add optional type hints to function definitions.

```python
def read_prices(filename: str) -> dict:
    '''
    Read prices from a CSV file of name,price data
    '''
    prices = {}
    with open(filename) as f:
        f_csv = csv.reader(f)
        for row in f_csv:
            prices[row[0]] = float(row[1])
    return prices
```

The hints do nothing operationally. They are purely informational.
However, they may be used by IDEs, code checkers, and other tools
to do more.

## Exercises

In section 2, you wrote a program called `report.py` that printed out
a report showing the performance of a stock portfolio.  This program
consisted of some functions. For example:

```python
# report.py
import csv

def read_portfolio(filename):
    '''
    Read a stock portfolio file into a list of dictionaries with keys
    name, shares, and price.
    '''
    portfolio = []
    with open(filename) as f:
        rows = csv.reader(f)
        headers = next(rows)

        for row in rows:
            record = dict(zip(headers, row))
            stock = {
                'name' : record['name'],
                'shares' : int(record['shares']),
                'price' : float(record['price'])
            }
            portfolio.append(stock)
    return portfolio
...
```

However, there were also portions of the program that just performed a
series of scripted calculations.  This code appeared near the end of
the program. For example:

```python
...

# Output the report

headers = ('Name', 'Shares', 'Price', 'Change')
print('%10s %10s %10s %10s'  % headers)
print(('-' * 10 + ' ') * len(headers))
for row in report:
    print('%10s %10d %10.2f %10.2f' % row)
...
```

In this exercise, we’re going take this program and organize it a
little more strongly around the use of functions.

### Exercise 3.1: Structuring a program as a collection of functions

Modify your `report.py` program so that all major operations,
including calculations and output, are carried out by a collection of
functions. Specifically:

* Create a function `print_report(report)` that prints out the report.
* Change the last part of the program so that it is nothing more than a series of function calls and no other computation.

### Exercise 3.2: Creating a top-level function for program execution

Take the last part of your program and package it into a single
function `portfolio_report(portfolio_filename, prices_filename)`.
Have the function work so that the following function call creates the
report as before:

```python
portfolio_report('Data/portfolio.csv', 'Data/prices.csv')
```

In this final version, your program will be nothing more than a series
of function definitions followed by a single function call to
`portfolio_report()` at the very end (which executes all of the steps
involved in the program).

By turning your program into a single function, it becomes easy to run
it on different inputs.  For example, try these statements
interactively after running your program:

```python
>>> portfolio_report('Data/portfolio2.csv', 'Data/prices.csv')
... look at the output ...
>>> files = ['Data/portfolio.csv', 'Data/portfolio2.csv']
>>> for name in files:
        print(f'{name:-^43s}')
        portfolio_report(name, 'Data/prices.csv')
        print()

... look at the output ...
>>>
```

### Commentary

Python makes it very easy to write relatively unstructured scripting code
where you just have a file with a sequence of statements in it. In the
big picture, it's almost always better to utilize functions whenever
you can.  At some point, that script is going to grow and you'll wish
you had a bit more organization.  Also, a little known fact is that Python
runs a bit faster if you use functions.

[Contents](../Contents.md) \| [Previous (2.7 Object Model)](../02_Working_with_data/07_Objects.md) \| [Next (3.2 More on Functions)](02_More_functions.md)

================================================
FILE: Notes/03_Program_organization/02_More_functions.md
================================================
[Contents](../Contents.md) \| [Previous (3.1 Scripting)](01_Script.md) \| [Next (3.3 Error Checking)](03_Error_checking.md)

# 3.2 More on Functions

Although functions were introduced earlier, very few details were provided on how
they actually work at a deeper level.  This section aims to fill in some gaps
and discuss matters such as calling conventions, scoping rules, and more.

### Calling a Function

Consider this function:

```python
def read_prices(filename, debug):
    ...
```

You can call the function with positional arguments:

```
prices = read_prices('prices.csv', True)
```

Or you can call the function with keyword arguments:

```python
prices = read_prices(filename='prices.csv', debug=True)
```

### Default Arguments

Sometimes you want an argument to be optional.  If so, assign a default value
in the function definition.

```python
def read_prices(filename, debug=False):
    ...
```

If a default value is assigned, the argument is optional in function calls.

```python
d = read_prices('prices.csv')
e = read_prices('prices.dat', True)
```

*Note: Arguments with defaults must appear at the end of the arguments list (all non-optional arguments go first).*

### Prefer keyword arguments for optional arguments

Compare and contrast these two different calling styles:

```python
parse_data(data, False, True) # ?????

parse_data(data, ignore_errors=True)
parse_data(data, debug=True)
parse_data(data, debug=True, ignore_errors=True)
```

In most cases, keyword arguments improve code clarity--especially for arguments that
serve as flags or which are related to optional features.

### Design Best Practices

Always give short, but meaningful names to functions arguments.

Someone using a function may want to use the keyword calling style.

```python
d = read_prices('prices.csv', debug=True)
```

Python development tools will show the names in help features and documentation.

### Returning Values

The `return` statement returns a value

```python
def square(x):
    return x * x
```

If no return value is given or `return` is missing, `None` is returned.

```python
def bar(x):
    statements
    return

a = bar(4)      # a = None

# OR
def foo(x):
    statements  # No `return`

b = foo(4)      # b = None
```

### Multiple Return Values

Functions can only return one value.  However, a function may return
multiple values by returning them in a tuple.

```python
def divide(a,b):
    q = a // b      # Quotient
    r = a % b       # Remainder
    return q, r     # Return a tuple
```

Usage example:

```python
x, y = divide(37,5) # x = 7, y = 2

x = divide(37, 5)   # x = (7, 2)
```

### Variable Scope

Programs assign values to variables.

```python
x = value # Global variable

def foo():
    y = value # Local variable
```

Variables assignments occur outside and inside function definitions.
Variables defined outside are "global". Variables inside a function
are "local".

### Local Variables

Variables assigned inside functions are private.

```python
def read_portfolio(filename):
    portfolio = []
    for line in open(filename):
        fields = line.split(',')
        s = (fields[0], int(fields[1]), float(fields[2]))
        portfolio.append(s)
    return portfolio
```

In this example, `filename`, `portfolio`, `line`, `fields` and `s` are local variables.
Those variables are not retained or accessible after the function call.

```python
>>> stocks = read_portfolio('portfolio.csv')
>>> fields
Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: name 'fields' is not defined
>>>
```

Locals also can't conflict with variables found elsewhere.

### Global Variables

Functions can freely access the values of globals defined in the same
file.

```python
name = 'Dave'

def greeting():
    print('Hello', name)  # Using `name` global variable
```

However, functions can't modify globals:

```python
name = 'Dave'

def spam():
  name = 'Guido'

spam()
print(name) # prints 'Dave'
```

**Remember: All assignments in functions are local.**

### Modifying Globals

If you must modify a global variable you must declare it as such.

```python
name = 'Dave'

def spam():
    global name
    name = 'Guido' # Changes the global name above
```

The global declaration must appear before its use and the corresponding
variable must exist in the same file as the function.   Having seen this,
know that it is considered poor form.  In fact, try to avoid `global` entirely
if you can.  If you need a function to modify some kind of state outside
of the function, it's better to use a class instead (more on this later).

### Argument Passing

When you call a function, the argument variables are names that refer
to the passed values. These values are NOT copies (see [section
2.7](../02_Working_with_data/07_Objects.md)). If mutable data types are
passed (e.g. lists, dicts), they can be modified *in-place*.

```python
def foo(items):
    items.append(42)    # Modifies the input object

a = [1, 2, 3]
foo(a)
print(a)                # [1, 2, 3, 42]
```

**Key point: Functions don't receive a copy of the input arguments.**

### Reassignment vs Modifying

Make sure you understand the subtle difference between modifying a
value and reassigning a variable name.

```python
def foo(items):
    items.append(42)    # Modifies the input object

a = [1, 2, 3]
foo(a)
print(a)                # [1, 2, 3, 42]

# VS
def bar(items):
    items = [4,5,6]    # Changes local `items` variable to point to a different object

b = [1, 2, 3]
bar(b)
print(b)                # [1, 2, 3]
```

*Reminder: Variable assignment never overwrites memory. The name is merely bound to a new value.*

## Exercises

This set of exercises have you implement what is, perhaps, the most
powerful and difficult part of the course.  There are a lot of steps
and many concepts from past exercises are put together all at once.
The final solution is only about 25 lines of code, but take your time
and make sure you understand each part.

A central part of your `report.py` program focuses on the reading of
CSV files.  For example, the function `read_portfolio()` reads a file
containing rows of portfolio data and the function `read_prices()`
reads a file containing rows of price data. In both of those
functions, there are a lot of low-level "fiddly" bits and similar
features.  For example, they both open a file and wrap it with the
`csv` module and they both convert various fields into new types.

If you were doing a lot of file parsing for real, you’d probably want
to clean some of this up and make it more general purpose.  That's
our goal.

Start this exercise by opening the file called
`Work/fileparse.py`. This is where we will be doing our work.

### Exercise 3.3: Reading CSV Files

To start, let’s just focus on the problem of reading a CSV file into a
list of dictionaries.  In the file `fileparse.py`, define a
function that looks like this:

```python
# fileparse.py
import csv

def parse_csv(filename):
    '''
    Parse a CSV file into a list of records
    '''
    with open(filename) as f:
        rows = csv.reader(f)

        # Read the file headers
        headers = next(rows)
        records = []
        for row in rows:
            if not row:    # Skip rows with no data
                continue
            record = dict(zip(headers, row))
            records.append(record)

    return records
```

This function reads a CSV file into a list of dictionaries while
hiding the details of opening the file, wrapping it with the `csv`
module, ignoring blank lines, and so forth.

Try it out:

Hint: `python3 -i fileparse.py`.

```python
>>> portfolio = parse_csv('Data/portfolio.csv')
>>> portfolio
[{'price': '32.20', 'name': 'AA', 'shares': '100'}, {'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'}, {'price': '51.23', 'name': 'MSFT', 'shares': '200'}, {'price': '40.37', 'name': 'GE', 'shares': '95'}, {'price': '65.10', 'name': 'MSFT', 'shares': '50'}, {'price': '70.44', 'name': 'IBM', 'shares': '100'}]
>>>
```

This is good except that you can’t do any kind of useful calculation
with the data because everything is represented as a string.  We’ll
fix this shortly, but let’s keep building on it.

### Exercise 3.4: Building a Column Selector

In many cases, you’re only interested in selected columns from a CSV
file, not all of the data.  Modify the `parse_csv()` function so that
it optionally allows user-specified columns to be picked out as
follows:

```python
>>> # Read all of the data
>>> portfolio = parse_csv('Data/portfolio.csv')
>>> portfolio
[{'price': '32.20', 'name': 'AA', 'shares': '100'}, {'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'}, {'price': '51.23', 'name': 'MSFT', 'shares': '200'}, {'price': '40.37', 'name': 'GE', 'shares': '95'}, {'price': '65.10', 'name': 'MSFT', 'shares': '50'}, {'price': '70.44', 'name': 'IBM', 'shares': '100'}]

>>> # Read only some of the data
>>> shares_held = parse_csv('Data/portfolio.csv', select=['name','shares'])
>>> shares_held
[{'name': 'AA', 'shares': '100'}, {'name': 'IBM', 'shares': '50'}, {'name': 'CAT', 'shares': '150'}, {'name': 'MSFT', 'shares': '200'}, {'name': 'GE', 'shares': '95'}, {'name': 'MSFT', 'shares': '50'}, {'name': 'IBM', 'shares': '100'}]
>>>
```

An example of a column selector was given in [Exercise 2.23](../02_Working_with_data/06_List_comprehension.md).
However, here’s one way to do it:

```python
# fileparse.py
import csv

def parse_csv(filename, select=None):
    '''
    Parse a CSV file into a list of records
    '''
    with open(filename) as f:
        rows = csv.reader(f)

        # Read the file headers
        headers = next(rows)

        # If a column selector was given, find indices of the specified columns.
        # Also narrow the set of headers used for resulting dictionaries
        if select:
            indices = [headers.index(colname) for colname in select]
            headers = select
        else:
            indices = []

        records = []
        for row in rows:
            if not row:    # Skip rows with no data
                continue
            # Filter the row if specific columns were selected
            if indices:
                row = [ row[index] for index in indices ]

            # Make a dictionary
            record = dict(zip(headers, row))
            records.append(record)

    return records
```

There are a number of tricky bits to this part. Probably the most
important one is the mapping of the column selections to row indices.
For example, suppose the input file had the following headers:

```python
>>> headers = ['name', 'date', 'time', 'shares', 'price']
>>>
```

Now, suppose the selected columns were as follows:

```python
>>> select = ['name', 'shares']
>>>
```

To perform the proper selection, you have to map the selected column names to column indices in the file.
That’s what this step is doing:

```python
>>> indices = [headers.index(colname) for colname in select ]
>>> indices
[0, 3]
>>>
```

In other words, "name" is column 0 and "shares" is column 3.
When you read a row of data from the file, the indices are used to filter it:

```python
>>> row = ['AA', '6/11/2007', '9:50am', '100', '32.20' ]
>>> row = [ row[index] for index in indices ]
>>> row
['AA', '100']
>>>
```

### Exercise 3.5: Performing Type Conversion

Modify the `parse_csv()` function so that it optionally allows
type-conversions to be applied to the returned data.  For example:

```python
>>> portfolio = parse_csv('Data/portfolio.csv', types=[str, int, float])
>>> portfolio
[{'price': 32.2, 'name': 'AA', 'shares': 100}, {'price': 91.1, 'name': 'IBM', 'shares': 50}, {'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 51.23, 'name': 'MSFT', 'shares': 200}, {'price': 40.37, 'name': 'GE', 'shares': 95}, {'price': 65.1, 'name': 'MSFT', 'shares': 50}, {'price': 70.44, 'name': 'IBM', 'shares': 100}]

>>> shares_held = parse_csv('Data/portfolio.csv', select=['name', 'shares'], types=[str, int])
>>> shares_held
[{'name': 'AA', 'shares': 100}, {'name': 'IBM', 'shares': 50}, {'name': 'CAT', 'shares': 150}, {'name': 'MSFT', 'shares': 200}, {'name': 'GE', 'shares': 95}, {'name': 'MSFT', 'shares': 50}, {'name': 'IBM', 'shares': 100}]
>>>
```

You already explored this in [Exercise 2.24](../02_Working_with_data/07_Objects.md).
You'll need to insert the following fragment of code into your solution:

```python
...
if types:
    row = [func(val) for func, val in zip(types, row) ]
...
```

### Exercise 3.6: Working without Headers

Some CSV files don’t include any header information.
For example, the file `prices.csv` looks like this:

```csv
"AA",9.22
"AXP",24.85
"BA",44.85
"BAC",11.27
...
```

Modify the `parse_csv()` function so that it can work with such files
by creating a list of tuples instead.  For example:

```python
>>> prices = parse_csv('Data/prices.csv', types=[str,float], has_headers=False)
>>> prices
[('AA', 9.22), ('AXP', 24.85), ('BA', 44.85), ('BAC', 11.27), ('C', 3.72), ('CAT', 35.46), ('CVX', 66.67), ('DD', 28.47), ('DIS', 24.22), ('GE', 13.48), ('GM', 0.75), ('HD', 23.16), ('HPQ', 34.35), ('IBM', 106.28), ('INTC', 15.72), ('JNJ', 55.16), ('JPM', 36.9), ('KFT', 26.11), ('KO', 49.16), ('MCD', 58.99), ('MMM', 57.1), ('MRK', 27.58), ('MSFT', 20.89), ('PFE', 15.19), ('PG', 51.94), ('T', 24.79), ('UTX', 52.61), ('VZ', 29.26), ('WMT', 49.74), ('XOM', 69.35)]
>>>
```

To make this change, you’ll need to modify the code so that the first
line of data isn’t interpreted as a header line.  Also, you’ll need to
make sure you don’t create dictionaries as there are no longer any
column names to use for keys.

### Exercise 3.7: Picking a different column delimiter

Although CSV files are pretty common, it’s also possible that you
could encounter a file that uses a different column separator such as
a tab or space.  For example, the file `Data/portfolio.dat` looks like
this:

```csv
name shares price
"AA" 100 32.20
"IBM" 50 91.10
"CAT" 150 83.44
"MSFT" 200 51.23
"GE" 95 40.37
"MSFT" 50 65.10
"IBM" 100 70.44
```

The `csv.reader()` function allows a different column delimiter to be given as follows:

```python
rows = csv.reader(f, delimiter=' ')
```

Modify your `parse_csv()` function so that it also allows the
delimiter to be changed.

For example:

```python
>>> portfolio = parse_csv('Data/portfolio.dat', types=[str, int, float], delimiter=' ')
>>> portfolio
[{'name': 'AA', 'shares': 100, 'price': 32.2}, {'name': 'IBM', 'shares': 50, 'price': 91.1}, {'name': 'CAT', 'shares': 150, 'price': 83.44}, {'name': 'MSFT', 'shares': 200, 'price': 51.23}, {'name': 'GE', 'shares': 95, 'price': 40.37}, {'name': 'MSFT', 'shares': 50, 'price': 65.1}, {'name': 'IBM', 'shares': 100, 'price': 70.44}]
>>>
```

### Commentary

If you’ve made it this far, you’ve created a nice library function
that’s genuinely useful.  You can use it to parse arbitrary CSV files,
select out columns of interest, perform type conversions, without
having to worry too much about the inner workings of files or the
`csv` module.

[Contents](../Contents.md) \| [Previous (3.1 Scripting)](01_Script.md) \| [Next (3.3 Error Checking)](03_Error_checking.md)


================================================
FILE: Notes/03_Program_organization/03_Error_checking.md
================================================
[Contents](../Contents.md) \| [Previous (3.2 More on Functions)](02_More_functions.md) \| [Next (3.4 Modules)](04_Modules.md)

# 3.3 Error Checking

Although exceptions were introduced earlier, this section fills in some additional
details about error checking and exception handling.

### How programs fail

Python performs no checking or validation of function argument types
or values.  A function will work on any data that is compatible with
the statements in the function.

```python
def add(x, y):
    return x + y

add(3, 4)               # 7
add('Hello', 'World')   # 'HelloWorld'
add('3', '4')           # '34'
```

If there are errors in a function, they appear at run time (as an exception).

```python
def add(x, y):
    return x + y

>>> add(3, '4')
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +:
'int' and 'str'
>>>
```

To verify code, there is a strong emphasis on testing (covered later).

### Exceptions

Exceptions are used to signal errors.
To raise an exception yourself, use `raise` statement.

```python
if name not in authorized:
    raise RuntimeError(f'{name} not authorized')
```

To catch an exception use `try-except`.

```python
try:
    authenticate(username)
except RuntimeError as e:
    print(e)
```

### Exception Handling

Exceptions propagate to the first matching `except`.

```python
def grok():
    ...
    raise RuntimeError('Whoa!')   # Exception raised here

def spam():
    grok()                        # Call that will raise exception

def bar():
    try:
       spam()
    except RuntimeError as e:     # Exception caught here
        ...

def foo():
    try:
         bar()
    except RuntimeError as e:     # Exception does NOT arrive here
        ...

foo()
```

To handle the exception, put statements in the `except` block. You can add any
statements you want to handle the error.

```python
def grok(): ...
    raise RuntimeError('Whoa!')

def bar():
    try:
      grok()
    except RuntimeError as e:   # Exception caught here
        statements              # Use this statements
        statements
        ...

bar()
```

After handling, execution resumes with the first statement after the
`try-except`.

```python
def grok(): ...
    raise RuntimeError('Whoa!')

def bar():
    try:
      grok()
    except RuntimeError as e:   # Exception caught here
        statements
        statements
        ...
    statements                  # Resumes execution here
    statements                  # And continues here
    ...

bar()
```

### Built-in Exceptions

There are about two-dozen built-in exceptions.  Usually the name of
the exception is indicative of what's wrong (e.g., a `ValueError` is
raised because you supplied a bad value). This is not an
exhaustive list. Check the [documentation](https://docs.python.org/3/library/exceptions.html) for more.

```python
ArithmeticError
AssertionError
EnvironmentError
EOFError
ImportError
IndexError
KeyboardInterrupt
KeyError
MemoryError
NameError
ReferenceError
RuntimeError
SyntaxError
SystemError
TypeError
ValueError
```

### Exception Values

Exceptions have an associated value. It contains more specific
information about what's wrong.

```python
raise RuntimeError('Invalid user name')
```

This value is part of the exception instance that's placed in the variable supplied to `except`.

```python
try:
    ...
except RuntimeError as e:   # `e` holds the exception raised
    ...
```

`e` is an instance of the exception type. However, it often looks like a string when
printed.

```python
except RuntimeError as e:
    print('Failed : Reason', e)
```

### Catching Multiple Errors

You can catch different kinds of exceptions using multiple `except` blocks.

```python
try:
  ...
except LookupError as e:
  ...
except RuntimeError as e:
  ...
except IOError as e:
  ...
except KeyboardInterrupt as e:
  ...
```

Alternatively, if the statements to handle them is the same, you can group them:

```python
try:
  ...
except (IOError,LookupError,RuntimeError) as e:
  ...
```

### Catching All Errors

To catch any exception, use `Exception` like this:

```python
try:
    ...
except Exception:       # DANGER. See below
    print('An error occurred')
```

In general, writing code like that is a bad idea because you'll have
no idea why it failed.

### Wrong Way to Catch Errors

Here is the wrong way to use exceptions.

```python
try:
    go_do_something()
except Exception:
    print('Computer says no')
```

This catches all possible errors and it may make it impossible to debug
when the code is failing for some reason you didn't expect at all
(e.g. uninstalled Python module, etc.).

### Somewhat Better Approach

If you're going to catch all errors, this is a more sane approach.

```python
try:
    go_do_something()
except Exception as e:
    print('Computer says no. Reason :', e)
```

It reports a specific reason for failure.  It is almost always a good
idea to have some mechanism for viewing/reporting errors when you
write code that catches all possible exceptions.

In general though, it's better to catch the error as narrowly as is
reasonable. Only catch the errors you can actually handle. Let
other errors pass by--maybe some other code can handle them.

### Reraising an Exception

Use `raise` to propagate a caught error.

```python
try:
    go_do_something()
except Exception as e:
    print('Computer says no. Reason :', e)
    raise
```

This allows you to take action (e.g. logging) and pass the error on to
the caller.

### Exception Best Practices

Don't catch exceptions. Fail fast and loud. If it's important, someone
else will take care of the problem.  Only catch an exception if you
are *that* someone.  That is, only catch errors where you can recover
and sanely keep going.

### `finally` statement

It specifies code that must run regardless of whether or not an
exception occurs.

```python
lock = Lock()
...
lock.acquire()
try:
    ...
finally:
    lock.release()  # this will ALWAYS be executed. With and without exception.
```

Commonly used to safely manage resources (especially locks, files, etc.).

### `with` statement

In modern code, `try-finally` is often replaced with the `with` statement.

```python
lock = Lock()
with lock:
    # lock acquired
    ...
# lock released
```

A more familiar example:

```python
with open(filename) as f:
    # Use the file
    ...
# File closed
```

`with` defines a usage *context* for a resource.  When execution
leaves that context, resources are released. `with` only works with
certain objects that have been specifically programmed to support it.

## Exercises

### Exercise 3.8: Raising exceptions

The `parse_csv()` function you wrote in the last section allows
user-specified columns to be selected, but that only works if the
input data file has column headers.

Modify the code so that an exception gets raised if both the `select`
and `has_headers=False` arguments are passed.  For example:

```python
>>> parse_csv('Data/prices.csv', select=['name','price'], has_headers=False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "fileparse.py", line 9, in parse_csv
    raise RuntimeError("select argument requires column headers")
RuntimeError: select argument requires column headers
>>>
```

Having added this one check, you might ask if you should be performing
other kinds of sanity checks in the function.  For example, should you
check that the filename is a string, that types is a list, or anything
of that nature?

As a general rule, it’s usually best to skip such tests and to just
let the program fail on bad inputs.  The traceback message will point
at the source of the problem and can assist in debugging.

The main reason for adding the above check is to avoid running the code
in a non-sensical mode (e.g., using a feature that requires column
headers, but simultaneously specifying that there are no headers).

This indicates a programming error on the part of the calling code.
Checking for cases that "aren't supposed to happen" is often a good idea.

### Exercise 3.9: Catching exceptions

The `parse_csv()` function you wrote is used to process the entire
contents of a file.  However, in the real-world, it’s possible that
input files might have corrupted, missing, or dirty data.  Try this
experiment:

```python
>>> portfolio = parse_csv('Data/missing.csv', types=[str, int, float])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "fileparse.py", line 36, in parse_csv
    row = [func(val) for func, val in zip(types, row)]
ValueError: invalid literal for int() with base 10: ''
>>>
```

Modify the `parse_csv()` function to catch all `ValueError` exceptions
generated during record creation and print a warning message for rows
that can’t be converted.

The message should include the row number and information about the
reason why it failed.  To test your function, try reading the file
`Data/missing.csv` above.  For example:

```python
>>> portfolio = parse_csv('Data/missing.csv', types=[str, int, float])
Row 4: Couldn't convert ['MSFT', '', '51.23']
Row 4: Reason invalid literal for int() with base 10: ''
Row 7: Couldn't convert ['IBM', '', '70.44']
Row 7: Reason invalid literal for int() with base 10: ''
>>>
>>> portfolio
[{'price': 32.2, 'name': 'AA', 'shares': 100}, {'price': 91.1, 'name': 'IBM', 'shares': 50}, {'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 40.37, 'name': 'GE', 'shares': 95}, {'price': 65.1, 'name': 'MSFT', 'shares': 50}]
>>>
```

### Exercise 3.10: Silencing Errors

Modify the `parse_csv()` function so that parsing error messages can
be silenced if explicitly desired by the user.  For example:

```python
>>> portfolio = parse_csv('Data/missing.csv', types=[str,int,float], silence_errors=True)
>>> portfolio
[{'price': 32.2, 'name': 'AA', 'shares': 100}, {'price': 91.1, 'name': 'IBM', 'shares': 50}, {'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 40.37, 'name': 'GE', 'shares': 95}, {'price': 65.1, 'name': 'MSFT', 'shares': 50}]
>>>
```

Error handling is one of the most difficult things to get right in
most programs.  As a general rule, you shouldn’t silently ignore
errors.  Instead, it’s better to report problems and to give the user
an option to the silence the error message if they choose to do so.

[Contents](../Contents.md) \| [Previous (3.2 More on Functions)](02_More_functions.md) \| [Next (3.4 Modules)](04_Modules.md)


================================================
FILE: Notes/03_Program_organization/04_Modules.md
================================================
[Contents](../Contents.md) \| [Previous (3.3 Error Checking)](03_Error_checking.md) \| [Next (3.5 Main Module)](05_Main_module.md)

# 3.4 Modules

This section introduces the concept of modules and working with functions that span
multiple files.

### Modules and import

Any Python source file is a module.

```python
# foo.py
def grok(a):
    ...
def spam(b):
    ...
```

The `import` statement loads and *executes* a module.

```python
# program.py
import foo

a = foo.grok(2)
b = foo.spam('Hello')
...
```

### Namespaces

A module is a collection of named values and is sometimes said to be a
*namespace*.  The names are all of the global variables and functions
defined in the source file.  After importing, the module name is used
as a prefix. Hence the *namespace*.

```python
import foo

a = foo.grok(2)
b = foo.spam('Hello')
...
```

The module name is directly tied to the file name (foo -> foo.py).

### Global Definitions

Everything defined in the *global* scope is what populates the module
namespace. Consider two modules
that define the same variable `x`.

```python
# foo.py
x = 42
def grok(a):
    ...
```

```python
# bar.py
x = 37
def spam(a):
    ...
```

In this case, the `x` definitions refer to different variables.  One
is `foo.x` and the other is `bar.x`.  Different modules can use the
same names and those names won't conflict with each other.

**Modules are isolated.**

### Modules as Environments

Modules form an enclosing environment for all of the code defined inside.

```python
# foo.py
x = 42

def grok(a):
    print(x)
```

*Global* variables are always bound to the enclosing module (same file).
Each source file is its own little universe.

### Module Execution

When a module is imported, *all of the statements in the module
execute* one after another until the end of the file is reached.  The
contents of the module namespace are all of the *global* names that
are still defined at the end of the execution process.  If there are
scripting statements that carry out tasks in the global scope
(printing, creating files, etc.) you will see them run on import.

### `import as` statement

You can change the name of a module as you import it:

```python
import math as m
def rectangular(r, theta):
    x = r * m.cos(theta)
    y = r * m.sin(theta)
    return x, y
```

It works the same as a normal import. It just renames the module in that one file.

### `from` module import

This picks selected symbols out of a module and makes them available locally.

```python
from math import sin, cos

def rectangular(r, theta):
    x = r * cos(theta)
    y = r * sin(theta)
    return x, y
```

This allows parts of a module to be used without having to type the module prefix.
It's useful for frequently used names.

### Comments on importing

Variations on import do *not* change the way that modules work.

```python
import math
# vs
import math as m
# vs
from math import cos, sin
...
```

Specifically, `import` always executes the *entire* file and modules
are still isolated environments.

The `import module as` statement is only changing the name locally.
The `from math import cos, sin` statement still loads the entire
math module behind the scenes. It's merely copying the `cos` and `sin`
names from the module into the local space after it's done.

### Module Loading

Each module loads and executes only *once*.
*Note: Repeated imports just return a reference to the previously loaded module.*

`sys.modules` is a dict of all loaded modules.

```python
>>> import sys
>>> sys.modules.keys()
['copy_reg', '__main__', 'site', '__builtin__', 'encodings', 'encodings.encodings', 'posixpath', ...]
>>>
```

**Caution:** A common confusion arises if you repeat an `import` statement after
changing the source code for a module.  Because of the module cache `sys.modules`,
repeated imports always return the previously loaded module--even if a change
was made.  The safest way to load modified code into Python is to quit and restart
the interpreter.

### Locating Modules

Python consults a path list (sys.path) when looking for modules.

```python
>>> import sys
>>> sys.path
[
  '',
  '/usr/local/lib/python36/python36.zip',
  '/usr/local/lib/python36',
  ...
]
```

The current working directory is usually first.

### Module Search Path

As noted, `sys.path` contains the search paths.
You can manually adjust if you need to.

```python
import sys
sys.path.append('/project/foo/pyfiles')
```

Paths can also be added via environment variables.

```python
% env PYTHONPATH=/project/foo/pyfiles python3
Python 3.6.0 (default, Feb 3 2017, 05:53:21)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.38)]
>>> import sys
>>> sys.path
['','/project/foo/pyfiles', ...]
```

As a general rule, it should not be necessary to manually adjust
the module search path.  However, it sometimes arises if you're
trying to import Python code that's in an unusual location or
not readily accessible from the current working directory.

## Exercises

For this exercise involving modules, it is critically important to
make sure you are running Python in a proper environment.  Modules 
often present new programmers with problems related to the current working
directory or with Python's path settings.  For this course, it is
assumed that you're writing all of your code in the `Work/` directory.
For best results, you should make sure you're also in that directory
when you launch the interpreter.  If not, you need to make sure
`practical-python/Work` is added to `sys.path`.

### Exercise 3.11: Module imports

In section 3, we created a general purpose function `parse_csv()` for
parsing the contents of CSV datafiles.

Now, we’re going to see how to use that function in other programs.
First, start in a new shell window.  Navigate to the folder where you
have all your files. We are going to import them.

Start Python interactive mode.

```shell
bash % python3
Python 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
```

Once you’ve done that, try importing some of the programs you
previously wrote.  You should see their output exactly as before.
Just to emphasize, importing a module runs its code.

```python
>>> import bounce
... watch output ...
>>> import mortgage
... watch output ...
>>> import report
... watch output ...
>>>
```

If none of this works, you’re probably running Python in the wrong directory.
Now, try importing your `fileparse` module and getting some help on it.

```python
>>> import fileparse
>>> help(fileparse)
... look at the output ...
>>> dir(fileparse)
... look at the output ...
>>>
```

Try using the module to read some data:

```python
>>> portfolio = fileparse.parse_csv('Data/portfolio.csv',select=['name','shares','price'], types=[str,int,float])
>>> portfolio
... look at the output ...
>>> pricelist = fileparse.parse_csv('Data/prices.csv',types=[str,float], has_headers=False)
>>> pricelist
... look at the output ...
>>> prices = dict(pricelist)
>>> prices
... look at the output ...
>>> prices['IBM']
106.11
>>>
```

Try importing a function so that you don’t need to include the module name:

```python
>>> from fileparse import parse_csv
>>> portfolio = parse_csv('Data/portfolio.csv', select=['name','shares','price'], types=[str,int,float])
>>> portfolio
... look at the output ...
>>>
```

### Exercise 3.12: Using your library module

In section 2, you wrote a program `report.py` that produced a stock report like this:

```
      Name     Shares      Price     Change
---------- ---------- ---------- ----------
        AA        100       9.22     -22.98
       IBM         50     106.28      15.18
       CAT        150      35.46     -47.98
      MSFT        200      20.89     -30.34
        GE         95      13.48     -26.89
      MSFT         50      20.89     -44.21
       IBM        100     106.28      35.84
```

Take that program and modify it so that all of the input file
processing is done using functions in your `fileparse` module.  To do
that, import `fileparse` as a module and change the `read_portfolio()`
and `read_prices()` functions to use the `parse_csv()` function.

Use the interactive example at the start of this exercise as a guide.
Afterwards, you should get exactly the same output as before.

### Exercise 3.13: Intentionally left blank (skip)

### Exercise 3.14: Using more library imports

In section 1, you wrote a program `pcost.py` that read a portfolio and computed its cost.

```python
>>> import pcost
>>> pcost.portfolio_cost('Data/portfolio.csv')
44671.15
>>>
```

Modify the `pcost.py` file so that it uses the `report.read_portfolio()` function.

### Commentary

When you are done with this exercise, you should have three
programs. `fileparse.py` which contains a general purpose
`parse_csv()` function.  `report.py` which produces a nice report, but
also contains `read_portfolio()` and `read_prices()` functions.  And
finally, `pcost.py` which computes the portfolio cost, but makes use
of the `read_portfolio()` function written for the `report.py` program.

[Contents](../Contents.md) \| [Previous (3.3 Error Checking)](03_Error_checking.md) \| [Next (3.5 Main Module)](05_Main_module.md)


================================================
FILE: Notes/03_Program_organization/05_Main_module.md
================================================
[Contents](../Contents.md) \| [Previous (3.4 Modules)](04_Modules.md) \| [Next (3.6 Design Discussion)](06_Design_discussion.md)

# 3.5 Main Module

This section introduces the concept of a main program or main module.

### Main Functions

In many programming languages, there is a concept of a *main* function or method.

```c
// c / c++
int main(int argc, char *argv[]) {
    ...
}
```

```java
// java
class myprog {
    public static void main(String args[]) {
        ...
    }
}
```

This is the first function that executes when an application is launched.

### Python Main Module

Python has no *main* function or method.  Instead, there is a *main*
module. The *main module* is the source file that runs first.

```bash
bash % python3 prog.py
...
```

Whatever file you give to the interpreter at startup becomes *main*. It doesn't matter the name.

### `__main__` check

It is standard practice for modules that run as a main script to use this convention:

```python
# prog.py
...
if __name__ == '__main__':
    # Running as the main program ...
    statements
    ...
```

Statements enclosed inside the `if` statement become the *main* program.

### Main programs vs. library imports

Any Python file can either run as main or as a library import:

```bash
bash % python3 prog.py # Running as main
```

```python
import prog   # Running as library import
```

In both cases, `__name__` is the name of the module.  However, it will only be set to `__main__` if
running as main.

Usually, you don't want statements that are part of the main program
to execute on a library import.  So, it's common to have an `if-`check
in code that might be used either way.

```python
if __name__ == '__main__':
    # Does not execute if loaded with import ...
```

### Program Template

Here is a common program template for writing a Python program:

```python
# prog.py
# Import statements (libraries)
import modules

# Functions
def spam():
    ...

def blah():
    ...

# Main function
def main():
    ...

if __name__ == '
Download .txt
gitextract_7n8e5kpg/

├── .gitignore
├── LICENSE.md
├── Notes/
│   ├── 00_Setup.md
│   ├── 01_Introduction/
│   │   ├── 00_Overview.md
│   │   ├── 01_Python.md
│   │   ├── 02_Hello_world.md
│   │   ├── 03_Numbers.md
│   │   ├── 04_Strings.md
│   │   ├── 05_Lists.md
│   │   ├── 06_Files.md
│   │   └── 07_Functions.md
│   ├── 02_Working_with_data/
│   │   ├── 00_Overview.md
│   │   ├── 01_Datatypes.md
│   │   ├── 02_Containers.md
│   │   ├── 03_Formatting.md
│   │   ├── 04_Sequences.md
│   │   ├── 05_Collections.md
│   │   ├── 06_List_comprehension.md
│   │   └── 07_Objects.md
│   ├── 03_Program_organization/
│   │   ├── 00_Overview.md
│   │   ├── 01_Script.md
│   │   ├── 02_More_functions.md
│   │   ├── 03_Error_checking.md
│   │   ├── 04_Modules.md
│   │   ├── 05_Main_module.md
│   │   └── 06_Design_discussion.md
│   ├── 04_Classes_objects/
│   │   ├── 00_Overview.md
│   │   ├── 01_Class.md
│   │   ├── 02_Inheritance.md
│   │   ├── 03_Special_methods.md
│   │   └── 04_Defining_exceptions.md
│   ├── 05_Object_model/
│   │   ├── 00_Overview.md
│   │   ├── 01_Dicts_revisited.md
│   │   └── 02_Classes_encapsulation.md
│   ├── 06_Generators/
│   │   ├── 00_Overview.md
│   │   ├── 01_Iteration_protocol.md
│   │   ├── 02_Customizing_iteration.md
│   │   ├── 03_Producers_consumers.md
│   │   └── 04_More_generators.md
│   ├── 07_Advanced_Topics/
│   │   ├── 00_Overview.md
│   │   ├── 01_Variable_arguments.md
│   │   ├── 02_Anonymous_function.md
│   │   ├── 03_Returning_functions.md
│   │   ├── 04_Function_decorators.md
│   │   └── 05_Decorated_methods.md
│   ├── 08_Testing_debugging/
│   │   ├── 00_Overview.md
│   │   ├── 01_Testing.md
│   │   ├── 02_Logging.md
│   │   └── 03_Debugging.md
│   ├── 09_Packages/
│   │   ├── 00_Overview.md
│   │   ├── 01_Packages.md
│   │   ├── 02_Third_party.md
│   │   ├── 03_Distribution.md
│   │   └── TheEnd.md
│   ├── Contents.md
│   └── InstructorNotes.md
├── README.md
├── Solutions/
│   ├── 1_10/
│   │   └── mortgage.py
│   ├── 1_27/
│   │   └── pcost.py
│   ├── 1_33/
│   │   └── pcost.py
│   ├── 1_5/
│   │   └── bounce.py
│   ├── 2_11/
│   │   └── report.py
│   ├── 2_16/
│   │   ├── pcost.py
│   │   └── report.py
│   ├── 2_7/
│   │   └── report.py
│   ├── 3_10/
│   │   └── fileparse.py
│   ├── 3_14/
│   │   ├── fileparse.py
│   │   ├── pcost.py
│   │   └── report.py
│   ├── 3_16/
│   │   ├── fileparse.py
│   │   ├── pcost.py
│   │   └── report.py
│   ├── 3_18/
│   │   ├── fileparse.py
│   │   ├── pcost.py
│   │   └── report.py
│   ├── 3_2/
│   │   └── report.py
│   ├── 3_7/
│   │   └── fileparse.py
│   ├── 4_10/
│   │   ├── fileparse.py
│   │   ├── pcost.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   └── tableformat.py
│   ├── 4_4/
│   │   ├── fileparse.py
│   │   ├── pcost.py
│   │   ├── report.py
│   │   └── stock.py
│   ├── 5_8/
│   │   ├── fileparse.py
│   │   ├── pcost.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   └── tableformat.py
│   ├── 6_12/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   ├── tableformat.py
│   │   └── ticker.py
│   ├── 6_15/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   ├── tableformat.py
│   │   └── ticker.py
│   ├── 6_3/
│   │   ├── fileparse.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   └── tableformat.py
│   ├── 6_7/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   └── tableformat.py
│   ├── 7_10/
│   │   └── timethis.py
│   ├── 7_11/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   ├── tableformat.py
│   │   ├── ticker.py
│   │   ├── timethis.py
│   │   └── typedproperty.py
│   ├── 7_4/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   ├── tableformat.py
│   │   └── ticker.py
│   ├── 7_9/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   ├── tableformat.py
│   │   ├── ticker.py
│   │   └── typedproperty.py
│   ├── 8_1/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   ├── tableformat.py
│   │   ├── test_stock.py
│   │   ├── ticker.py
│   │   ├── timethis.py
│   │   └── typedproperty.py
│   ├── 8_2/
│   │   ├── fileparse.py
│   │   ├── follow.py
│   │   ├── pcost.py
│   │   ├── portfolio.py
│   │   ├── report.py
│   │   ├── stock.py
│   │   ├── tableformat.py
│   │   ├── test_stock.py
│   │   ├── ticker.py
│   │   ├── timethis.py
│   │   └── typedproperty.py
│   ├── 9_3/
│   │   └── porty-app/
│   │       ├── README.txt
│   │       ├── portfolio.csv
│   │       ├── porty/
│   │       │   ├── __init__.py
│   │       │   ├── fileparse.py
│   │       │   ├── follow.py
│   │       │   ├── pcost.py
│   │       │   ├── portfolio.py
│   │       │   ├── report.py
│   │       │   ├── stock.py
│   │       │   ├── tableformat.py
│   │       │   ├── test_stock.py
│   │       │   ├── ticker.py
│   │       │   └── typedproperty.py
│   │       ├── prices.csv
│   │       └── print-report.py
│   ├── 9_5/
│   │   └── porty-app/
│   │       ├── MANIFEST.in
│   │       ├── README.txt
│   │       ├── portfolio.csv
│   │       ├── porty/
│   │       │   ├── __init__.py
│   │       │   ├── fileparse.py
│   │       │   ├── follow.py
│   │       │   ├── pcost.py
│   │       │   ├── portfolio.py
│   │       │   ├── report.py
│   │       │   ├── stock.py
│   │       │   ├── tableformat.py
│   │       │   ├── test_stock.py
│   │       │   ├── ticker.py
│   │       │   └── typedproperty.py
│   │       ├── prices.csv
│   │       ├── print-report.py
│   │       └── setup.py
│   └── README.md
├── Work/
│   ├── Data/
│   │   ├── dowstocks.csv
│   │   ├── missing.csv
│   │   ├── portfolio.csv
│   │   ├── portfolio2.csv
│   │   ├── portfolioblank.csv
│   │   ├── portfoliodate.csv
│   │   ├── prices.csv
│   │   └── stocksim.py
│   ├── README.md
│   ├── bounce.py
│   ├── fileparse.py
│   ├── mortgage.py
│   ├── pcost.py
│   └── report.py
├── _config.yml
└── _layouts/
    └── default.html
Download .txt
SYMBOL INDEX (681 symbols across 131 files)

FILE: Solutions/1_33/pcost.py
  function portfolio_cost (line 4) | def portfolio_cost(filename):

FILE: Solutions/2_11/report.py
  function read_portfolio (line 4) | def read_portfolio(filename):
  function read_prices (line 24) | def read_prices(filename):
  function make_report_data (line 39) | def make_report_data(portfolio, prices):

FILE: Solutions/2_16/pcost.py
  function portfolio_cost (line 4) | def portfolio_cost(filename):

FILE: Solutions/2_16/report.py
  function read_portfolio (line 4) | def read_portfolio(filename):
  function read_prices (line 25) | def read_prices(filename):
  function make_report_data (line 40) | def make_report_data(portfolio, prices):

FILE: Solutions/2_7/report.py
  function read_portfolio (line 4) | def read_portfolio(filename):
  function read_prices (line 24) | def read_prices(filename):

FILE: Solutions/3_10/fileparse.py
  function parse_csv (line 4) | def parse_csv(filename, select=None, types=None, has_headers=True, delim...

FILE: Solutions/3_14/fileparse.py
  function parse_csv (line 4) | def parse_csv(filename, select=None, types=None, has_headers=True, delim...

FILE: Solutions/3_14/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):

FILE: Solutions/3_14/report.py
  function read_portfolio (line 5) | def read_portfolio(filename):
  function read_prices (line 12) | def read_prices(filename):
  function make_report_data (line 18) | def make_report_data(portfolio,prices):
  function print_report (line 31) | def print_report(reportdata):
  function portfolio_report (line 41) | def portfolio_report(portfoliofile,pricefile):

FILE: Solutions/3_16/fileparse.py
  function parse_csv (line 4) | def parse_csv(filename, select=None, types=None, has_headers=True, delim...

FILE: Solutions/3_16/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/3_16/report.py
  function read_portfolio (line 5) | def read_portfolio(filename):
  function read_prices (line 12) | def read_prices(filename):
  function make_report_data (line 18) | def make_report_data(portfolio,prices):
  function print_report (line 31) | def print_report(reportdata):
  function portfolio_report (line 41) | def portfolio_report(portfoliofile, pricefile):
  function main (line 55) | def main(args):

FILE: Solutions/3_18/fileparse.py
  function parse_csv (line 4) | def parse_csv(lines, select=None, types=None, has_headers=True, delimite...

FILE: Solutions/3_18/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/3_18/report.py
  function read_portfolio (line 5) | def read_portfolio(filename):
  function read_prices (line 13) | def read_prices(filename):
  function make_report_data (line 20) | def make_report_data(portfolio,prices):
  function print_report (line 33) | def print_report(reportdata):
  function portfolio_report (line 43) | def portfolio_report(portfoliofile, pricefile):
  function main (line 57) | def main(args):

FILE: Solutions/3_2/report.py
  function read_portfolio (line 4) | def read_portfolio(filename):
  function read_prices (line 25) | def read_prices(filename):
  function make_report_data (line 40) | def make_report_data(portfolio,prices):
  function print_report (line 53) | def print_report(reportdata):
  function portfolio_report (line 63) | def portfolio_report(portfoliofile,pricefile):

FILE: Solutions/3_7/fileparse.py
  function parse_csv (line 4) | def parse_csv(filename, select=None, types=None, has_headers=True, delim...

FILE: Solutions/4_10/fileparse.py
  function parse_csv (line 4) | def parse_csv(lines, select=None, types=None, has_headers=True, delimite...

FILE: Solutions/4_10/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/4_10/report.py
  function read_portfolio (line 7) | def read_portfolio(filename):
  function read_prices (line 20) | def read_prices(filename):
  function make_report_data (line 27) | def make_report_data(portfolio, prices):
  function print_report (line 40) | def print_report(reportdata, formatter):
  function portfolio_report (line 49) | def portfolio_report(portfoliofile, pricefile, fmt='txt'):
  function main (line 64) | def main(args):

FILE: Solutions/4_10/stock.py
  class Stock (line 3) | class Stock:
    method __init__ (line 7) | def __init__(self, name, shares, price):
    method __repr__ (line 12) | def __repr__(self):
    method cost (line 15) | def cost(self):
    method sell (line 21) | def sell(self, nshares):

FILE: Solutions/4_10/tableformat.py
  class TableFormatter (line 3) | class TableFormatter:
    method headings (line 4) | def headings(self, headers):
    method row (line 10) | def row(self, rowdata):
  class TextTableFormatter (line 16) | class TextTableFormatter(TableFormatter):
    method headings (line 20) | def headings(self, headers):
    method row (line 26) | def row(self, rowdata):
  class CSVTableFormatter (line 31) | class CSVTableFormatter(TableFormatter):
    method headings (line 35) | def headings(self, headers):
    method row (line 38) | def row(self, rowdata):
  class HTMLTableFormatter (line 41) | class HTMLTableFormatter(TableFormatter):
    method headings (line 45) | def headings(self, headers):
    method row (line 51) | def row(self, rowdata):
  class FormatError (line 57) | class FormatError(Exception):
  function create_formatter (line 60) | def create_formatter(name):
  function print_table (line 73) | def print_table(objects, columns, formatter):

FILE: Solutions/4_4/fileparse.py
  function parse_csv (line 4) | def parse_csv(lines, select=None, types=None, has_headers=True, delimite...

FILE: Solutions/4_4/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/4_4/report.py
  function read_portfolio (line 6) | def read_portfolio(filename):
  function read_prices (line 19) | def read_prices(filename):
  function make_report_data (line 26) | def make_report_data(portfolio, prices):
  function print_report (line 39) | def print_report(reportdata):
  function portfolio_report (line 49) | def portfolio_report(portfoliofile, pricefile):
  function main (line 63) | def main(args):

FILE: Solutions/4_4/stock.py
  class Stock (line 3) | class Stock:
    method __init__ (line 7) | def __init__(self, name, shares, price):
    method cost (line 12) | def cost(self):
    method sell (line 18) | def sell(self, nshares):

FILE: Solutions/5_8/fileparse.py
  function parse_csv (line 4) | def parse_csv(lines, select=None, types=None, has_headers=True, delimite...

FILE: Solutions/5_8/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/5_8/report.py
  function read_portfolio (line 7) | def read_portfolio(filename):
  function read_prices (line 20) | def read_prices(filename):
  function make_report_data (line 27) | def make_report_data(portfolio, prices):
  function print_report (line 40) | def print_report(reportdata, formatter):
  function portfolio_report (line 49) | def portfolio_report(portfoliofile, pricefile, fmt='txt'):
  function main (line 64) | def main(args):

FILE: Solutions/5_8/stock.py
  class Stock (line 3) | class Stock:
    method __init__ (line 8) | def __init__(self,name, shares, price):
    method __repr__ (line 13) | def __repr__(self):
    method shares (line 17) | def shares(self):
    method shares (line 21) | def shares(self, value):
    method cost (line 27) | def cost(self):
    method sell (line 33) | def sell(self, nshares):

FILE: Solutions/5_8/tableformat.py
  class TableFormatter (line 3) | class TableFormatter:
    method headings (line 4) | def headings(self, headers):
    method row (line 10) | def row(self, rowdata):
  class TextTableFormatter (line 16) | class TextTableFormatter(TableFormatter):
    method headings (line 20) | def headings(self, headers):
    method row (line 26) | def row(self, rowdata):
  class CSVTableFormatter (line 31) | class CSVTableFormatter(TableFormatter):
    method headings (line 35) | def headings(self, headers):
    method row (line 38) | def row(self, rowdata):
  class HTMLTableFormatter (line 41) | class HTMLTableFormatter(TableFormatter):
    method headings (line 45) | def headings(self, headers):
    method row (line 51) | def row(self, rowdata):
  class FormatError (line 57) | class FormatError(Exception):
  function create_formatter (line 60) | def create_formatter(name):
  function print_table (line 73) | def print_table(objects, columns, formatter):

FILE: Solutions/6_12/fileparse.py
  function parse_csv (line 4) | def parse_csv(lines, select=None, types=None, has_headers=True, delimite...

FILE: Solutions/6_12/follow.py
  function follow (line 5) | def follow(filename):

FILE: Solutions/6_12/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/6_12/portfolio.py
  class Portfolio (line 3) | class Portfolio:
    method __init__ (line 4) | def __init__(self, holdings):
    method __iter__ (line 7) | def __iter__(self):
    method __len__ (line 10) | def __len__(self):
    method __getitem__ (line 13) | def __getitem__(self, index):
    method __contains__ (line 16) | def __contains__(self, name):
    method total_cost (line 20) | def total_cost(self):
    method tabulate_shares (line 23) | def tabulate_shares(self):

FILE: Solutions/6_12/report.py
  function read_portfolio (line 8) | def read_portfolio(filename):
  function read_prices (line 21) | def read_prices(filename):
  function make_report_data (line 28) | def make_report_data(portfolio, prices):
  function print_report (line 41) | def print_report(reportdata, formatter):
  function portfolio_report (line 50) | def portfolio_report(portfoliofile, pricefile, fmt='txt'):
  function main (line 65) | def main(args):

FILE: Solutions/6_12/stock.py
  class Stock (line 3) | class Stock:
    method __init__ (line 8) | def __init__(self,name, shares, price):
    method __repr__ (line 13) | def __repr__(self):
    method shares (line 17) | def shares(self):
    method shares (line 21) | def shares(self, value):
    method cost (line 27) | def cost(self):
    method sell (line 33) | def sell(self, nshares):

FILE: Solutions/6_12/tableformat.py
  class TableFormatter (line 3) | class TableFormatter:
    method headings (line 4) | def headings(self, headers):
    method row (line 10) | def row(self, rowdata):
  class TextTableFormatter (line 16) | class TextTableFormatter(TableFormatter):
    method headings (line 20) | def headings(self, headers):
    method row (line 26) | def row(self, rowdata):
  class CSVTableFormatter (line 31) | class CSVTableFormatter(TableFormatter):
    method headings (line 35) | def headings(self, headers):
    method row (line 38) | def row(self, rowdata):
  class HTMLTableFormatter (line 41) | class HTMLTableFormatter(TableFormatter):
    method headings (line 45) | def headings(self, headers):
    method row (line 51) | def row(self, rowdata):
  class FormatError (line 57) | class FormatError(Exception):
  function create_formatter (line 60) | def create_formatter(name):
  function print_table (line 73) | def print_table(objects, columns, formatter):

FILE: Solutions/6_12/ticker.py
  function select_columns (line 9) | def select_columns(rows, indices):
  function convert_types (line 13) | def convert_types(rows, types):
  function make_dicts (line 17) | def make_dicts(rows, headers):
  function parse_stock_data (line 21) | def parse_stock_data(lines):
  function filter_symbols (line 28) | def filter_symbols(rows, names):
  function ticker (line 33) | def ticker(portfile, logfile, fmt):
  function main (line 43) | def main(args):

FILE: Solutions/6_15/fileparse.py
  function parse_csv (line 4) | def parse_csv(lines, select=None, types=None, has_headers=True, delimite...

FILE: Solutions/6_15/follow.py
  function follow (line 6) | def follow(filename):

FILE: Solutions/6_15/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/6_15/portfolio.py
  class Portfolio (line 3) | class Portfolio:
    method __init__ (line 4) | def __init__(self, holdings):
    method __iter__ (line 7) | def __iter__(self):
    method __len__ (line 10) | def __len__(self):
    method __getitem__ (line 13) | def __getitem__(self, index):
    method __contains__ (line 16) | def __contains__(self, name):
    method total_cost (line 20) | def total_cost(self):
    method tabulate_shares (line 23) | def tabulate_shares(self):

FILE: Solutions/6_15/report.py
  function read_portfolio (line 8) | def read_portfolio(filename):
  function read_prices (line 21) | def read_prices(filename):
  function make_report_data (line 28) | def make_report_data(portfolio, prices):
  function print_report (line 41) | def print_report(reportdata, formatter):
  function portfolio_report (line 50) | def portfolio_report(portfoliofile, pricefile, fmt='txt'):
  function main (line 65) | def main(args):

FILE: Solutions/6_15/stock.py
  class Stock (line 3) | class Stock:
    method __init__ (line 8) | def __init__(self,name, shares, price):
    method __repr__ (line 13) | def __repr__(self):
    method shares (line 17) | def shares(self):
    method shares (line 21) | def shares(self, value):
    method cost (line 27) | def cost(self):
    method sell (line 33) | def sell(self, nshares):

FILE: Solutions/6_15/tableformat.py
  class TableFormatter (line 3) | class TableFormatter:
    method headings (line 4) | def headings(self, headers):
    method row (line 10) | def row(self, rowdata):
  class TextTableFormatter (line 16) | class TextTableFormatter(TableFormatter):
    method headings (line 20) | def headings(self, headers):
    method row (line 26) | def row(self, rowdata):
  class CSVTableFormatter (line 31) | class CSVTableFormatter(TableFormatter):
    method headings (line 35) | def headings(self, headers):
    method row (line 38) | def row(self, rowdata):
  class HTMLTableFormatter (line 41) | class HTMLTableFormatter(TableFormatter):
    method headings (line 45) | def headings(self, headers):
    method row (line 51) | def row(self, rowdata):
  class FormatError (line 57) | class FormatError(Exception):
  function create_formatter (line 60) | def create_formatter(name):
  function print_table (line 73) | def print_table(objects, columns, formatter):

FILE: Solutions/6_15/ticker.py
  function select_columns (line 9) | def select_columns(rows, indices):
  function convert_types (line 13) | def convert_types(rows, types):
  function make_dicts (line 17) | def make_dicts(rows, headers):
  function parse_stock_data (line 20) | def parse_stock_data(lines):
  function ticker (line 27) | def ticker(portfile, logfile, fmt):
  function main (line 37) | def main(args):

FILE: Solutions/6_3/fileparse.py
  function parse_csv (line 4) | def parse_csv(lines, select=None, types=None, has_headers=True, delimite...

FILE: Solutions/6_3/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/6_3/portfolio.py
  class Portfolio (line 3) | class Portfolio:
    method __init__ (line 4) | def __init__(self, holdings):
    method __iter__ (line 7) | def __iter__(self):
    method __len__ (line 10) | def __len__(self):
    method __getitem__ (line 13) | def __getitem__(self, index):
    method __contains__ (line 16) | def __contains__(self, name):
    method total_cost (line 20) | def total_cost(self):
    method tabulate_shares (line 23) | def tabulate_shares(self):

FILE: Solutions/6_3/report.py
  function read_portfolio (line 8) | def read_portfolio(filename):
  function read_prices (line 21) | def read_prices(filename):
  function make_report_data (line 28) | def make_report_data(portfolio, prices):
  function print_report (line 41) | def print_report(reportdata, formatter):
  function portfolio_report (line 50) | def portfolio_report(portfoliofile, pricefile, fmt='txt'):
  function main (line 65) | def main(args):

FILE: Solutions/6_3/stock.py
  class Stock (line 3) | class Stock:
    method __init__ (line 8) | def __init__(self,name, shares, price):
    method __repr__ (line 13) | def __repr__(self):
    method shares (line 17) | def shares(self):
    method shares (line 21) | def shares(self, value):
    method cost (line 27) | def cost(self):
    method sell (line 33) | def sell(self, nshares):

FILE: Solutions/6_3/tableformat.py
  class TableFormatter (line 3) | class TableFormatter:
    method headings (line 4) | def headings(self, headers):
    method row (line 10) | def row(self, rowdata):
  class TextTableFormatter (line 16) | class TextTableFormatter(TableFormatter):
    method headings (line 20) | def headings(self, headers):
    method row (line 26) | def row(self, rowdata):
  class CSVTableFormatter (line 31) | class CSVTableFormatter(TableFormatter):
    method headings (line 35) | def headings(self, headers):
    method row (line 38) | def row(self, rowdata):
  class HTMLTableFormatter (line 41) | class HTMLTableFormatter(TableFormatter):
    method headings (line 45) | def headings(self, headers):
    method row (line 51) | def row(self, rowdata):
  class FormatError (line 57) | class FormatError(Exception):
  function create_formatter (line 60) | def create_formatter(name):
  function print_table (line 73) | def print_table(objects, columns, formatter):

FILE: Solutions/6_7/fileparse.py
  function parse_csv (line 4) | def parse_csv(lines, select=None, types=None, has_headers=True, delimite...

FILE: Solutions/6_7/follow.py
  function follow (line 5) | def follow(filename):

FILE: Solutions/6_7/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/6_7/portfolio.py
  class Portfolio (line 3) | class Portfolio:
    method __init__ (line 4) | def __init__(self, holdings):
    method __iter__ (line 7) | def __iter__(self):
    method __len__ (line 10) | def __len__(self):
    method __getitem__ (line 13) | def __getitem__(self, index):
    method __contains__ (line 16) | def __contains__(self, name):
    method total_cost (line 20) | def total_cost(self):
    method tabulate_shares (line 23) | def tabulate_shares(self):

FILE: Solutions/6_7/report.py
  function read_portfolio (line 8) | def read_portfolio(filename):
  function read_prices (line 21) | def read_prices(filename):
  function make_report_data (line 28) | def make_report_data(portfolio, prices):
  function print_report (line 41) | def print_report(reportdata, formatter):
  function portfolio_report (line 50) | def portfolio_report(portfoliofile, pricefile, fmt='txt'):
  function main (line 65) | def main(args):

FILE: Solutions/6_7/stock.py
  class Stock (line 3) | class Stock:
    method __init__ (line 8) | def __init__(self,name, shares, price):
    method __repr__ (line 13) | def __repr__(self):
    method shares (line 17) | def shares(self):
    method shares (line 21) | def shares(self, value):
    method cost (line 27) | def cost(self):
    method sell (line 33) | def sell(self, nshares):

FILE: Solutions/6_7/tableformat.py
  class TableFormatter (line 3) | class TableFormatter:
    method headings (line 4) | def headings(self, headers):
    method row (line 10) | def row(self, rowdata):
  class TextTableFormatter (line 16) | class TextTableFormatter(TableFormatter):
    method headings (line 20) | def headings(self, headers):
    method row (line 26) | def row(self, rowdata):
  class CSVTableFormatter (line 31) | class CSVTableFormatter(TableFormatter):
    method headings (line 35) | def headings(self, headers):
    method row (line 38) | def row(self, rowdata):
  class HTMLTableFormatter (line 41) | class HTMLTableFormatter(TableFormatter):
    method headings (line 45) | def headings(self, headers):
    method row (line 51) | def row(self, rowdata):
  class FormatError (line 57) | class FormatError(Exception):
  function create_formatter (line 60) | def create_formatter(name):
  function print_table (line 73) | def print_table(objects, columns, formatter):

FILE: Solutions/7_10/timethis.py
  function timethis (line 5) | def timethis(func):
  function countdown (line 17) | def countdown(n):

FILE: Solutions/7_11/fileparse.py
  function parse_csv (line 4) | def parse_csv(lines, select=None, types=None, has_headers=True, delimite...

FILE: Solutions/7_11/follow.py
  function follow (line 6) | def follow(filename):

FILE: Solutions/7_11/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/7_11/portfolio.py
  class Portfolio (line 6) | class Portfolio:
    method __init__ (line 7) | def __init__(self):
    method from_csv (line 11) | def from_csv(cls, lines, **opts):
    method append (line 23) | def append(self, holding):
    method __iter__ (line 26) | def __iter__(self):
    method __len__ (line 29) | def __len__(self):
    method __getitem__ (line 32) | def __getitem__(self, index):
    method __contains__ (line 35) | def __contains__(self, name):
    method total_cost (line 39) | def total_cost(self):
    method tabulate_shares (line 42) | def tabulate_shares(self):

FILE: Solutions/7_11/report.py
  function read_portfolio (line 8) | def read_portfolio(filename, **opts):
  function read_prices (line 16) | def read_prices(filename, **opts):
  function make_report (line 23) | def make_report(portfolio, prices):
  function print_report (line 36) | def print_report(reportdata, formatter):
  function portfolio_report (line 45) | def portfolio_report(portfoliofile, pricefile, fmt='txt'):
  function main (line 60) | def main(args):

FILE: Solutions/7_11/stock.py
  class Stock (line 5) | class Stock:
    method __init__ (line 13) | def __init__(self,name, shares, price):
    method __repr__ (line 18) | def __repr__(self):
    method cost (line 22) | def cost(self):
    method sell (line 28) | def sell(self, nshares):

FILE: Solutions/7_11/tableformat.py
  class TableFormatter (line 3) | class TableFormatter:
    method headings (line 4) | def headings(self, headers):
    method row (line 10) | def row(self, rowdata):
  class TextTableFormatter (line 16) | class TextTableFormatter(TableFormatter):
    method headings (line 20) | def headings(self, headers):
    method row (line 26) | def row(self, rowdata):
  class CSVTableFormatter (line 31) | class CSVTableFormatter(TableFormatter):
    method headings (line 35) | def headings(self, headers):
    method row (line 38) | def row(self, rowdata):
  class HTMLTableFormatter (line 41) | class HTMLTableFormatter(TableFormatter):
    method headings (line 45) | def headings(self, headers):
    method row (line 51) | def row(self, rowdata):
  class FormatError (line 57) | class FormatError(Exception):
  function create_formatter (line 60) | def create_formatter(name):
  function print_table (line 73) | def print_table(objects, columns, formatter):

FILE: Solutions/7_11/ticker.py
  function select_columns (line 8) | def select_columns(rows, indices):
  function convert_types (line 12) | def convert_types(rows, types):
  function make_dicts (line 16) | def make_dicts(rows, headers):
  function parse_stock_data (line 19) | def parse_stock_data(lines):
  function ticker (line 26) | def ticker(portfile, logfile, fmt):
  function main (line 36) | def main(args):

FILE: Solutions/7_11/timethis.py
  function timethis (line 5) | def timethis(func):
  function countdown (line 17) | def countdown(n):

FILE: Solutions/7_11/typedproperty.py
  function typedproperty (line 3) | def typedproperty(name, expected_type):
  class Stock (line 23) | class Stock:
    method __init__ (line 28) | def __init__(self, name, shares, price):
  class Stock2 (line 34) | class Stock2:
    method __init__ (line 39) | def __init__(self, name, shares, price):

FILE: Solutions/7_4/fileparse.py
  function parse_csv (line 4) | def parse_csv(lines, select=None, types=None, has_headers=True, delimite...

FILE: Solutions/7_4/follow.py
  function follow (line 6) | def follow(filename):

FILE: Solutions/7_4/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/7_4/portfolio.py
  class Portfolio (line 3) | class Portfolio:
    method __init__ (line 4) | def __init__(self, holdings):
    method __iter__ (line 7) | def __iter__(self):
    method __len__ (line 10) | def __len__(self):
    method __getitem__ (line 13) | def __getitem__(self, index):
    method __contains__ (line 16) | def __contains__(self, name):
    method total_cost (line 20) | def total_cost(self):
    method tabulate_shares (line 23) | def tabulate_shares(self):

FILE: Solutions/7_4/report.py
  function read_portfolio (line 8) | def read_portfolio(filename, **opts):
  function read_prices (line 22) | def read_prices(filename, **opts):
  function make_report_data (line 29) | def make_report_data(portfolio, prices):
  function print_report (line 42) | def print_report(reportdata, formatter):
  function portfolio_report (line 51) | def portfolio_report(portfoliofile, pricefile, fmt='txt'):
  function main (line 66) | def main(args):

FILE: Solutions/7_4/stock.py
  class Stock (line 3) | class Stock:
    method __init__ (line 8) | def __init__(self,name, shares, price):
    method __repr__ (line 13) | def __repr__(self):
    method shares (line 17) | def shares(self):
    method shares (line 21) | def shares(self, value):
    method cost (line 27) | def cost(self):
    method sell (line 33) | def sell(self, nshares):

FILE: Solutions/7_4/tableformat.py
  class TableFormatter (line 3) | class TableFormatter:
    method headings (line 4) | def headings(self, headers):
    method row (line 10) | def row(self, rowdata):
  class TextTableFormatter (line 16) | class TextTableFormatter(TableFormatter):
    method headings (line 20) | def headings(self, headers):
    method row (line 26) | def row(self, rowdata):
  class CSVTableFormatter (line 31) | class CSVTableFormatter(TableFormatter):
    method headings (line 35) | def headings(self, headers):
    method row (line 38) | def row(self, rowdata):
  class HTMLTableFormatter (line 41) | class HTMLTableFormatter(TableFormatter):
    method headings (line 45) | def headings(self, headers):
    method row (line 51) | def row(self, rowdata):
  class FormatError (line 57) | class FormatError(Exception):
  function create_formatter (line 60) | def create_formatter(name):
  function print_table (line 73) | def print_table(objects, columns, formatter):

FILE: Solutions/7_4/ticker.py
  function select_columns (line 9) | def select_columns(rows, indices):
  function convert_types (line 13) | def convert_types(rows, types):
  function make_dicts (line 17) | def make_dicts(rows, headers):
  function parse_stock_data (line 20) | def parse_stock_data(lines):
  function ticker (line 27) | def ticker(portfile, logfile, fmt):
  function main (line 37) | def main(args):

FILE: Solutions/7_9/fileparse.py
  function parse_csv (line 4) | def parse_csv(lines, select=None, types=None, has_headers=True, delimite...

FILE: Solutions/7_9/follow.py
  function follow (line 6) | def follow(filename):

FILE: Solutions/7_9/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/7_9/portfolio.py
  class Portfolio (line 3) | class Portfolio:
    method __init__ (line 4) | def __init__(self, holdings):
    method __iter__ (line 7) | def __iter__(self):
    method __len__ (line 10) | def __len__(self):
    method __getitem__ (line 13) | def __getitem__(self, index):
    method __contains__ (line 16) | def __contains__(self, name):
    method total_cost (line 20) | def total_cost(self):
    method tabulate_shares (line 23) | def tabulate_shares(self):

FILE: Solutions/7_9/report.py
  function read_portfolio (line 8) | def read_portfolio(filename, **opts):
  function read_prices (line 22) | def read_prices(filename, **opts):
  function make_report_data (line 29) | def make_report_data(portfolio, prices):
  function print_report (line 42) | def print_report(reportdata, formatter):
  function portfolio_report (line 51) | def portfolio_report(portfoliofile, pricefile, fmt='txt'):
  function main (line 66) | def main(args):

FILE: Solutions/7_9/stock.py
  class Stock (line 5) | class Stock:
    method __init__ (line 13) | def __init__(self,name, shares, price):
    method __repr__ (line 18) | def __repr__(self):
    method cost (line 22) | def cost(self):
    method sell (line 28) | def sell(self, nshares):

FILE: Solutions/7_9/tableformat.py
  class TableFormatter (line 3) | class TableFormatter:
    method headings (line 4) | def headings(self, headers):
    method row (line 10) | def row(self, rowdata):
  class TextTableFormatter (line 16) | class TextTableFormatter(TableFormatter):
    method headings (line 20) | def headings(self, headers):
    method row (line 26) | def row(self, rowdata):
  class CSVTableFormatter (line 31) | class CSVTableFormatter(TableFormatter):
    method headings (line 35) | def headings(self, headers):
    method row (line 38) | def row(self, rowdata):
  class HTMLTableFormatter (line 41) | class HTMLTableFormatter(TableFormatter):
    method headings (line 45) | def headings(self, headers):
    method row (line 51) | def row(self, rowdata):
  class FormatError (line 57) | class FormatError(Exception):
  function create_formatter (line 60) | def create_formatter(name):
  function print_table (line 73) | def print_table(objects, columns, formatter):

FILE: Solutions/7_9/ticker.py
  function select_columns (line 9) | def select_columns(rows, indices):
  function convert_types (line 13) | def convert_types(rows, types):
  function make_dicts (line 17) | def make_dicts(rows, headers):
  function parse_stock_data (line 20) | def parse_stock_data(lines):
  function ticker (line 27) | def ticker(portfile, logfile, fmt):
  function main (line 37) | def main(args):

FILE: Solutions/7_9/typedproperty.py
  function typedproperty (line 3) | def typedproperty(name, expected_type):
  class Stock (line 23) | class Stock:
    method __init__ (line 28) | def __init__(self, name, shares, price):
  class Stock2 (line 33) | class Stock2:
    method __init__ (line 38) | def __init__(self, name, shares, price):

FILE: Solutions/8_1/fileparse.py
  function parse_csv (line 4) | def parse_csv(lines, select=None, types=None, has_headers=True, delimite...

FILE: Solutions/8_1/follow.py
  function follow (line 6) | def follow(filename):

FILE: Solutions/8_1/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/8_1/portfolio.py
  class Portfolio (line 6) | class Portfolio:
    method __init__ (line 7) | def __init__(self):
    method from_csv (line 11) | def from_csv(cls, lines, **opts):
    method append (line 23) | def append(self, holding):
    method __iter__ (line 26) | def __iter__(self):
    method __len__ (line 29) | def __len__(self):
    method __getitem__ (line 32) | def __getitem__(self, index):
    method __contains__ (line 35) | def __contains__(self, name):
    method total_cost (line 39) | def total_cost(self):
    method tabulate_shares (line 42) | def tabulate_shares(self):

FILE: Solutions/8_1/report.py
  function read_portfolio (line 8) | def read_portfolio(filename, **opts):
  function read_prices (line 16) | def read_prices(filename, **opts):
  function make_report (line 23) | def make_report(portfolio, prices):
  function print_report (line 36) | def print_report(reportdata, formatter):
  function portfolio_report (line 45) | def portfolio_report(portfoliofile, pricefile, fmt='txt'):
  function main (line 60) | def main(args):

FILE: Solutions/8_1/stock.py
  class Stock (line 5) | class Stock:
    method __init__ (line 13) | def __init__(self,name, shares, price):
    method __repr__ (line 18) | def __repr__(self):
    method cost (line 22) | def cost(self):
    method sell (line 28) | def sell(self, nshares):

FILE: Solutions/8_1/tableformat.py
  class TableFormatter (line 3) | class TableFormatter:
    method headings (line 4) | def headings(self, headers):
    method row (line 10) | def row(self, rowdata):
  class TextTableFormatter (line 16) | class TextTableFormatter(TableFormatter):
    method headings (line 20) | def headings(self, headers):
    method row (line 26) | def row(self, rowdata):
  class CSVTableFormatter (line 31) | class CSVTableFormatter(TableFormatter):
    method headings (line 35) | def headings(self, headers):
    method row (line 38) | def row(self, rowdata):
  class HTMLTableFormatter (line 41) | class HTMLTableFormatter(TableFormatter):
    method headings (line 45) | def headings(self, headers):
    method row (line 51) | def row(self, rowdata):
  class FormatError (line 57) | class FormatError(Exception):
  function create_formatter (line 60) | def create_formatter(name):
  function print_table (line 73) | def print_table(objects, columns, formatter):

FILE: Solutions/8_1/test_stock.py
  class TestStock (line 6) | class TestStock(unittest.TestCase):
    method test_create (line 7) | def test_create(self):
    method test_cost (line 13) | def test_cost(self):
    method test_sell (line 17) | def test_sell(self):
    method test_shares_check (line 22) | def test_shares_check(self):

FILE: Solutions/8_1/ticker.py
  function select_columns (line 8) | def select_columns(rows, indices):
  function convert_types (line 12) | def convert_types(rows, types):
  function make_dicts (line 16) | def make_dicts(rows, headers):
  function parse_stock_data (line 19) | def parse_stock_data(lines):
  function ticker (line 26) | def ticker(portfile, logfile, fmt):
  function main (line 36) | def main(args):

FILE: Solutions/8_1/timethis.py
  function timethis (line 5) | def timethis(func):
  function countdown (line 17) | def countdown(n):

FILE: Solutions/8_1/typedproperty.py
  function typedproperty (line 3) | def typedproperty(name, expected_type):
  class Stock (line 23) | class Stock:
    method __init__ (line 28) | def __init__(self, name, shares, price):

FILE: Solutions/8_2/fileparse.py
  function parse_csv (line 6) | def parse_csv(lines, select=None, types=None, has_headers=True, delimite...

FILE: Solutions/8_2/follow.py
  function follow (line 6) | def follow(filename):

FILE: Solutions/8_2/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/8_2/portfolio.py
  class Portfolio (line 6) | class Portfolio:
    method __init__ (line 7) | def __init__(self):
    method from_csv (line 11) | def from_csv(cls, lines, **opts):
    method append (line 23) | def append(self, holding):
    method __iter__ (line 26) | def __iter__(self):
    method __len__ (line 29) | def __len__(self):
    method __getitem__ (line 32) | def __getitem__(self, index):
    method __contains__ (line 35) | def __contains__(self, name):
    method total_cost (line 39) | def total_cost(self):
    method tabulate_shares (line 42) | def tabulate_shares(self):

FILE: Solutions/8_2/report.py
  function read_portfolio (line 8) | def read_portfolio(filename, **opts):
  function read_prices (line 16) | def read_prices(filename, **opts):
  function make_report (line 23) | def make_report(portfolio, prices):
  function print_report (line 36) | def print_report(reportdata, formatter):
  function portfolio_report (line 45) | def portfolio_report(portfoliofile, pricefile, fmt='txt'):
  function main (line 60) | def main(args):

FILE: Solutions/8_2/stock.py
  class Stock (line 5) | class Stock:
    method __init__ (line 13) | def __init__(self,name, shares, price):
    method __repr__ (line 18) | def __repr__(self):
    method cost (line 22) | def cost(self):
    method sell (line 28) | def sell(self, nshares):

FILE: Solutions/8_2/tableformat.py
  class TableFormatter (line 3) | class TableFormatter:
    method headings (line 4) | def headings(self, headers):
    method row (line 10) | def row(self, rowdata):
  class TextTableFormatter (line 16) | class TextTableFormatter(TableFormatter):
    method headings (line 20) | def headings(self, headers):
    method row (line 26) | def row(self, rowdata):
  class CSVTableFormatter (line 31) | class CSVTableFormatter(TableFormatter):
    method headings (line 35) | def headings(self, headers):
    method row (line 38) | def row(self, rowdata):
  class HTMLTableFormatter (line 41) | class HTMLTableFormatter(TableFormatter):
    method headings (line 45) | def headings(self, headers):
    method row (line 51) | def row(self, rowdata):
  class FormatError (line 57) | class FormatError(Exception):
  function create_formatter (line 60) | def create_formatter(name):
  function print_table (line 73) | def print_table(objects, columns, formatter):

FILE: Solutions/8_2/test_stock.py
  class TestStock (line 6) | class TestStock(unittest.TestCase):
    method test_create (line 7) | def test_create(self):
    method test_cost (line 13) | def test_cost(self):
    method test_sell (line 17) | def test_sell(self):
    method test_shares_check (line 22) | def test_shares_check(self):

FILE: Solutions/8_2/ticker.py
  function select_columns (line 8) | def select_columns(rows, indices):
  function convert_types (line 12) | def convert_types(rows, types):
  function make_dicts (line 16) | def make_dicts(rows, headers):
  function parse_stock_data (line 19) | def parse_stock_data(lines):
  function ticker (line 26) | def ticker(portfile, logfile, fmt):
  function main (line 36) | def main(args):

FILE: Solutions/8_2/timethis.py
  function timethis (line 5) | def timethis(func):
  function countdown (line 17) | def countdown(n):

FILE: Solutions/8_2/typedproperty.py
  function typedproperty (line 3) | def typedproperty(name, expected_type):
  class Stock (line 23) | class Stock:
    method __init__ (line 28) | def __init__(self, name, shares, price):

FILE: Solutions/9_3/porty-app/porty/fileparse.py
  function parse_csv (line 6) | def parse_csv(lines, select=None, types=None, has_headers=True, delimite...

FILE: Solutions/9_3/porty-app/porty/follow.py
  function follow (line 6) | def follow(filename):

FILE: Solutions/9_3/porty-app/porty/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/9_3/porty-app/porty/portfolio.py
  class Portfolio (line 6) | class Portfolio:
    method __init__ (line 7) | def __init__(self):
    method from_csv (line 11) | def from_csv(cls, lines, **opts):
    method append (line 23) | def append(self, holding):
    method __iter__ (line 26) | def __iter__(self):
    method __len__ (line 29) | def __len__(self):
    method __getitem__ (line 32) | def __getitem__(self, index):
    method __contains__ (line 35) | def __contains__(self, name):
    method total_cost (line 39) | def total_cost(self):
    method tabulate_shares (line 42) | def tabulate_shares(self):

FILE: Solutions/9_3/porty-app/porty/report.py
  function read_portfolio (line 8) | def read_portfolio(filename, **opts):
  function read_prices (line 16) | def read_prices(filename, **opts):
  function make_report (line 23) | def make_report(portfolio, prices):
  function print_report (line 36) | def print_report(reportdata, formatter):
  function portfolio_report (line 45) | def portfolio_report(portfoliofile, pricefile, fmt='txt'):
  function main (line 60) | def main(args):

FILE: Solutions/9_3/porty-app/porty/stock.py
  class Stock (line 5) | class Stock:
    method __init__ (line 14) | def __init__(self,name, shares, price):
    method __repr__ (line 19) | def __repr__(self):
    method cost (line 23) | def cost(self):
    method sell (line 29) | def sell(self, nshares):

FILE: Solutions/9_3/porty-app/porty/tableformat.py
  class TableFormatter (line 3) | class TableFormatter:
    method headings (line 4) | def headings(self, headers):
    method row (line 10) | def row(self, rowdata):
  class TextTableFormatter (line 16) | class TextTableFormatter(TableFormatter):
    method headings (line 20) | def headings(self, headers):
    method row (line 26) | def row(self, rowdata):
  class CSVTableFormatter (line 31) | class CSVTableFormatter(TableFormatter):
    method headings (line 35) | def headings(self, headers):
    method row (line 38) | def row(self, rowdata):
  class HTMLTableFormatter (line 41) | class HTMLTableFormatter(TableFormatter):
    method headings (line 45) | def headings(self, headers):
    method row (line 51) | def row(self, rowdata):
  class FormatError (line 57) | class FormatError(Exception):
  function create_formatter (line 60) | def create_formatter(name):
  function print_table (line 73) | def print_table(objects, columns, formatter):

FILE: Solutions/9_3/porty-app/porty/test_stock.py
  class TestStock (line 6) | class TestStock(unittest.TestCase):
    method test_create (line 7) | def test_create(self):
    method test_cost (line 13) | def test_cost(self):
    method test_sell (line 17) | def test_sell(self):
    method test_shares_check (line 22) | def test_shares_check(self):

FILE: Solutions/9_3/porty-app/porty/ticker.py
  function select_columns (line 8) | def select_columns(rows, indices):
  function convert_types (line 12) | def convert_types(rows, types):
  function make_dicts (line 16) | def make_dicts(rows, headers):
  function parse_stock_data (line 19) | def parse_stock_data(lines):
  function ticker (line 26) | def ticker(portfile, logfile, fmt):
  function main (line 36) | def main(args):

FILE: Solutions/9_3/porty-app/porty/typedproperty.py
  function typedproperty (line 3) | def typedproperty(name, expected_type):
  class Stock (line 23) | class Stock:
    method __init__ (line 28) | def __init__(self, name, shares, price):

FILE: Solutions/9_5/porty-app/porty/fileparse.py
  function parse_csv (line 6) | def parse_csv(lines, select=None, types=None, has_headers=True, delimite...

FILE: Solutions/9_5/porty-app/porty/follow.py
  function follow (line 6) | def follow(filename):

FILE: Solutions/9_5/porty-app/porty/pcost.py
  function portfolio_cost (line 5) | def portfolio_cost(filename):
  function main (line 12) | def main(args):

FILE: Solutions/9_5/porty-app/porty/portfolio.py
  class Portfolio (line 6) | class Portfolio:
    method __init__ (line 7) | def __init__(self):
    method from_csv (line 11) | def from_csv(cls, lines, **opts):
    method append (line 23) | def append(self, holding):
    method __iter__ (line 26) | def __iter__(self):
    method __len__ (line 29) | def __len__(self):
    method __getitem__ (line 32) | def __getitem__(self, index):
    method __contains__ (line 35) | def __contains__(self, name):
    method total_cost (line 39) | def total_cost(self):
    method tabulate_shares (line 42) | def tabulate_shares(self):

FILE: Solutions/9_5/porty-app/porty/report.py
  function read_portfolio (line 8) | def read_portfolio(filename, **opts):
  function read_prices (line 16) | def read_prices(filename, **opts):
  function make_report (line 23) | def make_report(portfolio, prices):
  function print_report (line 36) | def print_report(reportdata, formatter):
  function portfolio_report (line 45) | def portfolio_report(portfoliofile, pricefile, fmt='txt'):
  function main (line 60) | def main(args):

FILE: Solutions/9_5/porty-app/porty/stock.py
  class Stock (line 5) | class Stock:
    method __init__ (line 14) | def __init__(self,name, shares, price):
    method __repr__ (line 19) | def __repr__(self):
    method cost (line 23) | def cost(self):
    method sell (line 29) | def sell(self, nshares):

FILE: Solutions/9_5/porty-app/porty/tableformat.py
  class TableFormatter (line 3) | class TableFormatter:
    method headings (line 4) | def headings(self, headers):
    method row (line 10) | def row(self, rowdata):
  class TextTableFormatter (line 16) | class TextTableFormatter(TableFormatter):
    method headings (line 20) | def headings(self, headers):
    method row (line 26) | def row(self, rowdata):
  class CSVTableFormatter (line 31) | class CSVTableFormatter(TableFormatter):
    method headings (line 35) | def headings(self, headers):
    method row (line 38) | def row(self, rowdata):
  class HTMLTableFormatter (line 41) | class HTMLTableFormatter(TableFormatter):
    method headings (line 45) | def headings(self, headers):
    method row (line 51) | def row(self, rowdata):
  class FormatError (line 57) | class FormatError(Exception):
  function create_formatter (line 60) | def create_formatter(name):
  function print_table (line 73) | def print_table(objects, columns, formatter):

FILE: Solutions/9_5/porty-app/porty/test_stock.py
  class TestStock (line 6) | class TestStock(unittest.TestCase):
    method test_create (line 7) | def test_create(self):
    method test_cost (line 13) | def test_cost(self):
    method test_sell (line 17) | def test_sell(self):
    method test_shares_check (line 22) | def test_shares_check(self):

FILE: Solutions/9_5/porty-app/porty/ticker.py
  function select_columns (line 8) | def select_columns(rows, indices):
  function convert_types (line 12) | def convert_types(rows, types):
  function make_dicts (line 16) | def make_dicts(rows, headers):
  function parse_stock_data (line 19) | def parse_stock_data(lines):
  function ticker (line 26) | def ticker(portfile, logfile, fmt):
  function main (line 36) | def main(args):

FILE: Solutions/9_5/porty-app/porty/typedproperty.py
  function typedproperty (line 3) | def typedproperty(name, expected_type):
  class Stock (line 23) | class Stock:
    method __init__ (line 28) | def __init__(self, name, shares, price):

FILE: Work/Data/stocksim.py
  function minutes (line 19) | def minutes(tm):
  function minutes_to_str (line 31) | def minutes_to_str(m):
  function read_history (line 39) | def read_history(filename):
  function csv_record (line 51) | def csv_record(fields):
  class StockTrack (line 55) | class StockTrack(object):
    method __init__ (line 56) | def __init__(self,name):
    method add_data (line 69) | def add_data(self,record):
    method reset (line 71) | def reset(self,time):
    method interpolate (line 90) | def interpolate(self,field):
    method update (line 102) | def update(self):
    method incr (line 112) | def incr(self,dt):
    method make_record (line 119) | def make_record(self):
  class MarketSimulator (line 123) | class MarketSimulator(object):
    method __init__ (line 124) | def __init__(self):
    method register (line 129) | def register(self,observer):
    method publish (line 132) | def publish(self,record):
    method add_history (line 135) | def add_history(self,filename):
    method reset (line 142) | def reset(self,time):
    method run (line 148) | def run(self,dt):
  class BasicPrinter (line 162) | class BasicPrinter(object):
    method update (line 163) | def update(self,record):
  class LogPrinter (line 166) | class LogPrinter(object):
    method __init__ (line 167) | def __init__(self,filename):
    method update (line 169) | def update(self,record):
Condensed preview — 219 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (756K chars).
[
  {
    "path": ".gitignore",
    "chars": 1799,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": "LICENSE.md",
    "chars": 18532,
    "preview": "## creative commons\n\n# Attribution-ShareAlike 4.0 International\n\nCreative Commons Corporation (“Creative Commons”) is no"
  },
  {
    "path": "Notes/00_Setup.md",
    "chars": 3599,
    "preview": "# Course Setup and Overview\n\nWelcome to Practical Python Programming!   This page has some important information\nabout c"
  },
  {
    "path": "Notes/01_Introduction/00_Overview.md",
    "chars": 735,
    "preview": "[Contents](../Contents.md) \\| [Next (2 Working With Data)](../02_Working_with_data/00_Overview.md)\n\n## 1. Introduction t"
  },
  {
    "path": "Notes/01_Introduction/01_Python.md",
    "chars": 7537,
    "preview": "[Contents](../Contents.md) \\| [Next (1.2 A First Program)](02_Hello_world.md)\n\n# 1.1 Python\n\n### What is Python?\n\nPython"
  },
  {
    "path": "Notes/01_Introduction/02_Hello_world.md",
    "chars": 11293,
    "preview": "[Contents](../Contents.md) \\| [Previous (1.1 Python)](01_Python.md) \\| [Next (1.3 Numbers)](03_Numbers.md)\n\n# 1.2 A Firs"
  },
  {
    "path": "Notes/01_Introduction/03_Numbers.md",
    "chars": 5650,
    "preview": "[Contents](../Contents.md) \\| [Previous (1.2 A First Program)](02_Hello_world.md) \\| [Next (1.4 Strings)](04_Strings.md)"
  },
  {
    "path": "Notes/01_Introduction/04_Strings.md",
    "chars": 12360,
    "preview": "[Contents](../Contents.md) \\| [Previous (1.3 Numbers)](03_Numbers.md) \\| [Next (1.5 Lists)](05_Lists.md)\n\n# 1.4 Strings\n"
  },
  {
    "path": "Notes/01_Introduction/05_Lists.md",
    "chars": 8798,
    "preview": "[Contents](../Contents.md) \\| [Previous (1.4 Strings)](04_Strings.md) \\| [Next (1.6 Files)](06_Files.md)\n\n# 1.5 Lists\n\nT"
  },
  {
    "path": "Notes/01_Introduction/06_Files.md",
    "chars": 6545,
    "preview": "[Contents](../Contents.md) \\| [Previous (1.5 Lists)](05_Lists.md) \\| [Next (1.7 Functions)](07_Functions.md)\n\n# 1.6 File"
  },
  {
    "path": "Notes/01_Introduction/07_Functions.md",
    "chars": 7087,
    "preview": "[Contents](../Contents.md) \\| [Previous (1.6 Files)](06_Files.md) \\| [Next (2.0 Working with Data)](../02_Working_with_d"
  },
  {
    "path": "Notes/02_Working_with_data/00_Overview.md",
    "chars": 982,
    "preview": "[Contents](../Contents.md) \\| [Prev (1 Introduction to Python)](../01_Introduction/00_Overview.md) \\| [Next (3 Program O"
  },
  {
    "path": "Notes/02_Working_with_data/01_Datatypes.md",
    "chars": 9591,
    "preview": "[Contents](../Contents.md) \\| [Previous (1.6 Files)](../01_Introduction/06_Files.md) \\| [Next (2.2 Containers)](02_Conta"
  },
  {
    "path": "Notes/02_Working_with_data/02_Containers.md",
    "chars": 11056,
    "preview": "[Contents](../Contents.md) \\| [Previous (2.1 Datatypes)](01_Datatypes.md) \\| [Next (2.3 Formatting)](03_Formatting.md)\n\n"
  },
  {
    "path": "Notes/02_Working_with_data/03_Formatting.md",
    "chars": 8526,
    "preview": "[Contents](../Contents.md) \\| [Previous (2.2 Containers)](02_Containers.md) \\| [Next (2.4 Sequences)](04_Sequences.md)\n\n"
  },
  {
    "path": "Notes/02_Working_with_data/04_Sequences.md",
    "chars": 12915,
    "preview": "[Contents](../Contents.md) \\| [Previous (2.3 Formatting)](03_Formatting.md) \\| [Next (2.5 Collections)](05_Collections.m"
  },
  {
    "path": "Notes/02_Working_with_data/05_Collections.md",
    "chars": 4376,
    "preview": "[Contents](../Contents.md) \\| [Previous (2.4 Sequences)](04_Sequences.md) \\| [Next (2.6 List Comprehensions)](06_List_co"
  },
  {
    "path": "Notes/02_Working_with_data/06_List_comprehension.md",
    "chars": 8782,
    "preview": "[Contents](../Contents.md) \\| [Previous (2.5 Collections)](05_Collections.md) \\| [Next (2.7 Object Model)](07_Objects.md"
  },
  {
    "path": "Notes/02_Working_with_data/07_Objects.md",
    "chars": 10432,
    "preview": "[Contents](../Contents.md) \\| [Previous (2.6 List Comprehensions)](06_List_comprehension.md) \\| [Next (3 Program Organiz"
  },
  {
    "path": "Notes/03_Program_organization/00_Overview.md",
    "chars": 1119,
    "preview": "[Contents](../Contents.md) \\| [Prev (2 Working With Data)](../02_Working_with_data/00_Overview.md) \\| [Next (4 Classes a"
  },
  {
    "path": "Notes/03_Program_organization/01_Script.md",
    "chars": 7727,
    "preview": "[Contents](../Contents.md) \\| [Previous (2.7 Object Model)](../02_Working_with_data/07_Objects.md) \\| [Next (3.2 More on"
  },
  {
    "path": "Notes/03_Program_organization/02_More_functions.md",
    "chars": 15217,
    "preview": "[Contents](../Contents.md) \\| [Previous (3.1 Scripting)](01_Script.md) \\| [Next (3.3 Error Checking)](03_Error_checking."
  },
  {
    "path": "Notes/03_Program_organization/03_Error_checking.md",
    "chars": 10436,
    "preview": "[Contents](../Contents.md) \\| [Previous (3.2 More on Functions)](02_More_functions.md) \\| [Next (3.4 Modules)](04_Module"
  },
  {
    "path": "Notes/03_Program_organization/04_Modules.md",
    "chars": 9268,
    "preview": "[Contents](../Contents.md) \\| [Previous (3.3 Error Checking)](03_Error_checking.md) \\| [Next (3.5 Main Module)](05_Main_"
  },
  {
    "path": "Notes/03_Program_organization/05_Main_module.md",
    "chars": 6438,
    "preview": "[Contents](../Contents.md) \\| [Previous (3.4 Modules)](04_Modules.md) \\| [Next (3.6 Design Discussion)](06_Design_discus"
  },
  {
    "path": "Notes/03_Program_organization/06_Design_discussion.md",
    "chars": 3679,
    "preview": "[Contents](../Contents.md) \\| [Previous (3.5 Main module)](05_Main_module.md) \\| [Next (4 Classes)](../04_Classes_object"
  },
  {
    "path": "Notes/04_Classes_objects/00_Overview.md",
    "chars": 1042,
    "preview": "[Contents](../Contents.md) \\| [Prev (3 Program Organization)](../03_Program_organization/00_Overview.md) \\| [Next (5 Inn"
  },
  {
    "path": "Notes/04_Classes_objects/01_Class.md",
    "chars": 7888,
    "preview": "[Contents](../Contents.md) \\| [Previous (3.6 Design discussion)](../03_Program_organization/06_Design_discussion.md) \\| "
  },
  {
    "path": "Notes/04_Classes_objects/02_Inheritance.md",
    "chars": 17711,
    "preview": "[Contents](../Contents.md) \\| [Previous (4.1 Classes)](01_Class.md) \\| [Next (4.3 Special methods)](03_Special_methods.m"
  },
  {
    "path": "Notes/04_Classes_objects/03_Special_methods.md",
    "chars": 6989,
    "preview": "[Contents](../Contents.md) \\| [Previous (4.2 Inheritance)](02_Inheritance.md) \\| [Next (4.4 Exceptions)](04_Defining_exc"
  },
  {
    "path": "Notes/04_Classes_objects/04_Defining_exceptions.md",
    "chars": 1530,
    "preview": "[Contents](../Contents.md) \\| [Previous (4.3 Special methods)](03_Special_methods.md) \\| [Next (5 Object Model)](../05_O"
  },
  {
    "path": "Notes/05_Object_model/00_Overview.md",
    "chars": 1188,
    "preview": "[Contents](../Contents.md) \\| [Prev (4 Classes and Objects)](../04_Classes_objects/00_Overview.md) \\| [Next (6 Generator"
  },
  {
    "path": "Notes/05_Object_model/01_Dicts_revisited.md",
    "chars": 14467,
    "preview": "[Contents](../Contents.md) \\| [Previous (4.4 Exceptions)](../04_Classes_objects/04_Defining_exceptions.md) \\| [Next (5.2"
  },
  {
    "path": "Notes/05_Object_model/02_Classes_encapsulation.md",
    "chars": 9369,
    "preview": "[Contents](../Contents.md) \\| [Previous (5.1 Dictionaries Revisited)](01_Dicts_revisited.md) \\| [Next (6 Generators)](.."
  },
  {
    "path": "Notes/06_Generators/00_Overview.md",
    "chars": 1093,
    "preview": "[Contents](../Contents.md) \\| [Prev (5 Inner Workings of Python Objects)](../05_Object_model/00_Overview.md) \\| [Next (7"
  },
  {
    "path": "Notes/06_Generators/01_Iteration_protocol.md",
    "chars": 7318,
    "preview": "[Contents](../Contents.md) \\| [Previous (5.2 Encapsulation)](../05_Object_model/02_Classes_encapsulation.md) \\| [Next (6"
  },
  {
    "path": "Notes/06_Generators/02_Customizing_iteration.md",
    "chars": 7526,
    "preview": "[Contents](../Contents.md) \\| [Previous (6.1 Iteration Protocol)](01_Iteration_protocol.md) \\| [Next (6.3 Producer/Consu"
  },
  {
    "path": "Notes/06_Generators/03_Producers_consumers.md",
    "chars": 7683,
    "preview": "[Contents](../Contents.md) \\| [Previous (6.2 Customizing Iteration)](02_Customizing_iteration.md) \\| [Next (6.4 Generato"
  },
  {
    "path": "Notes/06_Generators/04_More_generators.md",
    "chars": 4682,
    "preview": "[Contents](../Contents.md) \\| [Previous (6.3 Producer/Consumer)](03_Producers_consumers.md) \\| [Next (7 Advanced Topics)"
  },
  {
    "path": "Notes/07_Advanced_Topics/00_Overview.md",
    "chars": 1124,
    "preview": "[Contents](../Contents.md) \\| [Prev (6 Generators)](../06_Generators/00_Overview.md) \\| [Next (8 Testing and Debugging)]"
  },
  {
    "path": "Notes/07_Advanced_Topics/01_Variable_arguments.md",
    "chars": 5345,
    "preview": "\n[Contents](../Contents.md) \\| [Previous (6.4 Generator Expressions)](../06_Generators/04_More_generators.md) \\| [Next ("
  },
  {
    "path": "Notes/07_Advanced_Topics/02_Anonymous_function.md",
    "chars": 4188,
    "preview": "[Contents](../Contents.md) \\| [Previous (7.1 Variable Arguments)](01_Variable_arguments.md) \\| [Next (7.3 Returning Func"
  },
  {
    "path": "Notes/07_Advanced_Topics/03_Returning_functions.md",
    "chars": 5470,
    "preview": "[Contents](../Contents.md) \\| [Previous (7.2 Anonymous Functions)](02_Anonymous_function.md) \\| [Next (7.4 Decorators)]("
  },
  {
    "path": "Notes/07_Advanced_Topics/04_Function_decorators.md",
    "chars": 3903,
    "preview": "[Contents](../Contents.md) \\| [Previous (7.3 Returning Functions)](03_Returning_functions.md) \\| [Next (7.5 Decorated Me"
  },
  {
    "path": "Notes/07_Advanced_Topics/05_Decorated_methods.md",
    "chars": 5222,
    "preview": "[Contents](../Contents.md) \\| [Previous (7.4 Decorators)](04_Function_decorators.md) \\| [Next (8 Testing and Debugging)]"
  },
  {
    "path": "Notes/08_Testing_debugging/00_Overview.md",
    "chars": 545,
    "preview": "[Contents](../Contents.md) \\| [Prev (7 Advanced Topics)](../07_Advanced_Topics/00_Overview.md) \\| [Next (9 Packages)](.."
  },
  {
    "path": "Notes/08_Testing_debugging/01_Testing.md",
    "chars": 7296,
    "preview": "[Contents](../Contents.md) \\| [Previous (7.5 Decorated Methods)](../07_Advanced_Topics/05_Decorated_methods.md) \\| [Next"
  },
  {
    "path": "Notes/08_Testing_debugging/02_Logging.md",
    "chars": 8812,
    "preview": "[Contents](../Contents.md) \\| [Previous (8.1 Testing)](01_Testing.md) \\| [Next (8.3 Debugging)](03_Debugging.md)\n\n# 8.2 "
  },
  {
    "path": "Notes/08_Testing_debugging/03_Debugging.md",
    "chars": 3594,
    "preview": "[Contents](../Contents.md) \\| [Previous (8.2 Logging)](02_Logging.md) \\| [Next (9 Packages)](../09_Packages/00_Overview."
  },
  {
    "path": "Notes/09_Packages/00_Overview.md",
    "chars": 875,
    "preview": "[Contents](../Contents.md) \\| [Prev (8 Testing and Debugging)](../08_Testing_debugging/00_Overview.md)\n\n# 9 Packages\n\nWe"
  },
  {
    "path": "Notes/09_Packages/01_Packages.md",
    "chars": 9605,
    "preview": "[Contents](../Contents.md) \\| [Previous (8.3 Debugging)](../08_Testing_debugging/03_Debugging.md) \\| [Next (9.2 Third Pa"
  },
  {
    "path": "Notes/09_Packages/02_Third_party.md",
    "chars": 4336,
    "preview": "[Contents](../Contents.md) \\| [Previous (9.1 Packages)](01_Packages.md) \\| [Next (9.3 Distribution)](03_Distribution.md)"
  },
  {
    "path": "Notes/09_Packages/03_Distribution.md",
    "chars": 2481,
    "preview": "[Contents](../Contents.md) \\| [Previous (9.2 Third Party Packages)](02_Third_party.md) \\| [Next (The End)](TheEnd.md)\n\n#"
  },
  {
    "path": "Notes/09_Packages/TheEnd.md",
    "chars": 364,
    "preview": "# The End!\n\nYou've made it to the end of the course.  Thanks for your time and your attention.\nMay your future Python ha"
  },
  {
    "path": "Notes/Contents.md",
    "chars": 782,
    "preview": "# Practical Python Programming\n\n## Table of Contents\n\n* [0. Course Setup (READ FIRST!)](00_Setup.md)\n* [1. Introduction "
  },
  {
    "path": "Notes/InstructorNotes.md",
    "chars": 16720,
    "preview": "# Practical Python Programming - Instructor Notes\n\nAuthor: David Beazley\n\n## Overview\n\nThis document provides some gener"
  },
  {
    "path": "README.md",
    "chars": 6020,
    "preview": "# Welcome!\n\nWhen I first learned Python nearly 27 years ago, I was immediately\nstruck by how I could productively apply "
  },
  {
    "path": "Solutions/1_10/mortgage.py",
    "chars": 618,
    "preview": "# mortgage.py\n\nprincipal = 500000.0\nrate = 0.05\npayment = 2684.11\ntotal_paid = 0.0\nmonth = 0\n\nextra_payment = 1000.0\next"
  },
  {
    "path": "Solutions/1_27/pcost.py",
    "chars": 287,
    "preview": "# pcost.py\n\ntotal_cost = 0.0\n\nwith open('../../Work/Data/portfolio.csv', 'rt') as f:\n    headers = next(f)\n    for line "
  },
  {
    "path": "Solutions/1_33/pcost.py",
    "chars": 748,
    "preview": "# pcost.py\n\nimport csv\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio f"
  },
  {
    "path": "Solutions/1_5/bounce.py",
    "chars": 137,
    "preview": "# bounce.py\n\nheight = 100\nbounce = 1\nwhile bounce <= 10:\n    height = height * (3/5)\n    print(bounce, round(height, 4))"
  },
  {
    "path": "Solutions/2_11/report.py",
    "chars": 1742,
    "preview": "# report.py\nimport csv\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionarie"
  },
  {
    "path": "Solutions/2_16/pcost.py",
    "chars": 848,
    "preview": "# pcost.py\n\nimport csv\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio f"
  },
  {
    "path": "Solutions/2_16/report.py",
    "chars": 1833,
    "preview": "# report.py\nimport csv\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionarie"
  },
  {
    "path": "Solutions/2_7/report.py",
    "chars": 1347,
    "preview": "# report.py\nimport csv\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionarie"
  },
  {
    "path": "Solutions/3_10/fileparse.py",
    "chars": 1633,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(filename, select=None, types=None, has_headers=True, delimiter=',', silence_err"
  },
  {
    "path": "Solutions/3_14/fileparse.py",
    "chars": 1633,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(filename, select=None, types=None, has_headers=True, delimiter=',', silence_err"
  },
  {
    "path": "Solutions/3_14/pcost.py",
    "chars": 414,
    "preview": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfol"
  },
  {
    "path": "Solutions/3_14/report.py",
    "chars": 1691,
    "preview": "# report.py\n\nimport fileparse\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dict"
  },
  {
    "path": "Solutions/3_16/fileparse.py",
    "chars": 1633,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(filename, select=None, types=None, has_headers=True, delimiter=',', silence_err"
  },
  {
    "path": "Solutions/3_16/pcost.py",
    "chars": 484,
    "preview": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfol"
  },
  {
    "path": "Solutions/3_16/report.py",
    "chars": 1802,
    "preview": "# report.py\n\nimport fileparse\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dict"
  },
  {
    "path": "Solutions/3_18/fileparse.py",
    "chars": 1484,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors"
  },
  {
    "path": "Solutions/3_18/pcost.py",
    "chars": 484,
    "preview": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfol"
  },
  {
    "path": "Solutions/3_18/report.py",
    "chars": 1873,
    "preview": "# report.py\n\nimport fileparse\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dict"
  },
  {
    "path": "Solutions/3_2/report.py",
    "chars": 2148,
    "preview": "# report.py\nimport csv\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionarie"
  },
  {
    "path": "Solutions/3_7/fileparse.py",
    "chars": 1206,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(filename, select=None, types=None, has_headers=True, delimiter=','):\n    '''\n  "
  },
  {
    "path": "Solutions/4_10/fileparse.py",
    "chars": 1484,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors"
  },
  {
    "path": "Solutions/4_10/pcost.py",
    "chars": 468,
    "preview": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfol"
  },
  {
    "path": "Solutions/4_10/report.py",
    "chars": 2177,
    "preview": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\n\ndef read_portfolio(filename):\n    '''\n    Read"
  },
  {
    "path": "Solutions/4_10/stock.py",
    "chars": 584,
    "preview": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    d"
  },
  {
    "path": "Solutions/4_10/tableformat.py",
    "chars": 1963,
    "preview": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n    "
  },
  {
    "path": "Solutions/4_4/fileparse.py",
    "chars": 1484,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors"
  },
  {
    "path": "Solutions/4_4/pcost.py",
    "chars": 468,
    "preview": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfol"
  },
  {
    "path": "Solutions/4_4/report.py",
    "chars": 2056,
    "preview": "# report.py\n\nimport fileparse\nfrom stock import Stock\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio "
  },
  {
    "path": "Solutions/4_4/stock.py",
    "chars": 487,
    "preview": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    d"
  },
  {
    "path": "Solutions/5_8/fileparse.py",
    "chars": 1484,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors"
  },
  {
    "path": "Solutions/5_8/pcost.py",
    "chars": 468,
    "preview": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfol"
  },
  {
    "path": "Solutions/5_8/report.py",
    "chars": 2177,
    "preview": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\n\ndef read_portfolio(filename):\n    '''\n    Read"
  },
  {
    "path": "Solutions/5_8/stock.py",
    "chars": 898,
    "preview": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    _"
  },
  {
    "path": "Solutions/5_8/tableformat.py",
    "chars": 1963,
    "preview": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n    "
  },
  {
    "path": "Solutions/6_12/fileparse.py",
    "chars": 1484,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors"
  },
  {
    "path": "Solutions/6_12/follow.py",
    "chars": 782,
    "preview": "# follow.py\nimport os\nimport time\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being w"
  },
  {
    "path": "Solutions/6_12/pcost.py",
    "chars": 454,
    "preview": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfol"
  },
  {
    "path": "Solutions/6_12/portfolio.py",
    "chars": 722,
    "preview": "# portfolio.py\n\nclass Portfolio:\n    def __init__(self, holdings):\n        self._holdings = holdings\n\n    def __iter__(s"
  },
  {
    "path": "Solutions/6_12/report.py",
    "chars": 2220,
    "preview": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\nfrom portfolio import Portfolio\n\ndef read_portf"
  },
  {
    "path": "Solutions/6_12/stock.py",
    "chars": 898,
    "preview": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    _"
  },
  {
    "path": "Solutions/6_12/tableformat.py",
    "chars": 1963,
    "preview": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n    "
  },
  {
    "path": "Solutions/6_12/ticker.py",
    "chars": 1351,
    "preview": "# ticker.py\n\nimport csv\nimport report\nimport tableformat\nfrom follow import follow\nimport time\n\ndef select_columns(rows,"
  },
  {
    "path": "Solutions/6_15/fileparse.py",
    "chars": 1484,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors"
  },
  {
    "path": "Solutions/6_15/follow.py",
    "chars": 421,
    "preview": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being "
  },
  {
    "path": "Solutions/6_15/pcost.py",
    "chars": 454,
    "preview": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfol"
  },
  {
    "path": "Solutions/6_15/portfolio.py",
    "chars": 718,
    "preview": "# portfolio.py\n\nclass Portfolio:\n    def __init__(self, holdings):\n        self._holdings = holdings\n\n    def __iter__(s"
  },
  {
    "path": "Solutions/6_15/report.py",
    "chars": 2220,
    "preview": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\nfrom portfolio import Portfolio\n\ndef read_portf"
  },
  {
    "path": "Solutions/6_15/stock.py",
    "chars": 898,
    "preview": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    _"
  },
  {
    "path": "Solutions/6_15/tableformat.py",
    "chars": 1963,
    "preview": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n    "
  },
  {
    "path": "Solutions/6_15/ticker.py",
    "chars": 1252,
    "preview": "# ticker.py\n\nimport csv\nimport report\nimport tableformat\nfrom follow import follow\nimport time\n\ndef select_columns(rows,"
  },
  {
    "path": "Solutions/6_3/fileparse.py",
    "chars": 1484,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors"
  },
  {
    "path": "Solutions/6_3/pcost.py",
    "chars": 454,
    "preview": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfol"
  },
  {
    "path": "Solutions/6_3/portfolio.py",
    "chars": 722,
    "preview": "# portfolio.py\n\nclass Portfolio:\n    def __init__(self, holdings):\n        self._holdings = holdings\n\n    def __iter__(s"
  },
  {
    "path": "Solutions/6_3/report.py",
    "chars": 2220,
    "preview": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\nfrom portfolio import Portfolio\n\ndef read_portf"
  },
  {
    "path": "Solutions/6_3/stock.py",
    "chars": 898,
    "preview": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    _"
  },
  {
    "path": "Solutions/6_3/tableformat.py",
    "chars": 1963,
    "preview": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n    "
  },
  {
    "path": "Solutions/6_7/fileparse.py",
    "chars": 1484,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors"
  },
  {
    "path": "Solutions/6_7/follow.py",
    "chars": 782,
    "preview": "# follow.py\nimport os\nimport time\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being w"
  },
  {
    "path": "Solutions/6_7/pcost.py",
    "chars": 454,
    "preview": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfol"
  },
  {
    "path": "Solutions/6_7/portfolio.py",
    "chars": 722,
    "preview": "# portfolio.py\n\nclass Portfolio:\n    def __init__(self, holdings):\n        self._holdings = holdings\n\n    def __iter__(s"
  },
  {
    "path": "Solutions/6_7/report.py",
    "chars": 2220,
    "preview": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\nfrom portfolio import Portfolio\n\ndef read_portf"
  },
  {
    "path": "Solutions/6_7/stock.py",
    "chars": 898,
    "preview": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    _"
  },
  {
    "path": "Solutions/6_7/tableformat.py",
    "chars": 1963,
    "preview": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n    "
  },
  {
    "path": "Solutions/7_10/timethis.py",
    "chars": 432,
    "preview": "# timethis.py\n\nimport time\n\ndef timethis(func):\n    def wrapper(*args, **kwargs):\n        start = time.time()\n        tr"
  },
  {
    "path": "Solutions/7_11/fileparse.py",
    "chars": 1484,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors"
  },
  {
    "path": "Solutions/7_11/follow.py",
    "chars": 421,
    "preview": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being "
  },
  {
    "path": "Solutions/7_11/pcost.py",
    "chars": 454,
    "preview": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfol"
  },
  {
    "path": "Solutions/7_11/portfolio.py",
    "chars": 1208,
    "preview": "# portfolio.py\n\nimport fileparse\nimport stock\n\nclass Portfolio:\n    def __init__(self):\n        self._holdings = []\n\n   "
  },
  {
    "path": "Solutions/7_11/report.py",
    "chars": 1983,
    "preview": "# report.py\n\nimport fileparse\nfrom stock import Stock\nfrom portfolio import Portfolio\nimport tableformat\n\ndef read_portf"
  },
  {
    "path": "Solutions/7_11/stock.py",
    "chars": 762,
    "preview": "# stock.py\n\nfrom typedproperty import String, Integer, Float\n\nclass Stock:\n    '''\n    An instance of a stock holding co"
  },
  {
    "path": "Solutions/7_11/tableformat.py",
    "chars": 1963,
    "preview": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n    "
  },
  {
    "path": "Solutions/7_11/ticker.py",
    "chars": 1240,
    "preview": "# ticker.py\n\nimport csv\nimport report\nimport tableformat\nfrom follow import follow\n\ndef select_columns(rows, indices):\n "
  },
  {
    "path": "Solutions/7_11/timethis.py",
    "chars": 432,
    "preview": "# timethis.py\n\nimport time\n\ndef timethis(func):\n    def wrapper(*args, **kwargs):\n        start = time.time()\n        tr"
  },
  {
    "path": "Solutions/7_11/typedproperty.py",
    "chars": 1120,
    "preview": "# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n    @property\n    def prop(sel"
  },
  {
    "path": "Solutions/7_4/fileparse.py",
    "chars": 1484,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors"
  },
  {
    "path": "Solutions/7_4/follow.py",
    "chars": 421,
    "preview": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being "
  },
  {
    "path": "Solutions/7_4/pcost.py",
    "chars": 454,
    "preview": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfol"
  },
  {
    "path": "Solutions/7_4/portfolio.py",
    "chars": 718,
    "preview": "# portfolio.py\n\nclass Portfolio:\n    def __init__(self, holdings):\n        self._holdings = holdings\n\n    def __iter__(s"
  },
  {
    "path": "Solutions/7_4/report.py",
    "chars": 2260,
    "preview": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\nfrom portfolio import Portfolio\n\ndef read_portf"
  },
  {
    "path": "Solutions/7_4/stock.py",
    "chars": 898,
    "preview": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    _"
  },
  {
    "path": "Solutions/7_4/tableformat.py",
    "chars": 1963,
    "preview": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n    "
  },
  {
    "path": "Solutions/7_4/ticker.py",
    "chars": 1252,
    "preview": "# ticker.py\n\nimport csv\nimport report\nimport tableformat\nfrom follow import follow\nimport time\n\ndef select_columns(rows,"
  },
  {
    "path": "Solutions/7_9/fileparse.py",
    "chars": 1484,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors"
  },
  {
    "path": "Solutions/7_9/follow.py",
    "chars": 421,
    "preview": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being "
  },
  {
    "path": "Solutions/7_9/pcost.py",
    "chars": 454,
    "preview": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfol"
  },
  {
    "path": "Solutions/7_9/portfolio.py",
    "chars": 718,
    "preview": "# portfolio.py\n\nclass Portfolio:\n    def __init__(self, holdings):\n        self._holdings = holdings\n\n    def __iter__(s"
  },
  {
    "path": "Solutions/7_9/report.py",
    "chars": 2260,
    "preview": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\nfrom portfolio import Portfolio\n\ndef read_portf"
  },
  {
    "path": "Solutions/7_9/stock.py",
    "chars": 762,
    "preview": "# stock.py\n\nfrom typedproperty import String, Integer, Float\n\nclass Stock:\n    '''\n    An instance of a stock holding co"
  },
  {
    "path": "Solutions/7_9/tableformat.py",
    "chars": 1963,
    "preview": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n    "
  },
  {
    "path": "Solutions/7_9/ticker.py",
    "chars": 1252,
    "preview": "# ticker.py\n\nimport csv\nimport report\nimport tableformat\nfrom follow import follow\nimport time\n\ndef select_columns(rows,"
  },
  {
    "path": "Solutions/7_9/typedproperty.py",
    "chars": 1119,
    "preview": "# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n    @property\n    def prop(sel"
  },
  {
    "path": "Solutions/8_1/fileparse.py",
    "chars": 1484,
    "preview": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors"
  },
  {
    "path": "Solutions/8_1/follow.py",
    "chars": 421,
    "preview": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being "
  },
  {
    "path": "Solutions/8_1/pcost.py",
    "chars": 454,
    "preview": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfol"
  },
  {
    "path": "Solutions/8_1/portfolio.py",
    "chars": 1208,
    "preview": "# portfolio.py\n\nimport fileparse\nimport stock\n\nclass Portfolio:\n    def __init__(self):\n        self._holdings = []\n\n   "
  },
  {
    "path": "Solutions/8_1/report.py",
    "chars": 1983,
    "preview": "# report.py\n\nimport fileparse\nfrom stock import Stock\nfrom portfolio import Portfolio\nimport tableformat\n\ndef read_portf"
  },
  {
    "path": "Solutions/8_1/stock.py",
    "chars": 762,
    "preview": "# stock.py\n\nfrom typedproperty import String, Integer, Float\n\nclass Stock:\n    '''\n    An instance of a stock holding co"
  },
  {
    "path": "Solutions/8_1/tableformat.py",
    "chars": 1963,
    "preview": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n    "
  },
  {
    "path": "Solutions/8_1/test_stock.py",
    "chars": 714,
    "preview": "# test_stock.py\n\nimport unittest\nimport stock\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s "
  },
  {
    "path": "Solutions/8_1/ticker.py",
    "chars": 1240,
    "preview": "# ticker.py\n\nimport csv\nimport report\nimport tableformat\nfrom follow import follow\n\ndef select_columns(rows, indices):\n "
  },
  {
    "path": "Solutions/8_1/timethis.py",
    "chars": 432,
    "preview": "# timethis.py\n\nimport time\n\ndef timethis(func):\n    def wrapper(*args, **kwargs):\n        start = time.time()\n        tr"
  },
  {
    "path": "Solutions/8_1/typedproperty.py",
    "chars": 861,
    "preview": "# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n    @property\n    def prop(sel"
  },
  {
    "path": "Solutions/8_2/fileparse.py",
    "chars": 1549,
    "preview": "# fileparse.py\nimport csv\nimport logging\nlog = logging.getLogger(__name__)\n\ndef parse_csv(lines, select=None, types=None"
  },
  {
    "path": "Solutions/8_2/follow.py",
    "chars": 421,
    "preview": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being "
  },
  {
    "path": "Solutions/8_2/pcost.py",
    "chars": 454,
    "preview": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfol"
  },
  {
    "path": "Solutions/8_2/portfolio.py",
    "chars": 1208,
    "preview": "# portfolio.py\n\nimport fileparse\nimport stock\n\nclass Portfolio:\n    def __init__(self):\n        self._holdings = []\n\n   "
  },
  {
    "path": "Solutions/8_2/report.py",
    "chars": 1983,
    "preview": "# report.py\n\nimport fileparse\nfrom stock import Stock\nfrom portfolio import Portfolio\nimport tableformat\n\ndef read_portf"
  },
  {
    "path": "Solutions/8_2/stock.py",
    "chars": 762,
    "preview": "# stock.py\n\nfrom typedproperty import String, Integer, Float\n\nclass Stock:\n    '''\n    An instance of a stock holding co"
  },
  {
    "path": "Solutions/8_2/tableformat.py",
    "chars": 1963,
    "preview": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n    "
  },
  {
    "path": "Solutions/8_2/test_stock.py",
    "chars": 714,
    "preview": "# test_stock.py\n\nimport unittest\nimport stock\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s "
  },
  {
    "path": "Solutions/8_2/ticker.py",
    "chars": 1240,
    "preview": "# ticker.py\n\nimport csv\nimport report\nimport tableformat\nfrom follow import follow\n\ndef select_columns(rows, indices):\n "
  },
  {
    "path": "Solutions/8_2/timethis.py",
    "chars": 432,
    "preview": "# timethis.py\n\nimport time\n\ndef timethis(func):\n    def wrapper(*args, **kwargs):\n        start = time.time()\n        tr"
  },
  {
    "path": "Solutions/8_2/typedproperty.py",
    "chars": 861,
    "preview": "# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n    @property\n    def prop(sel"
  },
  {
    "path": "Solutions/9_3/porty-app/README.txt",
    "chars": 667,
    "preview": "Code from Practical Python.\n\nThe \"porty\" directory is a Python package of code that's loaded via \nimport.  The \"print-re"
  },
  {
    "path": "Solutions/9_3/porty-app/portfolio.csv",
    "chars": 127,
    "preview": "name,shares,price\n\"AA\",100,32.20\n\"IBM\",50,91.10\n\"CAT\",150,83.44\n\"MSFT\",200,51.23\n\"GE\",95,40.37\n\"MSFT\",50,65.10\n\"IBM\",100"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Solutions/9_3/porty-app/porty/fileparse.py",
    "chars": 1530,
    "preview": "# fileparse.py\nimport csv\nimport logging\nlog = logging.getLogger(__name__)\n\ndef parse_csv(lines, select=None, types=None"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/follow.py",
    "chars": 421,
    "preview": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being "
  },
  {
    "path": "Solutions/9_3/porty-app/porty/pcost.py",
    "chars": 461,
    "preview": "# pcost.py\n\nfrom . import report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a "
  },
  {
    "path": "Solutions/9_3/porty-app/porty/portfolio.py",
    "chars": 1222,
    "preview": "# portfolio.py\n\nfrom . import fileparse\nfrom . import stock\n\nclass Portfolio:\n    def __init__(self):\n        self._hold"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/report.py",
    "chars": 1999,
    "preview": "# report.py\n\nfrom . import fileparse\nfrom .stock import Stock\nfrom .portfolio import Portfolio\nfrom . import tableformat"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/stock.py",
    "chars": 793,
    "preview": "# stock.py\n\nfrom .typedproperty import String, Integer, Float\n\nclass Stock:\n    '''\n    An instance of a stock holding c"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/tableformat.py",
    "chars": 1963,
    "preview": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n    "
  },
  {
    "path": "Solutions/9_3/porty-app/porty/test_stock.py",
    "chars": 721,
    "preview": "# test_stock.py\n\nimport unittest\nfrom . import stock\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n   "
  },
  {
    "path": "Solutions/9_3/porty-app/porty/ticker.py",
    "chars": 1255,
    "preview": "# ticker.py\n\nimport csv\nfrom . import report\nfrom . import tableformat\nfrom .follow import follow\n\ndef select_columns(ro"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/typedproperty.py",
    "chars": 861,
    "preview": "# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n    @property\n    def prop(sel"
  },
  {
    "path": "Solutions/9_3/porty-app/prices.csv",
    "chars": 379,
    "preview": "\"AA\",9.22\r\n\"AXP\",24.85\r\n\"BA\",44.85\r\n\"BAC\",11.27\r\n\"C\",3.72\r\n\"CAT\",35.46\r\n\"CVX\",66.67\r\n\"DD\",28.47\r\n\"DIS\",24.22\r\n\"GE\",13.48"
  },
  {
    "path": "Solutions/9_3/porty-app/print-report.py",
    "chars": 98,
    "preview": "#!/usr/bin/env python3\n# print-report.py\n\nimport sys\nfrom porty.report import main\nmain(sys.argv)\n"
  },
  {
    "path": "Solutions/9_5/porty-app/MANIFEST.in",
    "chars": 14,
    "preview": "include *.csv\n"
  },
  {
    "path": "Solutions/9_5/porty-app/README.txt",
    "chars": 667,
    "preview": "Code from Practical Python.\n\nThe \"porty\" directory is a Python package of code that's loaded via \nimport.  The \"print-re"
  },
  {
    "path": "Solutions/9_5/porty-app/portfolio.csv",
    "chars": 127,
    "preview": "name,shares,price\n\"AA\",100,32.20\n\"IBM\",50,91.10\n\"CAT\",150,83.44\n\"MSFT\",200,51.23\n\"GE\",95,40.37\n\"MSFT\",50,65.10\n\"IBM\",100"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Solutions/9_5/porty-app/porty/fileparse.py",
    "chars": 1530,
    "preview": "# fileparse.py\nimport csv\nimport logging\nlog = logging.getLogger(__name__)\n\ndef parse_csv(lines, select=None, types=None"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/follow.py",
    "chars": 421,
    "preview": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being "
  },
  {
    "path": "Solutions/9_5/porty-app/porty/pcost.py",
    "chars": 461,
    "preview": "# pcost.py\n\nfrom . import report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a "
  },
  {
    "path": "Solutions/9_5/porty-app/porty/portfolio.py",
    "chars": 1222,
    "preview": "# portfolio.py\n\nfrom . import fileparse\nfrom . import stock\n\nclass Portfolio:\n    def __init__(self):\n        self._hold"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/report.py",
    "chars": 1999,
    "preview": "# report.py\n\nfrom . import fileparse\nfrom .stock import Stock\nfrom .portfolio import Portfolio\nfrom . import tableformat"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/stock.py",
    "chars": 793,
    "preview": "# stock.py\n\nfrom .typedproperty import String, Integer, Float\n\nclass Stock:\n    '''\n    An instance of a stock holding c"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/tableformat.py",
    "chars": 1963,
    "preview": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n    "
  },
  {
    "path": "Solutions/9_5/porty-app/porty/test_stock.py",
    "chars": 721,
    "preview": "# test_stock.py\n\nimport unittest\nfrom . import stock\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n   "
  },
  {
    "path": "Solutions/9_5/porty-app/porty/ticker.py",
    "chars": 1255,
    "preview": "# ticker.py\n\nimport csv\nfrom . import report\nfrom . import tableformat\nfrom .follow import follow\n\ndef select_columns(ro"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/typedproperty.py",
    "chars": 861,
    "preview": "# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n    @property\n    def prop(sel"
  },
  {
    "path": "Solutions/9_5/porty-app/prices.csv",
    "chars": 379,
    "preview": "\"AA\",9.22\r\n\"AXP\",24.85\r\n\"BA\",44.85\r\n\"BAC\",11.27\r\n\"C\",3.72\r\n\"CAT\",35.46\r\n\"CVX\",66.67\r\n\"DD\",28.47\r\n\"DIS\",24.22\r\n\"GE\",13.48"
  }
]

// ... and 19 more files (download for full content)

About this extraction

This page contains the full source code of the dabeaz-course/practical-python GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 219 files (685.9 KB), approximately 238.8k tokens, and a symbol index with 681 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!