Repository: dabeaz-course/python-mastery Branch: main Commit: a55856bf8985 Files: 255 Total size: 12.5 MB Directory structure: gitextract_qxj7p6fl/ ├── .gitignore ├── Data/ │ ├── ctabus.csv │ ├── dowstocks.csv │ ├── missing.csv │ ├── portfolio.csv │ ├── portfolio2.csv │ ├── portfolio3.csv │ ├── portfolio_noheader.csv │ ├── prices.csv │ ├── stocksim.py │ └── words.txt ├── Exercises/ │ ├── README.md │ ├── ex1_1.md │ ├── ex1_2.md │ ├── ex1_3.md │ ├── ex1_4.md │ ├── ex1_5.md │ ├── ex1_6.md │ ├── ex2_1.md │ ├── ex2_2.md │ ├── ex2_3.md │ ├── ex2_4.md │ ├── ex2_5.md │ ├── ex2_6.md │ ├── ex3_1.md │ ├── ex3_2.md │ ├── ex3_3.md │ ├── ex3_4.md │ ├── ex3_5.md │ ├── ex3_6.md │ ├── ex3_7.md │ ├── ex3_8.md │ ├── ex4_1.md │ ├── ex4_2.md │ ├── ex4_3.md │ ├── ex4_4.md │ ├── ex5_1.md │ ├── ex5_2.md │ ├── ex5_3.md │ ├── ex5_4.md │ ├── ex5_5.md │ ├── ex5_6.md │ ├── ex6_1.md │ ├── ex6_2.md │ ├── ex6_3.md │ ├── ex6_4.md │ ├── ex6_5.md │ ├── ex7_1.md │ ├── ex7_2.md │ ├── ex7_3.md │ ├── ex7_4.md │ ├── ex7_5.md │ ├── ex7_6.md │ ├── ex8_1.md │ ├── ex8_2.md │ ├── ex8_3.md │ ├── ex8_4.md │ ├── ex8_5.md │ ├── ex8_6.md │ ├── ex9_1.md │ ├── ex9_2.md │ ├── ex9_3.md │ ├── ex9_4.md │ ├── index.md │ ├── soln1_1.md │ ├── soln1_2.md │ ├── soln1_3.md │ ├── soln1_4.md │ ├── soln1_5.md │ ├── soln1_6.md │ ├── soln2_1.md │ ├── soln2_2.md │ ├── soln2_3.md │ ├── soln2_4.md │ ├── soln2_5.md │ ├── soln2_6.md │ ├── soln3_1.md │ ├── soln3_2.md │ ├── soln3_3.md │ ├── soln3_4.md │ ├── soln3_5.md │ ├── soln3_6.md │ ├── soln3_7.md │ ├── soln3_8.md │ ├── soln4_1.md │ ├── soln4_2.md │ ├── soln4_3.md │ ├── soln4_4.md │ ├── soln5_1.md │ ├── soln5_2.md │ ├── soln5_3.md │ ├── soln5_4.md │ ├── soln5_5.md │ ├── soln5_6.md │ ├── soln6_1.md │ ├── soln6_2.md │ ├── soln6_3.md │ ├── soln6_4.md │ ├── soln6_5.md │ ├── soln7_1.md │ ├── soln7_2.md │ ├── soln7_3.md │ ├── soln7_4.md │ ├── soln7_5.md │ ├── soln7_6.md │ ├── soln8_1.md │ ├── soln8_2.md │ ├── soln8_3.md │ ├── soln8_4.md │ ├── soln8_5.md │ ├── soln8_6.md │ ├── soln9_1.md │ ├── soln9_2.md │ ├── soln9_3.md │ └── soln9_4.md ├── LICENSE.md ├── README.md └── Solutions/ ├── 1_1/ │ └── art.py ├── 1_3/ │ └── pcost.py ├── 1_4/ │ └── pcost.py ├── 1_5/ │ └── stock.py ├── 1_6/ │ └── pcost.py ├── 2_1/ │ └── readrides.py ├── 2_2/ │ ├── cta.py │ ├── readport.py │ └── readrides.py ├── 2_4/ │ └── mutint.py ├── 2_5/ │ ├── cta.py │ └── readrides.py ├── 2_6/ │ ├── colreader.py │ ├── cta.py │ └── reader.py ├── 3_1/ │ └── stock.py ├── 3_2/ │ ├── stock.py │ └── tableformat.py ├── 3_3/ │ ├── reader.py │ ├── stock.py │ └── tableformat.py ├── 3_4/ │ └── stock.py ├── 3_5/ │ ├── reader.py │ ├── stock.py │ └── tableformat.py ├── 3_6/ │ ├── reader.py │ ├── stock.py │ └── tableformat.py ├── 3_7/ │ ├── reader.py │ ├── stock.py │ └── tableformat.py ├── 3_8/ │ ├── reader.py │ ├── stock.py │ └── tableformat.py ├── 4_2/ │ └── validate.py ├── 4_3/ │ └── validate.py ├── 5_2/ │ ├── reader.py │ └── stock.py ├── 5_3/ │ ├── reader.py │ └── stock.py ├── 5_4/ │ └── typedproperty.py ├── 5_5/ │ └── reader.py ├── 5_6/ │ ├── stock.py │ └── teststock.py ├── 6_1/ │ ├── stock.py │ ├── structure.py │ └── teststock.py ├── 6_2/ │ ├── stock.py │ ├── structure.py │ └── teststock.py ├── 6_3/ │ ├── stock.py │ ├── structure.py │ └── teststock.py ├── 6_4/ │ ├── stock.py │ ├── structure.py │ └── teststock.py ├── 6_5/ │ └── validate.py ├── 7_1/ │ ├── logcall.py │ ├── sample.py │ └── validate.py ├── 7_2/ │ ├── logcall.py │ ├── sample.py │ ├── spam.py │ └── validate.py ├── 7_3/ │ ├── reader.py │ ├── stock.py │ ├── structure.py │ ├── teststock.py │ └── validate.py ├── 7_4/ │ ├── stock.py │ ├── structure.py │ ├── teststock.py │ └── validate.py ├── 7_5/ │ └── mymeta.py ├── 7_6/ │ ├── reader.py │ ├── stock.py │ ├── structure.py │ ├── tableformat.py │ ├── teststock.py │ └── validate.py ├── 8_1/ │ ├── follow.py │ ├── reader.py │ ├── stock.py │ ├── structure.py │ ├── teststock.py │ └── validate.py ├── 8_2/ │ ├── follow.py │ ├── structure.py │ ├── tableformat.py │ ├── ticker.py │ └── validate.py ├── 8_3/ │ ├── cofollow.py │ ├── coticker.py │ ├── structure.py │ ├── tableformat.py │ └── validate.py ├── 8_4/ │ ├── cofollow.py │ └── follow.py ├── 8_5/ │ ├── multitask.py │ └── server.py ├── 8_6/ │ ├── asyncserver.py │ ├── cofollow.py │ ├── coticker.py │ ├── server.py │ ├── structure.py │ ├── tableformat.py │ └── validate.py ├── 9_1/ │ └── simplemod.py ├── 9_2/ │ ├── stock.py │ └── structly/ │ ├── __init__.py │ ├── reader.py │ ├── structure.py │ ├── tableformat.py │ └── validate.py ├── 9_3/ │ ├── stock.py │ └── structly/ │ ├── __init__.py │ ├── reader.py │ ├── structure.py │ ├── tableformat/ │ │ ├── __init__.py │ │ ├── formats/ │ │ │ ├── __init__.py │ │ │ ├── csv.py │ │ │ ├── html.py │ │ │ └── text.py │ │ └── formatter.py │ └── validate.py ├── 9_4/ │ ├── stock.py │ └── structly/ │ ├── __init__.py │ ├── reader.py │ ├── structure.py │ ├── tableformat/ │ │ ├── __init__.py │ │ ├── formats/ │ │ │ ├── __init__.py │ │ │ ├── csv.py │ │ │ ├── html.py │ │ │ ├── text.py │ │ │ └── tsv.py │ │ └── formatter.py │ └── validate.py └── README.md ================================================ 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/ 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/ cover/ # 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 .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .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 # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control #poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/#use-with-ide .pdm.toml # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __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/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ ================================================ FILE: Data/ctabus.csv ================================================ [File too large to display: 11.8 MB] ================================================ FILE: Data/dowstocks.csv ================================================ "AA",39.48,"6/11/2007","9:36am",-0.18,39.67,39.69,39.45,181800 "AIG",71.38,"6/11/2007","9:36am",-0.15,71.29,71.60,71.15,195500 "AXP",62.58,"6/11/2007","9:36am",-0.46,62.79,63.00,62.57,935000 "BA",98.31,"6/11/2007","9:36am",+0.12,98.25,98.58,98.19,104800 "C",53.08,"6/11/2007","9:36am",-0.25,53.20,53.25,53.03,360900 "CAT",78.29,"6/11/2007","9:36am",-0.23,78.32,78.33,78.06,225400 "DD",50.75,"6/11/2007","9:36am",-0.38,51.13,51.14,50.69,96900 "DIS",34.20,"6/11/2007","9:35am",0.00,34.28,34.30,34.12,191000 "GE",37.23,"6/11/2007","9:36am",-0.09,37.07,37.27,37.05,694300 "GM",31.44,"6/11/2007","9:36am",+0.44,31.00,31.62,30.90,715429 "HD",37.67,"6/11/2007","9:35am",-0.28,37.78,37.83,37.62,218369 "HON",57.12,"6/11/2007","9:35am",-0.26,57.25,57.33,57.11,131100 "HPQ",45.81,"6/11/2007","9:36am",+0.11,45.80,45.85,45.46,303200 "IBM",102.86,"6/11/2007","9:35am",-0.21,102.87,102.99,102.50,151700 "INTC",21.84,"6/11/2007","9:40am",+0.01,21.70,21.85,21.69,2927268 "JNJ",62.25,"6/11/2007","9:35am",+0.12,62.89,62.89,62.15,343500 "JPM",50.35,"6/11/2007","9:36am",-0.06,50.41,50.49,50.27,351100 "KO",51.65,"6/11/2007","9:35am",-0.02,51.67,51.73,51.54,3981400 "MCD",51.11,"6/11/2007","9:35am",-0.30,51.47,51.47,51.00,169100 "MMM",85.60,"6/11/2007","9:35am",-0.34,85.94,85.98,85.50,190800 "MO",70.09,"6/11/2007","9:36am",-0.21,70.25,70.30,70.04,471200 "MRK",50.21,"6/11/2007","9:36am",+0.07,50.30,50.46,50.04,1453300 "MSFT",30.08,"6/11/2007","9:41am",+0.03,30.05,30.09,29.93,6166010 "PFE",26.40,"6/11/2007","9:36am",-0.12,26.50,26.50,26.34,835600 "PG",62.79,"6/11/2007","9:35am",-0.28,62.80,62.87,62.75,256000 "T",40.03,"6/11/2007","9:35am",-0.23,40.20,40.25,39.89,691400 "UTX",69.81,"6/11/2007","9:36am",-0.42,69.85,70.20,69.51,153900 "VZ",42.92,"6/11/2007","9:35am",-0.15,42.95,43.00,42.89,221000 "WMT",49.78,"6/11/2007","9:36am",-0.30,49.90,50.00,49.76,676200 "XOM",82.50,"6/11/2007","9:36am",-0.18,82.68,82.84,82.41,481200 "AA",39.59,"6/11/2007","9:40am",-0.07,39.67,39.69,39.43,252600 "AIG",71.46,"6/11/2007","9:40am",-0.07,71.29,71.60,71.15,276900 "AXP",62.71,"6/11/2007","9:40am",-0.33,62.79,63.00,62.42,1002100 "BA",98.31,"6/11/2007","9:40am",+0.12,98.25,98.58,98.17,149700 "C",53.14,"6/11/2007","9:40am",-0.19,53.20,53.25,53.02,546500 "CAT",78.49,"6/11/2007","9:40am",-0.03,78.32,78.49,78.06,262812 "DD",50.85,"6/11/2007","9:40am",-0.28,51.13,51.14,50.69,155000 "DIS",34.36,"6/11/2007","9:40am",+0.16,34.28,34.38,34.12,276900 "GE",37.30,"6/11/2007","9:40am",-0.02,37.07,37.32,37.05,1039900 "GM",31.40,"6/11/2007","9:40am",+0.40,31.00,31.62,30.90,1074079 "HD",37.72,"6/11/2007","9:40am",-0.23,37.78,37.83,37.62,321769 "HON",57.22,"6/11/2007","9:40am",-0.16,57.25,57.33,57.05,150400 "HPQ",45.954,"6/11/2007","9:40am",+0.254,45.80,45.98,45.46,424600 "IBM",102.95,"6/11/2007","9:40am",-0.12,102.87,102.99,102.50,229500 "INTC",21.85,"6/11/2007","9:46am",+0.02,21.70,21.86,21.69,3605793 "JNJ",62.42,"6/11/2007","9:40am",+0.29,62.89,62.89,62.15,433600 "JPM",50.42,"6/11/2007","9:40am",+0.01,50.41,50.49,50.27,461400 "KO",51.67,"6/11/2007","9:40am",0.00,51.67,51.73,51.54,4010650 "MCD",51.42,"6/11/2007","9:40am",+0.01,51.47,51.47,50.98,245800 "MMM",85.45,"6/11/2007","9:40am",-0.49,85.94,85.98,85.44,225500 "MO",69.95,"6/11/2007","9:40am",-0.35,70.25,70.30,69.95,543600 "MRK",50.58,"6/11/2007","9:40am",+0.44,50.30,50.63,50.04,1586100 "MSFT",30.14,"6/11/2007","9:46am",+0.09,30.05,30.15,29.93,6758871 "PFE",26.46,"6/11/2007","9:40am",-0.06,26.50,26.50,26.34,1101900 "PG",62.97,"6/11/2007","9:40am",-0.10,62.80,63.00,62.75,431246 "T",40.19,"6/11/2007","9:40am",-0.07,40.20,40.25,39.89,874100 "UTX",69.88,"6/11/2007","9:40am",-0.35,69.85,70.20,69.51,191800 "VZ",43.06,"6/11/2007","9:40am",-0.01,42.95,43.07,42.89,322700 "WMT",49.72,"6/11/2007","9:40am",-0.36,49.90,50.00,49.70,822700 "XOM",82.41,"6/11/2007","9:40am",-0.27,82.68,82.84,82.35,705500 "AA",39.78,"6/11/2007","9:46am",+0.12,39.67,39.7946,39.43,347300 "AIG",71.41,"6/11/2007","9:46am",-0.12,71.29,71.60,71.15,378000 "AXP",62.87,"6/11/2007","9:46am",-0.17,62.79,63.00,62.42,1033500 "BA",98.50,"6/11/2007","9:46am",+0.31,98.25,98.58,98.17,195100 "C",53.13,"6/11/2007","9:46am",-0.20,53.20,53.25,53.02,715514 "CAT",78.77,"6/11/2007","9:46am",+0.25,78.32,78.8128,78.06,329712 "DD",50.81,"6/11/2007","9:46am",-0.32,51.13,51.14,50.69,198300 "DIS",34.34,"6/11/2007","9:46am",+0.14,34.28,34.44,34.12,392500 "GE",37.27,"6/11/2007","9:46am",-0.05,37.07,37.34,37.05,1311900 "GM",31.42,"6/11/2007","9:46am",+0.42,31.00,31.62,30.90,1340279 "HD",37.75,"6/11/2007","9:46am",-0.20,37.78,37.83,37.62,459769 "HON",57.20,"6/11/2007","9:46am",-0.18,57.25,57.33,57.05,185700 "HPQ",46.14,"6/11/2007","9:46am",+0.44,45.80,46.15,45.46,797800 "IBM",103.39,"6/11/2007","9:46am",+0.32,102.87,103.47,102.50,413800 "INTC",21.85,"6/11/2007","9:50am",+0.02,21.70,21.93,21.69,4380516 "JNJ",62.52,"6/11/2007","9:46am",+0.39,62.89,62.89,62.15,548200 "JPM",50.48,"6/11/2007","9:46am",+0.07,50.41,50.55,50.27,657600 "KO",51.67,"6/11/2007","9:45am",0.00,51.67,51.77,51.54,4092550 "MCD",51.36,"6/11/2007","9:46am",-0.05,51.47,51.47,50.98,343719 "MMM",85.48,"6/11/2007","9:46am",-0.46,85.94,85.98,85.41,270900 "MO",70.00,"6/11/2007","9:46am",-0.30,70.25,70.30,69.88,723100 "MRK",50.50,"6/11/2007","9:46am",+0.36,50.30,50.63,50.04,1689100 "MSFT",30.175,"6/11/2007","9:50am",+0.125,30.05,30.20,29.93,7234309 "PFE",26.48,"6/11/2007","9:45am",-0.04,26.50,26.52,26.34,1363300 "PG",62.89,"6/11/2007","9:46am",-0.18,62.80,63.00,62.75,553446 "T",40.12,"6/11/2007","9:46am",-0.14,40.20,40.25,39.89,1073400 "UTX",69.92,"6/11/2007","9:46am",-0.31,69.85,70.20,69.51,244600 "VZ",43.09,"6/11/2007","9:46am",+0.02,42.95,43.17,42.89,515110 "WMT",49.78,"6/11/2007","9:46am",-0.30,49.90,50.00,49.65,949000 "XOM",82.60,"6/11/2007","9:46am",-0.08,82.68,82.84,82.35,934200 "AA",40.15,"6/11/2007","9:51am",+0.49,39.67,40.15,39.43,510630 "AIG",71.50,"6/11/2007","9:50am",-0.03,71.29,71.60,71.15,441900 "AXP",62.99,"6/11/2007","9:50am",-0.05,62.79,63.07,62.42,1067820 "BA",98.73,"6/11/2007","9:50am",+0.54,98.25,98.79,98.17,233900 "C",53.15,"6/11/2007","9:51am",-0.18,53.20,53.25,53.02,885114 "CAT",78.88,"6/11/2007","9:50am",+0.36,78.32,78.99,78.06,386652 "DD",51.13,"6/11/2007","9:50am",0.00,51.13,51.14,50.69,274100 "DIS",34.42,"6/11/2007","9:51am",+0.22,34.28,34.44,34.12,448200 "GE",37.35,"6/11/2007","9:50am",+0.03,37.07,37.37,37.05,1541100 "GM",31.28,"6/11/2007","9:50am",+0.28,31.00,31.62,30.90,1715679 "HD",37.74,"6/11/2007","9:51am",-0.21,37.78,37.83,37.62,553869 "HON",57.30,"6/11/2007","9:51am",-0.08,57.25,57.40,57.05,218000 "HPQ",46.26,"6/11/2007","9:51am",+0.56,45.80,46.26,45.46,1497780 "IBM",103.3781,"6/11/2007","9:50am",+0.3081,102.87,103.47,102.50,485800 "INTC",21.83,"6/11/2007","9:55am",0.00,21.70,21.93,21.69,4802496 "JNJ",62.68,"6/11/2007","9:51am",+0.55,62.89,62.89,62.15,756500 "JPM",50.50,"6/11/2007","9:50am",+0.09,50.41,50.55,50.27,851700 "KO",51.79,"6/11/2007","9:50am",+0.12,51.67,51.796,51.54,4175960 "MCD",51.39,"6/11/2007","9:50am",-0.02,51.47,51.47,50.98,407819 "MMM",85.74,"6/11/2007","9:50am",-0.20,85.94,85.98,85.41,311100 "MO",70.08,"6/11/2007","9:51am",-0.22,70.25,70.30,69.88,840100 "MRK",50.45,"6/11/2007","9:50am",+0.31,50.30,50.63,50.04,1830600 "MSFT",30.22,"6/11/2007","9:56am",+0.17,30.05,30.24,29.93,7724578 "PFE",26.45,"6/11/2007","9:51am",-0.07,26.50,26.52,26.34,1805950 "PG",63.10,"6/11/2007","9:50am",+0.03,62.80,63.10,62.75,715146 "T",40.16,"6/11/2007","9:50am",-0.10,40.20,40.25,39.89,1199500 "UTX",70.06,"6/11/2007","9:50am",-0.17,69.85,70.20,69.51,270900 "VZ",43.16,"6/11/2007","9:50am",+0.09,42.95,43.18,42.89,614810 "WMT",49.87,"6/11/2007","9:51am",-0.21,49.90,50.00,49.65,1181700 "XOM",82.77,"6/11/2007","9:50am",+0.09,82.68,82.86,82.35,1121100 "AA",39.97,"6/11/2007","9:55am",+0.31,39.67,40.18,39.43,598130 "AIG",71.43,"6/11/2007","9:55am",-0.10,71.29,71.60,71.15,487600 "AXP",62.89,"6/11/2007","9:55am",-0.15,62.79,63.07,62.42,1084520 "BA",98.66,"6/11/2007","9:56am",+0.47,98.25,98.79,98.17,265800 "C",53.14,"6/11/2007","9:56am",-0.19,53.20,53.25,53.02,954314 "CAT",78.87,"6/11/2007","9:55am",+0.35,78.32,78.99,78.06,425952 "DD",51.18,"6/11/2007","9:55am",+0.05,51.13,51.21,50.69,341500 "DIS",34.39,"6/11/2007","9:55am",+0.19,34.28,34.44,34.12,523200 "GE",37.39,"6/11/2007","9:56am",+0.07,37.07,37.40,37.05,1786900 "GM",31.31,"6/11/2007","9:55am",+0.31,31.00,31.62,30.90,2399379 "HD",37.75,"6/11/2007","9:55am",-0.20,37.78,37.83,37.62,622969 "HON",57.29,"6/11/2007","9:56am",-0.09,57.25,57.40,57.05,245000 "HPQ",46.25,"6/11/2007","9:56am",+0.55,45.80,46.27,45.46,1615680 "IBM",103.60,"6/11/2007","9:56am",+0.53,102.87,103.62,102.50,569900 "INTC",21.85,"6/11/2007","10:00am",+0.02,21.70,21.95,21.69,5440945 "JNJ",62.74,"6/11/2007","9:56am",+0.61,62.89,62.89,62.15,887700 "JPM",50.48,"6/11/2007","9:56am",+0.07,50.41,50.55,50.27,924300 "KO",51.63,"6/11/2007","9:56am",-0.04,51.67,51.82,51.54,4247360 "MCD",51.40,"6/11/2007","9:56am",-0.01,51.47,51.47,50.98,448219 "MMM",85.69,"6/11/2007","9:55am",-0.25,85.94,85.98,85.41,323200 "MO",70.10,"6/11/2007","9:56am",-0.20,70.25,70.30,69.88,932900 "MRK",50.38,"6/11/2007","9:55am",+0.24,50.30,50.63,50.04,1943600 "MSFT",30.20,"6/11/2007","10:01am",+0.15,30.05,30.24,29.93,8053754 "PFE",26.48,"6/11/2007","9:55am",-0.04,26.50,26.53,26.34,2029150 "PG",63.07,"6/11/2007","9:56am",0.00,62.80,63.10,62.75,786646 "T",40.08,"6/11/2007","9:56am",-0.18,40.20,40.25,39.89,1364200 "UTX",70.06,"6/11/2007","9:56am",-0.17,69.85,70.20,69.51,289300 "VZ",43.17,"6/11/2007","9:56am",+0.10,42.95,43.19,42.89,683810 "WMT",49.88,"6/11/2007","9:56am",-0.20,49.90,50.00,49.65,1321100 "XOM",82.68,"6/11/2007","9:56am",0.00,82.68,82.86,82.35,1235100 "AA",39.94,"6/11/2007","10:00am",+0.28,39.67,40.18,39.43,660280 "AIG",71.36,"6/11/2007","10:00am",-0.17,71.29,71.60,71.15,575042 "AXP",62.83,"6/11/2007","10:00am",-0.21,62.79,63.07,62.42,1110520 "BA",98.62,"6/11/2007","10:01am",+0.43,98.25,98.79,98.17,285700 "C",53.06,"6/11/2007","10:01am",-0.27,53.20,53.25,53.02,1030199 "CAT",78.66,"6/11/2007","10:00am",+0.14,78.32,78.99,78.06,452352 "DD",51.09,"6/11/2007","10:01am",-0.04,51.13,51.21,50.69,402900 "DIS",34.33,"6/11/2007","10:01am",+0.13,34.28,34.44,34.12,740300 "GE",37.41,"6/11/2007","10:01am",+0.09,37.07,37.41,37.05,2106900 "GM",31.29,"6/11/2007","10:01am",+0.29,31.00,31.62,30.90,2570679 "HD",37.76,"6/11/2007","10:00am",-0.19,37.78,37.83,37.62,683769 "HON",57.335,"6/11/2007","10:01am",-0.045,57.25,57.40,57.05,289100 "HPQ",46.25,"6/11/2007","10:01am",+0.55,45.80,46.29,45.46,1752080 "IBM",103.51,"6/11/2007","10:01am",+0.44,102.87,103.63,102.50,607300 "INTC",21.85,"6/11/2007","10:05am",+0.02,21.70,21.95,21.69,5955030 "JNJ",62.75,"6/11/2007","10:01am",+0.62,62.89,62.89,62.15,1031900 "JPM",50.45,"6/11/2007","10:00am",+0.04,50.41,50.55,50.27,1010000 "KO",51.55,"6/11/2007","10:00am",-0.12,51.67,51.82,51.54,4283460 "MCD",51.34,"6/11/2007","10:01am",-0.07,51.47,51.47,50.98,481919 "MMM",85.75,"6/11/2007","10:00am",-0.19,85.94,85.98,85.41,332600 "MO",70.07,"6/11/2007","10:00am",-0.23,70.25,70.30,69.88,1007630 "MRK",50.32,"6/11/2007","10:01am",+0.18,50.30,50.64,50.04,2165200 "MSFT",30.21,"6/11/2007","10:05am",+0.16,30.05,30.25,29.93,8448925 "PFE",26.45,"6/11/2007","10:01am",-0.07,26.50,26.53,26.34,2252150 "PG",63.02,"6/11/2007","10:01am",-0.05,62.80,63.10,62.75,867746 "T",40.05,"6/11/2007","10:01am",-0.21,40.20,40.25,39.89,1496200 "UTX",70.00,"6/11/2007","10:00am",-0.23,69.85,70.20,69.51,308500 "VZ",43.11,"6/11/2007","10:00am",+0.04,42.95,43.19,42.89,804110 "WMT",49.78,"6/11/2007","10:01am",-0.30,49.90,50.00,49.65,1378500 "XOM",82.71,"6/11/2007","10:00am",+0.03,82.68,82.86,82.35,1379100 "AA",39.92,"6/11/2007","10:05am",+0.26,39.67,40.18,39.43,693080 "AIG",71.29,"6/11/2007","10:05am",-0.24,71.29,71.60,71.15,630742 "AXP",62.74,"6/11/2007","10:05am",-0.30,62.79,63.07,62.42,1149120 "BA",98.47,"6/11/2007","10:06am",+0.28,98.25,98.79,98.17,306700 "C",52.97,"6/11/2007","10:06am",-0.36,53.20,53.25,52.92,1191699 "CAT",78.56,"6/11/2007","10:06am",+0.04,78.32,78.99,78.06,512752 "DD",51.068,"6/11/2007","10:05am",-0.062,51.13,51.21,50.69,490000 "DIS",34.29,"6/11/2007","10:05am",+0.09,34.28,34.44,34.12,790550 "GE",37.36,"6/11/2007","10:06am",+0.04,37.07,37.41,37.05,2388519 "GM",31.35,"6/11/2007","10:05am",+0.35,31.00,31.62,30.90,2770879 "HD",37.75,"6/11/2007","10:06am",-0.20,37.78,37.83,37.62,909569 "HON",57.22,"6/11/2007","10:06am",-0.16,57.25,57.40,57.05,307300 "HPQ",46.22,"6/11/2007","10:05am",+0.52,45.80,46.29,45.46,1904480 "IBM",103.57,"6/11/2007","10:05am",+0.50,102.87,103.63,102.50,666400 "INTC",21.83,"6/11/2007","10:10am",0.00,21.70,21.95,21.69,6319637 "JNJ",62.70,"6/11/2007","10:06am",+0.57,62.89,62.89,62.15,1138900 "JPM",50.35,"6/11/2007","10:05am",-0.06,50.41,50.55,50.27,1112900 "KO",51.57,"6/11/2007","10:06am",-0.10,51.67,51.82,51.53,4320060 "MCD",51.31,"6/11/2007","10:06am",-0.10,51.47,51.47,50.98,533219 "MMM",85.76,"6/11/2007","10:05am",-0.18,85.94,85.98,85.41,384000 "MO",69.99,"6/11/2007","10:05am",-0.31,70.25,70.30,69.88,1191130 "MRK",50.35,"6/11/2007","10:06am",+0.21,50.30,50.64,50.04,2250500 "MSFT",30.18,"6/11/2007","10:11am",+0.13,30.05,30.25,29.93,9049059 "PFE",26.41,"6/11/2007","10:05am",-0.11,26.50,26.53,26.34,2353550 "PG",63.03,"6/11/2007","10:06am",-0.04,62.80,63.10,62.75,920246 "T",40.04,"6/11/2007","10:06am",-0.22,40.20,40.25,39.89,1644100 "UTX",69.95,"6/11/2007","10:05am",-0.28,69.85,70.20,69.51,332000 "VZ",43.04,"6/11/2007","10:05am",-0.03,42.95,43.19,42.89,886910 "WMT",49.70,"6/11/2007","10:05am",-0.38,49.90,50.00,49.65,1450500 "XOM",82.8681,"6/11/2007","10:05am",+0.1881,82.68,82.89,82.35,1544600 "AA",39.91,"6/11/2007","10:11am",+0.25,39.67,40.18,39.43,755880 "AIG",71.29,"6/11/2007","10:11am",-0.24,71.29,71.60,71.15,706842 "AXP",62.79,"6/11/2007","10:10am",-0.25,62.79,63.07,62.42,1183320 "BA",98.45,"6/11/2007","10:11am",+0.26,98.25,98.79,98.17,344500 "C",52.926,"6/11/2007","10:11am",-0.404,53.20,53.25,52.91,1347199 "CAT",78.58,"6/11/2007","10:11am",+0.06,78.32,78.99,78.06,559652 "DD",50.999,"6/11/2007","10:11am",-0.131,51.13,51.21,50.69,542900 "DIS",34.29,"6/11/2007","10:11am",+0.09,34.28,34.44,34.12,836650 "GE",37.38,"6/11/2007","10:11am",+0.06,37.07,37.41,37.05,2643919 "GM",31.30,"6/11/2007","10:11am",+0.30,31.00,31.62,30.90,2943679 "HD",37.75,"6/11/2007","10:11am",-0.20,37.78,37.83,37.62,1015369 "HON",57.23,"6/11/2007","10:11am",-0.15,57.25,57.40,57.05,332200 "HPQ",46.18,"6/11/2007","10:11am",+0.48,45.80,46.29,45.46,2006780 "IBM",103.35,"6/11/2007","10:11am",+0.28,102.87,103.63,102.50,734000 "INTC",21.83,"6/11/2007","10:16am",0.00,21.70,21.95,21.69,6790042 "JNJ",62.71,"6/11/2007","10:11am",+0.58,62.89,62.89,62.15,1274000 "JPM",50.28,"6/11/2007","10:11am",-0.13,50.41,50.55,50.26,1187600 "KO",51.56,"6/11/2007","10:11am",-0.11,51.67,51.82,51.53,4370560 "MCD",51.25,"6/11/2007","10:11am",-0.16,51.47,51.47,50.98,587519 "MMM",85.78,"6/11/2007","10:10am",-0.16,85.94,85.98,85.41,414300 "MO",69.95,"6/11/2007","10:11am",-0.35,70.25,70.30,69.88,1273530 "MRK",50.29,"6/11/2007","10:11am",+0.15,50.30,50.64,50.04,2332500 "MSFT",30.14,"6/11/2007","10:16am",+0.09,30.05,30.25,29.93,9998935 "PFE",26.449,"6/11/2007","10:10am",-0.071,26.50,26.53,26.34,2551150 "PG",63.07,"6/11/2007","10:10am",0.00,62.80,63.10,62.75,1010846 "T",40.04,"6/11/2007","10:10am",-0.22,40.20,40.25,39.89,1922900 "UTX",69.91,"6/11/2007","10:11am",-0.32,69.85,70.20,69.51,354300 "VZ",43.10,"6/11/2007","10:10am",+0.03,42.95,43.19,42.89,998110 "WMT",49.73,"6/11/2007","10:11am",-0.35,49.90,50.00,49.65,1577400 "XOM",83.02,"6/11/2007","10:11am",+0.34,82.68,83.11,82.35,1789600 "AA",39.76,"6/11/2007","10:15am",+0.10,39.67,40.18,39.43,804580 "AIG",71.29,"6/11/2007","10:16am",-0.24,71.29,71.60,71.15,781442 "AXP",62.69,"6/11/2007","10:16am",-0.35,62.79,63.07,62.42,1212120 "BA",98.29,"6/11/2007","10:16am",+0.10,98.25,98.79,98.17,373200 "C",52.91,"6/11/2007","10:16am",-0.42,53.20,53.25,52.84,1455899 "CAT",78.51,"6/11/2007","10:16am",-0.01,78.32,78.99,78.06,607452 "DD",51.02,"6/11/2007","10:16am",-0.11,51.13,51.21,50.69,576600 "DIS",34.25,"6/11/2007","10:16am",+0.05,34.28,34.44,34.12,880150 "GE",37.32,"6/11/2007","10:16am",0.00,37.07,37.41,37.05,2784119 "GM",31.28,"6/11/2007","10:16am",+0.28,31.00,31.62,30.90,3270779 "HD",37.76,"6/11/2007","10:16am",-0.19,37.78,37.83,37.62,1141469 "HON",57.11,"6/11/2007","10:16am",-0.27,57.25,57.40,57.05,379600 "HPQ",46.06,"6/11/2007","10:16am",+0.36,45.80,46.29,45.46,2073280 "IBM",103.17,"6/11/2007","10:16am",+0.10,102.87,103.63,102.50,798600 "INTC",21.84,"6/11/2007","10:20am",+0.01,21.70,21.95,21.69,7235829 "JNJ",62.66,"6/11/2007","10:16am",+0.53,62.89,62.89,62.15,1412600 "JPM",50.23,"6/11/2007","10:16am",-0.18,50.41,50.55,50.20,1304200 "KO",51.52,"6/11/2007","10:16am",-0.15,51.67,51.82,51.50,4471160 "MCD",51.17,"6/11/2007","10:15am",-0.24,51.47,51.47,50.98,640119 "MMM",85.69,"6/11/2007","10:15am",-0.25,85.94,85.98,85.41,436200 "MO",69.90,"6/11/2007","10:15am",-0.40,70.25,70.30,69.88,1350130 "MRK",50.375,"6/11/2007","10:16am",+0.235,50.30,50.64,50.04,2501800 "MSFT",30.105,"6/11/2007","10:21am",+0.055,30.05,30.25,29.93,11615862 "PFE",26.43,"6/11/2007","10:16am",-0.09,26.50,26.53,26.34,2699150 "PG",63.03,"6/11/2007","10:15am",-0.04,62.80,63.10,62.75,1084346 "T",40.04,"6/11/2007","10:16am",-0.22,40.20,40.25,39.89,2049000 "UTX",69.80,"6/11/2007","10:16am",-0.43,69.85,70.20,69.51,390200 "VZ",43.12,"6/11/2007","10:16am",+0.05,42.95,43.19,42.89,1540810 "WMT",49.71,"6/11/2007","10:16am",-0.37,49.90,50.00,49.65,1700900 "XOM",82.79,"6/11/2007","10:16am",+0.11,82.68,83.11,82.35,1945300 "AA",39.7264,"6/11/2007","10:21am",+0.0664,39.67,40.18,39.43,860780 "AIG",71.28,"6/11/2007","10:21am",-0.25,71.29,71.60,71.15,826742 "AXP",62.775,"6/11/2007","10:21am",-0.265,62.79,63.07,62.42,1262720 "BA",98.17,"6/11/2007","10:21am",-0.02,98.25,98.79,98.15,420500 "C",52.89,"6/11/2007","10:20am",-0.44,53.20,53.25,52.84,1554299 "CAT",78.46,"6/11/2007","10:21am",-0.06,78.32,78.99,78.06,645152 "DD",51.02,"6/11/2007","10:20am",-0.11,51.13,51.21,50.69,616000 "DIS",34.29,"6/11/2007","10:21am",+0.09,34.28,34.44,34.12,961150 "GE",37.31,"6/11/2007","10:21am",-0.01,37.07,37.41,37.05,2994719 "GM",31.16,"6/11/2007","10:21am",+0.16,31.00,31.62,30.90,3450979 "HD",37.75,"6/11/2007","10:20am",-0.20,37.78,37.83,37.62,1239969 "HON",57.14,"6/11/2007","10:21am",-0.24,57.25,57.40,57.05,427800 "HPQ",46.08,"6/11/2007","10:21am",+0.38,45.80,46.29,45.46,2169080 "IBM",103.24,"6/11/2007","10:21am",+0.17,102.87,103.63,102.50,1110200 "INTC",21.85,"6/11/2007","10:26am",+0.02,21.70,21.95,21.69,7668158 "JNJ",62.65,"6/11/2007","10:21am",+0.52,62.89,62.89,62.15,1466978 "JPM",50.14,"6/11/2007","10:21am",-0.27,50.41,50.55,50.13,1391100 "KO",51.44,"6/11/2007","10:20am",-0.23,51.67,51.82,51.44,4528060 "MCD",51.19,"6/11/2007","10:20am",-0.22,51.47,51.47,50.98,694019 "MMM",85.58,"6/11/2007","10:20am",-0.36,85.94,85.98,85.41,455600 "MO",69.86,"6/11/2007","10:21am",-0.44,70.25,70.30,69.76,1542230 "MRK",50.34,"6/11/2007","10:21am",+0.20,50.30,50.64,50.04,2582400 "MSFT",30.10,"6/11/2007","10:26am",+0.05,30.05,30.25,29.93,12004109 "PFE",26.43,"6/11/2007","10:21am",-0.09,26.50,26.53,26.34,2986570 "PG",63.06,"6/11/2007","10:21am",-0.01,62.80,63.10,62.75,1165646 "T",40.04,"6/11/2007","10:20am",-0.22,40.20,40.25,39.89,2229600 "UTX",69.66,"6/11/2007","10:21am",-0.57,69.85,70.20,69.51,420400 "VZ",43.12,"6/11/2007","10:21am",+0.05,42.95,43.19,42.89,1615410 "WMT",49.65,"6/11/2007","10:21am",-0.43,49.90,50.00,49.65,1851200 "XOM",82.84,"6/11/2007","10:21am",+0.16,82.68,83.11,82.35,2132200 "AA",39.63,"6/11/2007","10:26am",-0.03,39.67,40.18,39.43,899080 "AIG",71.30,"6/11/2007","10:26am",-0.23,71.29,71.60,71.15,896542 "AXP",62.80,"6/11/2007","10:26am",-0.24,62.79,63.07,62.42,1296620 "BA",98.04,"6/11/2007","10:26am",-0.15,98.25,98.79,97.96,466900 "C",52.91,"6/11/2007","10:26am",-0.42,53.20,53.25,52.81,1672699 "CAT",78.37,"6/11/2007","10:26am",-0.15,78.32,78.99,78.06,704152 "DD",50.94,"6/11/2007","10:25am",-0.19,51.13,51.21,50.69,643200 "DIS",34.24,"6/11/2007","10:26am",+0.04,34.28,34.44,34.12,1025550 "GE",37.26,"6/11/2007","10:26am",-0.06,37.07,37.41,37.05,3290619 "GM",31.22,"6/11/2007","10:26am",+0.22,31.00,31.62,30.90,4096679 "HD",37.75,"6/11/2007","10:26am",-0.20,37.78,37.83,37.62,1559369 "HON",57.10,"6/11/2007","10:26am",-0.28,57.25,57.40,57.03,450900 "HPQ",46.10,"6/11/2007","10:26am",+0.40,45.80,46.29,45.46,2260680 "IBM",103.20,"6/11/2007","10:26am",+0.13,102.87,103.63,102.50,1147300 "INTC",21.84,"6/11/2007","10:31am",+0.01,21.70,21.95,21.69,8125254 "JNJ",62.55,"6/11/2007","10:26am",+0.42,62.89,62.89,62.15,1616778 "JPM",50.14,"6/11/2007","10:26am",-0.27,50.41,50.55,50.05,1498700 "KO",51.39,"6/11/2007","10:26am",-0.28,51.67,51.82,51.32,4607560 "MCD",51.17,"6/11/2007","10:25am",-0.24,51.47,51.47,50.98,740919 "MMM",85.51,"6/11/2007","10:26am",-0.43,85.94,85.98,85.41,478800 "MO",69.85,"6/11/2007","10:26am",-0.45,70.25,70.30,69.76,1670405 "MRK",50.32,"6/11/2007","10:25am",+0.18,50.30,50.64,50.04,2654700 "MSFT",30.09,"6/11/2007","10:31am",+0.04,30.05,30.25,29.93,12221463 "PFE",26.39,"6/11/2007","10:26am",-0.13,26.50,26.53,26.34,3224570 "PG",63.04,"6/11/2007","10:25am",-0.03,62.80,63.10,62.75,1243746 "T",40.001,"6/11/2007","10:26am",-0.259,40.20,40.25,39.89,2441800 "UTX",69.63,"6/11/2007","10:26am",-0.60,69.85,70.20,69.51,446700 "VZ",43.12,"6/11/2007","10:26am",+0.05,42.95,43.19,42.89,1681910 "WMT",49.595,"6/11/2007","10:26am",-0.485,49.90,50.00,49.57,1951400 "XOM",82.79,"6/11/2007","10:26am",+0.11,82.68,83.11,82.35,2330500 "AA",39.555,"6/11/2007","10:31am",-0.105,39.67,40.18,39.43,944980 "AIG",71.32,"6/11/2007","10:30am",-0.21,71.29,71.60,71.15,953042 "AXP",62.79,"6/11/2007","10:31am",-0.25,62.79,63.07,62.42,1329020 "BA",97.85,"6/11/2007","10:31am",-0.34,98.25,98.79,97.74,531200 "C",52.92,"6/11/2007","10:31am",-0.41,53.20,53.25,52.81,1746999 "CAT",78.43,"6/11/2007","10:30am",-0.09,78.32,78.99,78.06,751652 "DD",50.88,"6/11/2007","10:30am",-0.25,51.13,51.21,50.69,698200 "DIS",34.21,"6/11/2007","10:30am",+0.01,34.28,34.44,34.12,1097150 "GE",37.28,"6/11/2007","10:31am",-0.04,37.07,37.41,37.05,3653019 "GM",31.15,"6/11/2007","10:31am",+0.15,31.00,31.62,30.90,4256179 "HD",37.75,"6/11/2007","10:31am",-0.20,37.78,37.83,37.62,1712669 "HON",57.09,"6/11/2007","10:30am",-0.29,57.25,57.40,57.03,481100 "HPQ",46.08,"6/11/2007","10:30am",+0.38,45.80,46.29,45.46,2341480 "IBM",103.20,"6/11/2007","10:30am",+0.13,102.87,103.63,102.50,1182000 "INTC",21.85,"6/11/2007","10:36am",+0.02,21.70,21.95,21.69,8728776 "JNJ",62.59,"6/11/2007","10:31am",+0.46,62.89,62.89,62.15,1771778 "JPM",50.13,"6/11/2007","10:30am",-0.28,50.41,50.55,50.05,1552000 "KO",51.34,"6/11/2007","10:31am",-0.33,51.67,51.82,51.32,4669560 "MCD",51.20,"6/11/2007","10:30am",-0.21,51.47,51.47,50.98,779219 "MMM",85.54,"6/11/2007","10:30am",-0.40,85.94,85.98,85.41,502100 "MO",69.89,"6/11/2007","10:31am",-0.41,70.25,70.30,69.76,1759605 "MRK",50.35,"6/11/2007","10:30am",+0.21,50.30,50.64,50.04,2706300 "MSFT",30.11,"6/11/2007","10:36am",+0.06,30.05,30.25,29.93,12723552 "PFE",26.38,"6/11/2007","10:30am",-0.14,26.50,26.53,26.34,3420170 "PG",63.07,"6/11/2007","10:31am",0.00,62.80,63.10,62.75,1285746 "T",40.05,"6/11/2007","10:30am",-0.21,40.20,40.25,39.89,2609700 "UTX",69.60,"6/11/2007","10:31am",-0.63,69.85,70.20,69.51,500000 "VZ",43.15,"6/11/2007","10:30am",+0.08,42.95,43.19,42.89,1759910 "WMT",49.61,"6/11/2007","10:31am",-0.47,49.90,50.00,49.57,2038300 "XOM",82.69,"6/11/2007","10:31am",+0.01,82.68,83.11,82.35,2496800 "AA",39.56,"6/11/2007","10:36am",-0.10,39.67,40.18,39.43,999880 "AIG",71.37,"6/11/2007","10:36am",-0.16,71.29,71.60,71.15,991742 "AXP",62.89,"6/11/2007","10:36am",-0.15,62.79,63.07,62.42,1356220 "BA",98.00,"6/11/2007","10:36am",-0.19,98.25,98.79,97.74,561900 "C",52.97,"6/11/2007","10:36am",-0.36,53.20,53.25,52.81,1864099 "CAT",78.53,"6/11/2007","10:36am",+0.01,78.32,78.99,78.06,785752 "DD",50.85,"6/11/2007","10:35am",-0.28,51.13,51.21,50.69,754500 "DIS",34.22,"6/11/2007","10:36am",+0.02,34.28,34.44,34.12,1157550 "GE",37.30,"6/11/2007","10:36am",-0.02,37.07,37.41,37.05,3926919 "GM",31.15,"6/11/2007","10:36am",+0.15,31.00,31.62,30.90,4421279 "HD",37.76,"6/11/2007","10:36am",-0.19,37.78,37.83,37.62,1911069 "HON",57.08,"6/11/2007","10:36am",-0.30,57.25,57.40,57.00,530800 "HPQ",46.01,"6/11/2007","10:35am",+0.31,45.80,46.29,45.46,2464880 "IBM",103.19,"6/11/2007","10:36am",+0.12,102.87,103.63,102.50,1242400 "INTC",21.88,"6/11/2007","10:41am",+0.05,21.70,21.95,21.69,9039718 "JNJ",62.54,"6/11/2007","10:35am",+0.41,62.89,62.89,62.15,1848378 "JPM",50.17,"6/11/2007","10:36am",-0.24,50.41,50.55,50.05,1615800 "KO",51.37,"6/11/2007","10:35am",-0.30,51.67,51.82,51.32,4715160 "MCD",51.19,"6/11/2007","10:36am",-0.22,51.47,51.47,50.98,862419 "MMM",85.60,"6/11/2007","10:36am",-0.34,85.94,85.98,85.41,535100 "MO",69.90,"6/11/2007","10:36am",-0.40,70.25,70.30,69.76,1818105 "MRK",50.405,"6/11/2007","10:36am",+0.265,50.30,50.64,50.04,2773500 "MSFT",30.08,"6/11/2007","10:41am",+0.03,30.05,30.25,29.93,13110210 "PFE",26.37,"6/11/2007","10:36am",-0.15,26.50,26.53,26.34,3656096 "PG",63.07,"6/11/2007","10:36am",0.00,62.80,63.10,62.75,1344746 "T",40.03,"6/11/2007","10:36am",-0.23,40.20,40.25,39.89,2759900 "UTX",69.6875,"6/11/2007","10:36am",-0.5425,69.85,70.20,69.51,573400 "VZ",43.17,"6/11/2007","10:36am",+0.10,42.95,43.19,42.89,1809710 "WMT",49.71,"6/11/2007","10:36am",-0.37,49.90,50.00,49.56,2171500 "XOM",82.87,"6/11/2007","10:36am",+0.19,82.68,83.11,82.35,2657200 "AA",39.59,"6/11/2007","10:41am",-0.07,39.67,40.18,39.43,1035980 "AIG",71.38,"6/11/2007","10:41am",-0.15,71.29,71.60,71.15,1036342 "AXP",62.99,"6/11/2007","10:40am",-0.05,62.79,63.07,62.42,1401320 "BA",97.98,"6/11/2007","10:40am",-0.21,98.25,98.79,97.74,586200 "C",53.04,"6/11/2007","10:41am",-0.29,53.20,53.25,52.81,1974394 "CAT",78.47,"6/11/2007","10:40am",-0.05,78.32,78.99,78.06,811452 "DD",50.75,"6/11/2007","10:41am",-0.38,51.13,51.21,50.69,816700 "DIS",34.22,"6/11/2007","10:41am",+0.02,34.28,34.44,34.12,1264950 "GE",37.32,"6/11/2007","10:41am",0.00,37.07,37.41,37.05,4056919 "GM",31.15,"6/11/2007","10:41am",+0.15,31.00,31.62,30.90,4500679 "HD",37.74,"6/11/2007","10:40am",-0.21,37.78,37.83,37.62,1949569 "HON",57.05,"6/11/2007","10:40am",-0.33,57.25,57.40,57.00,566900 "HPQ",45.95,"6/11/2007","10:41am",+0.25,45.80,46.29,45.46,2574530 "IBM",103.23,"6/11/2007","10:40am",+0.16,102.87,103.63,102.50,1272000 "INTC",21.88,"6/11/2007","10:46am",+0.05,21.70,21.95,21.69,9537433 "JNJ",62.52,"6/11/2007","10:41am",+0.39,62.89,62.89,62.15,1896678 "JPM",50.18,"6/11/2007","10:41am",-0.23,50.41,50.55,50.05,1682000 "KO",51.38,"6/11/2007","10:40am",-0.29,51.67,51.82,51.32,4744560 "MCD",51.20,"6/11/2007","10:40am",-0.21,51.47,51.47,50.98,903219 "MMM",85.61,"6/11/2007","10:40am",-0.33,85.94,85.98,85.41,544900 "MO",69.95,"6/11/2007","10:40am",-0.35,70.25,70.30,69.76,1858005 "MRK",50.49,"6/11/2007","10:40am",+0.35,50.30,50.64,50.04,2889100 "MSFT",30.022,"6/11/2007","10:45am",-0.028,30.05,30.25,29.93,13503536 "PFE",26.365,"6/11/2007","10:40am",-0.155,26.50,26.53,26.34,3770896 "PG",63.08,"6/11/2007","10:41am",+0.01,62.80,63.12,62.75,1435146 "T",39.99,"6/11/2007","10:41am",-0.27,40.20,40.25,39.89,2921500 "UTX",69.77,"6/11/2007","10:41am",-0.46,69.85,70.20,69.51,589900 "VZ",43.19,"6/11/2007","10:40am",+0.12,42.95,43.19,42.89,1878010 "WMT",49.625,"6/11/2007","10:41am",-0.455,49.90,50.00,49.56,2239000 "XOM",82.89,"6/11/2007","10:40am",+0.21,82.68,83.11,82.35,2766800 "AA",39.54,"6/11/2007","10:46am",-0.12,39.67,40.18,39.43,1080380 "AIG",71.32,"6/11/2007","10:46am",-0.21,71.29,71.60,71.15,1071642 "AXP",62.85,"6/11/2007","10:46am",-0.19,62.79,63.07,62.42,1441520 "BA",98.00,"6/11/2007","10:45am",-0.19,98.25,98.79,97.74,632300 "C",52.95,"6/11/2007","10:46am",-0.38,53.20,53.25,52.81,2045194 "CAT",78.50,"6/11/2007","10:45am",-0.02,78.32,78.99,78.06,834252 "DD",50.64,"6/11/2007","10:46am",-0.49,51.13,51.21,50.63,893400 "DIS",34.21,"6/11/2007","10:45am",+0.01,34.28,34.44,34.12,1344850 "GE",37.2627,"6/11/2007","10:46am",-0.0573,37.07,37.41,37.05,4537419 "GM",31.13,"6/11/2007","10:45am",+0.13,31.00,31.62,30.90,4598979 "HD",37.73,"6/11/2007","10:46am",-0.22,37.78,37.83,37.62,2039969 "HON",57.05,"6/11/2007","10:45am",-0.33,57.25,57.40,57.00,611200 "HPQ",45.89,"6/11/2007","10:46am",+0.19,45.80,46.29,45.46,2729030 "IBM",103.19,"6/11/2007","10:46am",+0.12,102.87,103.63,102.50,1319400 "INTC",21.90,"6/11/2007","10:51am",+0.07,21.70,21.95,21.69,9897575 "JNJ",62.42,"6/11/2007","10:46am",+0.29,62.89,62.89,62.15,1948833 "JPM",50.20,"6/11/2007","10:46am",-0.21,50.41,50.55,50.05,1760600 "KO",51.38,"6/11/2007","10:46am",-0.29,51.67,51.82,51.32,4787060 "MCD",51.21,"6/11/2007","10:45am",-0.20,51.47,51.47,50.98,957919 "MMM",85.54,"6/11/2007","10:45am",-0.40,85.94,85.98,85.41,565800 "MO",69.96,"6/11/2007","10:45am",-0.34,70.25,70.30,69.76,1992005 "MRK",50.41,"6/11/2007","10:45am",+0.27,50.30,50.64,50.04,2985100 "MSFT",30.04,"6/11/2007","10:50am",-0.01,30.05,30.25,29.93,13852152 "PFE",26.37,"6/11/2007","10:46am",-0.15,26.50,26.53,26.34,4020496 "PG",63.02,"6/11/2007","10:45am",-0.05,62.80,63.12,62.75,1542146 "T",39.95,"6/11/2007","10:46am",-0.31,40.20,40.25,39.89,3119000 "UTX",69.72,"6/11/2007","10:45am",-0.51,69.85,70.20,69.51,611500 "VZ",43.18,"6/11/2007","10:45am",+0.11,42.95,43.20,42.89,1971710 "WMT",49.60,"6/11/2007","10:46am",-0.48,49.90,50.00,49.56,2280600 "XOM",82.72,"6/11/2007","10:46am",+0.04,82.68,83.11,82.35,2888400 "AA",39.65,"6/11/2007","10:50am",-0.01,39.67,40.18,39.43,1109080 "AIG",71.44,"6/11/2007","10:50am",-0.09,71.29,71.60,71.15,1102442 "AXP",62.83,"6/11/2007","10:50am",-0.21,62.79,63.07,62.42,1484920 "BA",98.11,"6/11/2007","10:51am",-0.08,98.25,98.79,97.74,653100 "C",53.01,"6/11/2007","10:51am",-0.32,53.20,53.25,52.81,2147094 "CAT",78.56,"6/11/2007","10:50am",+0.04,78.32,78.99,78.06,857252 "DD",50.78,"6/11/2007","10:50am",-0.35,51.13,51.21,50.62,931500 "DIS",34.24,"6/11/2007","10:51am",+0.04,34.28,34.44,34.12,1456950 "GE",37.29,"6/11/2007","10:51am",-0.03,37.07,37.41,37.05,6374978 "GM",31.17,"6/11/2007","10:50am",+0.17,31.00,31.62,30.90,4686579 "HD",37.745,"6/11/2007","10:51am",-0.205,37.78,37.83,37.62,2111769 "HON",57.14,"6/11/2007","10:51am",-0.24,57.25,57.40,57.00,672700 "HPQ",46.03,"6/11/2007","10:51am",+0.33,45.80,46.29,45.46,2822530 "IBM",103.39,"6/11/2007","10:51am",+0.32,102.87,103.63,102.50,1365400 "INTC",21.89,"6/11/2007","10:56am",+0.06,21.70,21.95,21.69,10794661 "JNJ",62.60,"6/11/2007","10:51am",+0.47,62.89,62.89,62.15,2051122 "JPM",50.25,"6/11/2007","10:50am",-0.16,50.41,50.55,50.05,1853000 "KO",51.46,"6/11/2007","10:51am",-0.21,51.67,51.82,51.32,4861660 "MCD",51.34,"6/11/2007","10:50am",-0.07,51.47,51.47,50.98,999019 "MMM",85.53,"6/11/2007","10:50am",-0.41,85.94,85.98,85.41,582200 "MO",70.0411,"6/11/2007","10:51am",-0.2589,70.25,70.30,69.76,2064105 "MRK",50.48,"6/11/2007","10:51am",+0.34,50.30,50.64,50.04,3053000 "MSFT",30.01,"6/11/2007","10:56am",-0.04,30.05,30.25,29.93,14553915 "PFE",26.385,"6/11/2007","10:51am",-0.135,26.50,26.53,26.34,4373096 "PG",63.04,"6/11/2007","10:51am",-0.03,62.80,63.12,62.75,1613346 "T",39.97,"6/11/2007","10:51am",-0.29,40.20,40.25,39.89,3263300 "UTX",69.79,"6/11/2007","10:51am",-0.44,69.85,70.20,69.51,629700 "VZ",43.19,"6/11/2007","10:50am",+0.12,42.95,43.20,42.89,2067610 "WMT",49.70,"6/11/2007","10:51am",-0.38,49.90,50.00,49.56,2349800 "XOM",82.73,"6/11/2007","10:51am",+0.05,82.68,83.11,82.35,2994800 "AA",39.67,"6/11/2007","10:56am",+0.01,39.67,40.18,39.43,1139080 "AIG",71.45,"6/11/2007","10:55am",-0.08,71.29,71.60,71.15,1141342 "AXP",62.94,"6/11/2007","10:56am",-0.10,62.79,63.07,62.42,1517420 "BA",98.18,"6/11/2007","10:56am",-0.01,98.25,98.79,97.74,695900 "C",53.02,"6/11/2007","10:56am",-0.31,53.20,53.25,52.81,2245494 "CAT",78.57,"6/11/2007","10:56am",+0.05,78.32,78.99,78.06,892252 "DD",50.8276,"6/11/2007","10:55am",-0.3024,51.13,51.21,50.62,991900 "DIS",34.23,"6/11/2007","10:56am",+0.03,34.28,34.44,34.12,1533350 "GE",37.34,"6/11/2007","10:56am",+0.02,37.07,37.41,37.05,6871678 "GM",31.20,"6/11/2007","10:56am",+0.20,31.00,31.62,30.90,4845779 "HD",37.74,"6/11/2007","10:56am",-0.21,37.78,37.83,37.62,2282669 "HON",57.17,"6/11/2007","10:56am",-0.21,57.25,57.40,57.00,700800 "HPQ",46.07,"6/11/2007","10:56am",+0.37,45.80,46.29,45.46,2941930 "IBM",103.48,"6/11/2007","10:56am",+0.41,102.87,103.63,102.50,1449900 "INTC",21.93,"6/11/2007","11:01am",+0.10,21.70,21.95,21.69,11463765 "JNJ",62.47,"6/11/2007","10:56am",+0.34,62.89,62.89,62.15,2184222 "JPM",50.27,"6/11/2007","10:56am",-0.14,50.41,50.55,50.05,1992300 "KO",51.45,"6/11/2007","10:56am",-0.22,51.67,51.82,51.32,4959242 "MCD",51.39,"6/11/2007","10:56am",-0.02,51.47,51.47,50.98,1065114 "MMM",85.58,"6/11/2007","10:56am",-0.36,85.94,85.98,85.41,603300 "MO",70.09,"6/11/2007","10:56am",-0.21,70.25,70.30,69.76,2152505 "MRK",50.45,"6/11/2007","10:56am",+0.31,50.30,50.64,50.04,3120700 "MSFT",29.985,"6/11/2007","11:01am",-0.065,30.05,30.25,29.93,15025412 "PFE",26.35,"6/11/2007","10:55am",-0.17,26.50,26.53,26.34,4707596 "PG",63.01,"6/11/2007","10:56am",-0.06,62.80,63.12,62.75,1804846 "T",40.01,"6/11/2007","10:56am",-0.25,40.20,40.25,39.89,3433900 "UTX",69.90,"6/11/2007","10:56am",-0.33,69.85,70.20,69.51,647800 "VZ",43.17,"6/11/2007","10:56am",+0.10,42.95,43.20,42.89,2246610 "WMT",49.61,"6/11/2007","10:56am",-0.47,49.90,50.00,49.56,2553200 "XOM",82.90,"6/11/2007","10:56am",+0.22,82.68,83.11,82.35,3157200 "AA",39.65,"6/11/2007","11:01am",-0.01,39.67,40.18,39.43,1162580 "AIG",71.46,"6/11/2007","11:01am",-0.07,71.29,71.60,71.15,1167442 "AXP",62.99,"6/11/2007","11:00am",-0.05,62.79,63.07,62.42,1532720 "BA",98.09,"6/11/2007","11:01am",-0.10,98.25,98.79,97.74,714300 "C",53.11,"6/11/2007","11:01am",-0.22,53.20,53.25,52.81,2344994 "CAT",78.52,"6/11/2007","11:00am",0.00,78.32,78.99,78.06,915752 "DD",50.75,"6/11/2007","11:01am",-0.38,51.13,51.21,50.62,1025100 "DIS",34.22,"6/11/2007","11:01am",+0.02,34.28,34.44,34.12,1567750 "GE",37.32,"6/11/2007","11:01am",0.00,37.07,37.41,37.05,7016378 "GM",31.22,"6/11/2007","11:01am",+0.22,31.00,31.62,30.90,4944879 "HD",37.73,"6/11/2007","11:01am",-0.22,37.78,37.83,37.62,2556369 "HON",57.10,"6/11/2007","11:01am",-0.28,57.25,57.40,57.00,794183 "HPQ",46.06,"6/11/2007","11:01am",+0.36,45.80,46.29,45.46,3023656 "IBM",103.37,"6/11/2007","11:01am",+0.30,102.87,103.63,102.50,1483400 "INTC",21.97,"6/11/2007","11:06am",+0.14,21.70,21.97,21.69,12553376 "JNJ",62.46,"6/11/2007","11:01am",+0.33,62.89,62.89,62.15,2309522 "JPM",50.32,"6/11/2007","11:01am",-0.09,50.41,50.55,50.05,2110500 "KO",51.42,"6/11/2007","11:01am",-0.25,51.67,51.82,51.32,5000442 "MCD",51.50,"6/11/2007","11:01am",+0.09,51.47,51.50,50.98,1190914 "MMM",85.62,"6/11/2007","11:00am",-0.32,85.94,85.98,85.41,620500 "MO",70.12,"6/11/2007","11:01am",-0.18,70.25,70.30,69.76,2246305 "MRK",50.505,"6/11/2007","11:01am",+0.365,50.30,50.64,50.04,3232600 "MSFT",30.02,"6/11/2007","11:06am",-0.03,30.05,30.25,29.93,15371602 "PFE",26.34,"6/11/2007","11:01am",-0.18,26.50,26.53,26.33,5134096 "PG",62.98,"6/11/2007","11:01am",-0.09,62.80,63.12,62.75,1854046 "T",40.02,"6/11/2007","11:01am",-0.24,40.20,40.25,39.89,3531300 "UTX",69.91,"6/11/2007","11:00am",-0.32,69.85,70.20,69.51,658800 "VZ",43.16,"6/11/2007","11:01am",+0.09,42.95,43.20,42.89,2288410 "WMT",49.59,"6/11/2007","11:01am",-0.49,49.90,50.00,49.56,2659033 "XOM",82.98,"6/11/2007","11:01am",+0.30,82.68,83.11,82.35,3296100 "AA",39.60,"6/11/2007","11:06am",-0.06,39.67,40.18,39.43,1189680 "AIG",71.49,"6/11/2007","11:06am",-0.04,71.29,71.60,71.15,1226342 "AXP",63.11,"6/11/2007","11:06am",+0.07,62.79,63.11,62.42,1576120 "BA",98.20,"6/11/2007","11:06am",+0.01,98.25,98.79,97.74,743800 "C",53.17,"6/11/2007","11:06am",-0.16,53.20,53.25,52.81,2445294 "CAT",78.70,"6/11/2007","11:06am",+0.18,78.32,78.99,78.06,950252 "DD",50.73,"6/11/2007","11:06am",-0.40,51.13,51.21,50.62,1165000 "DIS",34.24,"6/11/2007","11:06am",+0.04,34.28,34.44,34.12,1636250 "GE",37.3314,"6/11/2007","11:06am",+0.0114,37.07,37.41,37.05,7140328 "GM",31.19,"6/11/2007","11:06am",+0.19,31.00,31.62,30.90,5065479 "HD",37.72,"6/11/2007","11:06am",-0.23,37.78,37.83,37.62,2746169 "HON",57.04,"6/11/2007","11:06am",-0.34,57.25,57.40,57.00,851383 "HPQ",46.09,"6/11/2007","11:06am",+0.39,45.80,46.29,45.46,3113456 "IBM",103.35,"6/11/2007","11:05am",+0.28,102.87,103.63,102.50,1528300 "INTC",21.95,"6/11/2007","11:11am",+0.12,21.70,21.98,21.69,13630344 "JNJ",62.45,"6/11/2007","11:06am",+0.32,62.89,62.89,62.15,2409122 "JPM",50.38,"6/11/2007","11:06am",-0.03,50.41,50.55,50.05,2209200 "KO",51.44,"6/11/2007","11:05am",-0.23,51.67,51.82,51.32,5034642 "MCD",51.61,"6/11/2007","11:06am",+0.20,51.47,51.61,50.98,1307114 "MMM",85.66,"6/11/2007","11:06am",-0.28,85.94,85.98,85.41,642300 "MO",70.15,"6/11/2007","11:06am",-0.15,70.25,70.30,69.76,2298805 "MRK",50.52,"6/11/2007","11:06am",+0.38,50.30,50.64,50.04,3272300 "MSFT",30.06,"6/11/2007","11:11am",+0.01,30.05,30.25,29.93,15638561 "PFE",26.36,"6/11/2007","11:06am",-0.16,26.50,26.53,26.33,5407496 "PG",62.99,"6/11/2007","11:06am",-0.08,62.80,63.12,62.75,1906246 "T",40.01,"6/11/2007","11:06am",-0.25,40.20,40.25,39.89,3728000 "UTX",69.92,"6/11/2007","11:06am",-0.31,69.85,70.20,69.51,669300 "VZ",43.17,"6/11/2007","11:06am",+0.10,42.95,43.20,42.89,2322610 "WMT",49.55,"6/11/2007","11:06am",-0.53,49.90,50.00,49.55,2733633 "XOM",83.08,"6/11/2007","11:06am",+0.40,82.68,83.11,82.35,3448500 "AA",39.52,"6/11/2007","11:11am",-0.14,39.67,40.18,39.43,1258280 "AIG",71.50,"6/11/2007","11:11am",-0.03,71.29,71.60,71.15,1250442 "AXP",63.05,"6/11/2007","11:10am",+0.01,62.79,63.12,62.42,1588220 "BA",98.16,"6/11/2007","11:11am",-0.03,98.25,98.79,97.74,759800 "C",53.18,"6/11/2007","11:11am",-0.15,53.20,53.25,52.81,2614994 "CAT",78.82,"6/11/2007","11:11am",+0.30,78.32,78.99,78.06,992052 "DD",50.72,"6/11/2007","11:11am",-0.41,51.13,51.21,50.59,1241700 "DIS",34.22,"6/11/2007","11:11am",+0.02,34.28,34.44,34.12,1691750 "GE",37.36,"6/11/2007","11:11am",+0.04,37.07,37.41,37.05,7342028 "GM",31.14,"6/11/2007","11:10am",+0.14,31.00,31.62,30.90,5195679 "HD",37.74,"6/11/2007","11:11am",-0.21,37.78,37.83,37.62,2859969 "HON",57.08,"6/11/2007","11:10am",-0.30,57.25,57.40,57.00,888783 "HPQ",46.08,"6/11/2007","11:11am",+0.38,45.80,46.29,45.46,3164856 "IBM",103.34,"6/11/2007","11:11am",+0.27,102.87,103.63,102.50,1554100 "INTC",21.92,"6/11/2007","11:15am",+0.09,21.70,21.98,21.69,14546699 "JNJ",62.49,"6/11/2007","11:11am",+0.36,62.89,62.89,62.15,2467522 "JPM",50.39,"6/11/2007","11:11am",-0.02,50.41,50.55,50.05,2298000 "KO",51.46,"6/11/2007","11:11am",-0.21,51.67,51.82,51.32,5073542 "MCD",51.60,"6/11/2007","11:11am",+0.19,51.47,51.62,50.98,1438514 "MMM",85.70,"6/11/2007","11:11am",-0.24,85.94,85.98,85.41,673300 "MO",70.2306,"6/11/2007","11:11am",-0.0694,70.25,70.30,69.76,2382105 "MRK",50.46,"6/11/2007","11:11am",+0.32,50.30,50.64,50.04,3337000 "MSFT",30.04,"6/11/2007","11:16am",-0.01,30.05,30.25,29.93,15908978 "PFE",26.32,"6/11/2007","11:11am",-0.20,26.50,26.53,26.31,5832646 "PG",62.99,"6/11/2007","11:11am",-0.08,62.80,63.12,62.75,1960846 "T",40.001,"6/11/2007","11:11am",-0.259,40.20,40.25,39.89,3794300 "UTX",69.85,"6/11/2007","11:10am",-0.38,69.85,70.20,69.51,678300 "VZ",43.16,"6/11/2007","11:10am",+0.09,42.95,43.20,42.89,2358345 "WMT",49.64,"6/11/2007","11:11am",-0.44,49.90,50.00,49.55,2915433 "XOM",83.09,"6/11/2007","11:11am",+0.41,82.68,83.11,82.35,3609300 "AA",39.48,"6/11/2007","11:16am",-0.18,39.67,40.18,39.43,1290380 "AIG",71.42,"6/11/2007","11:16am",-0.11,71.29,71.60,71.15,1276442 "AXP",63.01,"6/11/2007","11:16am",-0.03,62.79,63.12,62.42,1616220 "BA",98.01,"6/11/2007","11:16am",-0.18,98.25,98.79,97.74,777300 "C",53.23,"6/11/2007","11:16am",-0.10,53.20,53.25,52.81,2793394 "CAT",78.65,"6/11/2007","11:16am",+0.13,78.32,78.99,78.06,1046952 "DD",50.70,"6/11/2007","11:16am",-0.43,51.13,51.21,50.59,1337600 "DIS",34.20,"6/11/2007","11:15am",0.00,34.28,34.44,34.12,1805250 "GE",37.35,"6/11/2007","11:16am",+0.03,37.07,37.41,37.05,7601178 "GM",31.10,"6/11/2007","11:15am",+0.10,31.00,31.62,30.90,5260279 "HD",37.73,"6/11/2007","11:16am",-0.22,37.78,37.83,37.62,2927469 "HON",56.965,"6/11/2007","11:16am",-0.415,57.25,57.40,56.93,940183 "HPQ",46.04,"6/11/2007","11:16am",+0.34,45.80,46.29,45.46,3255956 "IBM",103.24,"6/11/2007","11:16am",+0.17,102.87,103.63,102.50,1595900 "INTC",21.92,"6/11/2007","11:21am",+0.09,21.70,21.98,21.69,14851182 "JNJ",62.55,"6/11/2007","11:16am",+0.42,62.89,62.89,62.15,2628222 "JPM",50.32,"6/11/2007","11:16am",-0.09,50.41,50.55,50.05,2372200 "KO",51.46,"6/11/2007","11:16am",-0.21,51.67,51.82,51.32,5091242 "MCD",51.49,"6/11/2007","11:15am",+0.08,51.47,51.62,50.98,1719114 "MMM",85.57,"6/11/2007","11:15am",-0.37,85.94,85.98,85.41,691600 "MO",70.17,"6/11/2007","11:16am",-0.13,70.25,70.30,69.76,2466057 "MRK",50.52,"6/11/2007","11:16am",+0.38,50.30,50.64,50.04,3543600 "MSFT",30.025,"6/11/2007","11:20am",-0.025,30.05,30.25,29.93,16110835 "PFE",26.33,"6/11/2007","11:16am",-0.19,26.50,26.53,26.31,6034446 "PG",62.96,"6/11/2007","11:16am",-0.11,62.80,63.12,62.75,2028646 "T",39.97,"6/11/2007","11:16am",-0.29,40.20,40.25,39.89,3937400 "UTX",69.81,"6/11/2007","11:15am",-0.42,69.85,70.20,69.51,697600 "VZ",43.15,"6/11/2007","11:16am",+0.08,42.95,43.20,42.89,2414845 "WMT",49.63,"6/11/2007","11:16am",-0.45,49.90,50.00,49.55,3029233 "XOM",82.96,"6/11/2007","11:16am",+0.28,82.68,83.11,82.35,3750000 "AA",39.46,"6/11/2007","11:20am",-0.20,39.67,40.18,39.43,1318080 "AIG",71.37,"6/11/2007","11:20am",-0.16,71.29,71.60,71.15,1313142 "AXP",63.09,"6/11/2007","11:21am",+0.05,62.79,63.12,62.42,1639320 "BA",98.06,"6/11/2007","11:21am",-0.13,98.25,98.79,97.74,796300 "C",53.24,"6/11/2007","11:21am",-0.09,53.20,53.26,52.81,2937494 "CAT",78.72,"6/11/2007","11:21am",+0.20,78.32,78.99,78.06,1095252 "DD",50.67,"6/11/2007","11:21am",-0.46,51.13,51.21,50.59,1421500 "DIS",34.21,"6/11/2007","11:21am",+0.01,34.28,34.44,34.12,1882850 "GE",37.41,"6/11/2007","11:21am",+0.09,37.07,37.43,37.05,8394078 "GM",31.06,"6/11/2007","11:21am",+0.06,31.00,31.62,30.90,5321979 "HD",37.72,"6/11/2007","11:21am",-0.23,37.78,37.83,37.62,2989469 "HON",56.98,"6/11/2007","11:21am",-0.40,57.25,57.40,56.91,1018183 "HPQ",46.01,"6/11/2007","11:21am",+0.31,45.80,46.29,45.46,3347656 "IBM",103.27,"6/11/2007","11:21am",+0.20,102.87,103.63,102.50,1635600 "INTC",21.93,"6/11/2007","11:26am",+0.10,21.70,21.98,21.69,15018868 "JNJ",62.58,"6/11/2007","11:21am",+0.45,62.89,62.89,62.15,2706422 "JPM",50.30,"6/11/2007","11:21am",-0.11,50.41,50.55,50.05,2419700 "KO",51.45,"6/11/2007","11:20am",-0.22,51.67,51.82,51.32,5160942 "MCD",51.46,"6/11/2007","11:20am",+0.05,51.47,51.62,50.98,1812914 "MMM",85.47,"6/11/2007","11:21am",-0.47,85.94,85.98,85.41,710100 "MO",70.20,"6/11/2007","11:21am",-0.10,70.25,70.30,69.76,2517457 "MRK",50.48,"6/11/2007","11:21am",+0.34,50.30,50.64,50.04,3625200 "MSFT",30.07,"6/11/2007","11:26am",+0.02,30.05,30.25,29.93,16343142 "PFE",26.349,"6/11/2007","11:21am",-0.171,26.50,26.53,26.31,6275846 "PG",62.98,"6/11/2007","11:21am",-0.09,62.80,63.12,62.75,2075846 "T",39.99,"6/11/2007","11:21am",-0.27,40.20,40.25,39.89,4085900 "UTX",69.76,"6/11/2007","11:21am",-0.47,69.85,70.20,69.51,714000 "VZ",43.16,"6/11/2007","11:20am",+0.09,42.95,43.20,42.89,2522445 "WMT",49.63,"6/11/2007","11:21am",-0.45,49.90,50.00,49.55,3096833 "XOM",82.91,"6/11/2007","11:21am",+0.23,82.68,83.11,82.35,3880100 "AA",39.50,"6/11/2007","11:26am",-0.16,39.67,40.18,39.43,1352980 "AIG",71.37,"6/11/2007","11:26am",-0.16,71.29,71.60,71.15,1345042 "AXP",63.03,"6/11/2007","11:26am",-0.01,62.79,63.12,62.42,1654030 "BA",98.06,"6/11/2007","11:26am",-0.13,98.25,98.79,97.74,817500 "C",53.24,"6/11/2007","11:26am",-0.09,53.20,53.26,52.81,3004394 "CAT",78.75,"6/11/2007","11:26am",+0.23,78.32,78.99,78.06,1112652 "DD",50.80,"6/11/2007","11:26am",-0.33,51.13,51.21,50.59,1443900 "DIS",34.22,"6/11/2007","11:25am",+0.02,34.28,34.44,34.12,1977950 "GE",37.401,"6/11/2007","11:26am",+0.081,37.07,37.43,37.05,8549378 "GM",31.18,"6/11/2007","11:26am",+0.18,31.00,31.62,30.90,5486579 "HD",37.74,"6/11/2007","11:26am",-0.21,37.78,37.83,37.62,3055769 "HON",57.03,"6/11/2007","11:26am",-0.35,57.25,57.40,56.91,1098842 "HPQ",46.00,"6/11/2007","11:26am",+0.30,45.80,46.29,45.46,3418156 "IBM",103.33,"6/11/2007","11:26am",+0.26,102.87,103.63,102.50,1657200 "INTC",21.94,"6/11/2007","11:31am",+0.11,21.70,21.98,21.69,15378550 "JNJ",62.59,"6/11/2007","11:26am",+0.46,62.89,62.89,62.15,2772722 "JPM",50.38,"6/11/2007","11:26am",-0.03,50.41,50.55,50.05,2481600 "KO",51.45,"6/11/2007","11:26am",-0.22,51.67,51.82,51.32,5180942 "MCD",51.49,"6/11/2007","11:26am",+0.08,51.47,51.62,50.98,1867514 "MMM",85.45,"6/11/2007","11:25am",-0.49,85.94,85.98,85.39,738400 "MO",70.21,"6/11/2007","11:26am",-0.09,70.25,70.30,69.76,2582357 "MRK",50.55,"6/11/2007","11:26am",+0.41,50.30,50.64,50.04,3773900 "MSFT",30.05,"6/11/2007","11:31am",0.00,30.05,30.25,29.93,16793888 "PFE",26.34,"6/11/2007","11:26am",-0.18,26.50,26.53,26.31,6551746 "PG",62.99,"6/11/2007","11:26am",-0.08,62.80,63.12,62.75,2114846 "T",40.06,"6/11/2007","11:26am",-0.20,40.20,40.25,39.89,4212600 "UTX",69.73,"6/11/2007","11:25am",-0.50,69.85,70.20,69.51,725600 "VZ",43.16,"6/11/2007","11:26am",+0.09,42.95,43.20,42.89,2595445 "WMT",49.64,"6/11/2007","11:25am",-0.44,49.90,50.00,49.55,3167033 "XOM",82.89,"6/11/2007","11:26am",+0.21,82.68,83.11,82.35,4029000 "AA",39.47,"6/11/2007","11:31am",-0.19,39.67,40.18,39.43,1409980 "AIG",71.48,"6/11/2007","11:31am",-0.05,71.29,71.60,71.15,1424042 "AXP",63.07,"6/11/2007","11:30am",+0.03,62.79,63.14,62.42,1695430 "BA",98.00,"6/11/2007","11:31am",-0.19,98.25,98.79,97.74,865400 "C",53.31,"6/11/2007","11:31am",-0.02,53.20,53.31,52.81,3119094 "CAT",78.72,"6/11/2007","11:31am",+0.20,78.32,78.99,78.06,1140252 "DD",50.75,"6/11/2007","11:31am",-0.38,51.13,51.21,50.59,1474900 "DIS",34.20,"6/11/2007","11:30am",0.00,34.28,34.44,34.12,2013250 "GE",37.41,"6/11/2007","11:31am",+0.09,37.07,37.44,37.05,8715001 "GM",31.23,"6/11/2007","11:31am",+0.23,31.00,31.62,30.90,5548179 "HD",37.72,"6/11/2007","11:30am",-0.23,37.78,37.83,37.62,3183869 "HON",57.01,"6/11/2007","11:31am",-0.37,57.25,57.40,56.91,1290742 "HPQ",45.99,"6/11/2007","11:31am",+0.29,45.80,46.29,45.46,3514456 "IBM",103.31,"6/11/2007","11:31am",+0.24,102.87,103.63,102.50,1841300 "INTC",21.94,"6/11/2007","11:36am",+0.11,21.70,21.98,21.69,15795668 "JNJ",62.59,"6/11/2007","11:31am",+0.46,62.89,62.89,62.15,2811022 "JPM",50.41,"6/11/2007","11:30am",0.00,50.41,50.55,50.05,2553800 "KO",51.45,"6/11/2007","11:30am",-0.22,51.67,51.82,51.32,5207142 "MCD",51.47,"6/11/2007","11:30am",+0.06,51.47,51.62,50.98,1913314 "MMM",85.41,"6/11/2007","11:31am",-0.53,85.94,85.98,85.39,805100 "MO",70.36,"6/11/2007","11:31am",+0.06,70.25,70.36,69.76,2672857 "MRK",50.55,"6/11/2007","11:31am",+0.41,50.30,50.64,50.04,3841500 "MSFT",30.09,"6/11/2007","11:36am",+0.04,30.05,30.25,29.93,17107810 "PFE",26.35,"6/11/2007","11:31am",-0.17,26.50,26.53,26.31,6720346 "PG",62.98,"6/11/2007","11:31am",-0.09,62.80,63.12,62.75,2155746 "T",40.07,"6/11/2007","11:31am",-0.19,40.20,40.25,39.89,4436800 "UTX",69.74,"6/11/2007","11:31am",-0.49,69.85,70.20,69.51,757000 "VZ",43.18,"6/11/2007","11:31am",+0.11,42.95,43.22,42.89,2918545 "WMT",49.66,"6/11/2007","11:31am",-0.42,49.90,50.00,49.55,3258333 "XOM",82.92,"6/11/2007","11:31am",+0.24,82.68,83.11,82.35,4163200 "AA",39.50,"6/11/2007","11:36am",-0.16,39.67,40.18,39.43,1444680 "AIG",71.48,"6/11/2007","11:35am",-0.05,71.29,71.60,71.15,1468642 "AXP",63.0703,"6/11/2007","11:35am",+0.0303,62.79,63.14,62.42,1717830 "BA",98.0028,"6/11/2007","11:36am",-0.1872,98.25,98.79,97.74,951000 "C",53.34,"6/11/2007","11:36am",+0.01,53.20,53.37,52.81,3207894 "CAT",78.66,"6/11/2007","11:36am",+0.14,78.32,78.99,78.06,1150952 "DD",50.73,"6/11/2007","11:36am",-0.40,51.13,51.21,50.59,1500000 "DIS",34.19,"6/11/2007","11:36am",-0.01,34.28,34.44,34.12,2167150 "GE",37.38,"6/11/2007","11:36am",+0.06,37.07,37.44,37.05,9041801 "GM",31.25,"6/11/2007","11:35am",+0.25,31.00,31.62,30.90,5608979 "HD",37.75,"6/11/2007","11:36am",-0.20,37.78,37.83,37.62,3269369 "HON",56.98,"6/11/2007","11:36am",-0.40,57.25,57.40,56.91,1330742 "HPQ",45.94,"6/11/2007","11:36am",+0.24,45.80,46.29,45.46,3621556 "IBM",103.28,"6/11/2007","11:36am",+0.21,102.87,103.63,102.50,1859400 "INTC",21.97,"6/11/2007","11:41am",+0.14,21.70,21.98,21.69,16127869 "JNJ",62.56,"6/11/2007","11:35am",+0.43,62.89,62.89,62.15,2852322 "JPM",50.43,"6/11/2007","11:36am",+0.02,50.41,50.55,50.05,2656300 "KO",51.40,"6/11/2007","11:36am",-0.27,51.67,51.82,51.32,5232342 "MCD",51.47,"6/11/2007","11:35am",+0.06,51.47,51.62,50.98,1953314 "MMM",85.43,"6/11/2007","11:35am",-0.51,85.94,85.98,85.39,828900 "MO",70.39,"6/11/2007","11:35am",+0.09,70.25,70.48,69.76,2823257 "MRK",50.551,"6/11/2007","11:36am",+0.411,50.30,50.64,50.04,3911800 "MSFT",30.11,"6/11/2007","11:41am",+0.06,30.05,30.25,29.93,17353272 "PFE",26.38,"6/11/2007","11:36am",-0.14,26.50,26.53,26.31,6992796 "PG",62.93,"6/11/2007","11:35am",-0.14,62.80,63.12,62.75,2196846 "T",40.03,"6/11/2007","11:35am",-0.23,40.20,40.25,39.89,4610000 "UTX",69.77,"6/11/2007","11:35am",-0.46,69.85,70.20,69.51,788900 "VZ",43.18,"6/11/2007","11:35am",+0.11,42.95,43.22,42.89,2997845 "WMT",49.74,"6/11/2007","11:36am",-0.34,49.90,50.00,49.55,3415433 "XOM",83.04,"6/11/2007","11:36am",+0.36,82.68,83.11,82.35,4297200 "AA",39.53,"6/11/2007","11:41am",-0.13,39.67,40.18,39.43,1481180 "AIG",71.53,"6/11/2007","11:41am",0.00,71.29,71.60,71.15,1498042 "AXP",63.07,"6/11/2007","11:41am",+0.03,62.79,63.14,62.42,1760630 "BA",98.06,"6/11/2007","11:41am",-0.13,98.25,98.79,97.74,968400 "C",53.41,"6/11/2007","11:41am",+0.08,53.20,53.41,52.81,3275394 "CAT",78.72,"6/11/2007","11:41am",+0.20,78.32,78.99,78.06,1165852 "DD",50.77,"6/11/2007","11:41am",-0.36,51.13,51.21,50.59,1523000 "DIS",34.19,"6/11/2007","11:41am",-0.01,34.28,34.44,34.12,2223450 "GE",37.42,"6/11/2007","11:41am",+0.10,37.07,37.44,37.05,9239801 "GM",31.30,"6/11/2007","11:41am",+0.30,31.00,31.62,30.90,5699079 "HD",37.75,"6/11/2007","11:41am",-0.20,37.78,37.83,37.62,3307869 "HON",56.99,"6/11/2007","11:41am",-0.39,57.25,57.40,56.91,1376642 "HPQ",45.96,"6/11/2007","11:41am",+0.26,45.80,46.29,45.46,3706756 "IBM",103.44,"6/11/2007","11:41am",+0.37,102.87,103.63,102.50,1880700 "INTC",21.97,"6/11/2007","11:45am",+0.14,21.70,21.98,21.69,16406027 "JNJ",62.64,"6/11/2007","11:41am",+0.51,62.89,62.89,62.15,2899022 "JPM",50.50,"6/11/2007","11:41am",+0.09,50.41,50.55,50.05,2715800 "KO",51.45,"6/11/2007","11:41am",-0.22,51.67,51.82,51.32,5260042 "MCD",51.49,"6/11/2007","11:40am",+0.08,51.47,51.62,50.98,2029314 "MMM",85.52,"6/11/2007","11:41am",-0.42,85.94,85.98,85.39,843500 "MO",70.47,"6/11/2007","11:41am",+0.17,70.25,70.50,69.76,3705557 "MRK",50.65,"6/11/2007","11:41am",+0.51,50.30,50.65,50.04,4091400 "MSFT",30.135,"6/11/2007","11:46am",+0.085,30.05,30.25,29.93,17741848 "PFE",26.42,"6/11/2007","11:41am",-0.10,26.50,26.53,26.31,7347396 "PG",63.00,"6/11/2007","11:41am",-0.07,62.80,63.12,62.75,2256146 "T",40.09,"6/11/2007","11:41am",-0.17,40.20,40.25,39.89,4805400 "UTX",69.82,"6/11/2007","11:40am",-0.41,69.85,70.20,69.51,796500 "VZ",43.23,"6/11/2007","11:40am",+0.16,42.95,43.23,42.89,3077145 "WMT",49.81,"6/11/2007","11:41am",-0.27,49.90,50.00,49.55,3532333 "XOM",83.18,"6/11/2007","11:41am",+0.50,82.68,83.18,82.35,4455700 "AA",39.54,"6/11/2007","11:46am",-0.12,39.67,40.18,39.43,1499880 "AIG",71.59,"6/11/2007","11:45am",+0.06,71.29,71.61,71.15,1522942 "AXP",63.09,"6/11/2007","11:46am",+0.05,62.79,63.14,62.42,1799530 "BA",98.01,"6/11/2007","11:46am",-0.18,98.25,98.79,97.74,1016800 "C",53.48,"6/11/2007","11:46am",+0.15,53.20,53.52,52.81,3465694 "CAT",78.7346,"6/11/2007","11:45am",+0.2146,78.32,78.99,78.06,1189152 "DD",50.72,"6/11/2007","11:46am",-0.41,51.13,51.21,50.59,1542800 "DIS",34.202,"6/11/2007","11:46am",+0.002,34.28,34.44,34.12,2263250 "GE",37.43,"6/11/2007","11:46am",+0.11,37.07,37.44,37.05,9402901 "GM",31.30,"6/11/2007","11:46am",+0.30,31.00,31.62,30.90,5791779 "HD",37.74,"6/11/2007","11:46am",-0.21,37.78,37.83,37.62,3438769 "HON",57.01,"6/11/2007","11:45am",-0.37,57.25,57.40,56.91,1411942 "HPQ",45.99,"6/11/2007","11:46am",+0.29,45.80,46.29,45.46,3780456 "IBM",103.53,"6/11/2007","11:46am",+0.46,102.87,103.63,102.50,1931500 "INTC",21.98,"6/11/2007","11:51am",+0.15,21.70,21.98,21.69,16615722 "JNJ",62.64,"6/11/2007","11:45am",+0.51,62.89,62.89,62.15,2931522 "JPM",50.52,"6/11/2007","11:46am",+0.11,50.41,50.55,50.05,2820200 "KO",51.48,"6/11/2007","11:45am",-0.19,51.67,51.82,51.32,5289842 "MCD",51.54,"6/11/2007","11:45am",+0.13,51.47,51.62,50.98,2063014 "MMM",85.53,"6/11/2007","11:46am",-0.41,85.94,85.98,85.39,860900 "MO",70.30,"6/11/2007","11:46am",0.00,70.25,70.50,69.76,3796557 "MRK",50.73,"6/11/2007","11:46am",+0.59,50.30,50.74,50.04,4201600 "MSFT",30.1603,"6/11/2007","11:50am",+0.1103,30.05,30.25,29.93,18232994 "PFE",26.43,"6/11/2007","11:46am",-0.09,26.50,26.53,26.31,7612046 "PG",62.95,"6/11/2007","11:45am",-0.12,62.80,63.12,62.75,2315446 "T",40.14,"6/11/2007","11:46am",-0.12,40.20,40.25,39.89,4928500 "UTX",69.83,"6/11/2007","11:46am",-0.40,69.85,70.20,69.51,808300 "VZ",43.25,"6/11/2007","11:45am",+0.18,42.95,43.26,42.89,3194645 "WMT",49.81,"6/11/2007","11:46am",-0.27,49.90,50.00,49.55,3691733 "XOM",83.20,"6/11/2007","11:46am",+0.52,82.68,83.25,82.35,4596200 "AA",39.59,"6/11/2007","11:51am",-0.07,39.67,40.18,39.43,1549580 "AIG",71.62,"6/11/2007","11:51am",+0.09,71.29,71.68,71.15,1587442 "AXP",63.09,"6/11/2007","11:51am",+0.05,62.79,63.19,62.42,1825430 "BA",97.98,"6/11/2007","11:50am",-0.21,98.25,98.79,97.74,1036600 "C",53.52,"6/11/2007","11:50am",+0.19,53.20,53.57,52.81,3665594 "CAT",78.78,"6/11/2007","11:51am",+0.26,78.32,78.99,78.06,1234052 "DD",50.74,"6/11/2007","11:51am",-0.39,51.13,51.21,50.59,1579200 "DIS",34.23,"6/11/2007","11:51am",+0.03,34.28,34.44,34.12,2335050 "GE",37.469,"6/11/2007","11:51am",+0.149,37.07,37.48,37.05,9637101 "GM",31.31,"6/11/2007","11:50am",+0.31,31.00,31.62,30.90,5900779 "HD",37.77,"6/11/2007","11:51am",-0.18,37.78,37.83,37.62,3574469 "HON",56.99,"6/11/2007","11:50am",-0.39,57.25,57.40,56.91,1469142 "HPQ",46.0616,"6/11/2007","11:50am",+0.3616,45.80,46.29,45.46,3886556 "IBM",103.70,"6/11/2007","11:51am",+0.63,102.87,103.71,102.50,1993700 "INTC",22.01,"6/11/2007","11:56am",+0.18,21.70,22.01,21.69,17848980 "JNJ",62.719,"6/11/2007","11:51am",+0.589,62.89,62.89,62.15,3194722 "JPM",50.53,"6/11/2007","11:51am",+0.12,50.41,50.55,50.05,2963700 "KO",51.50,"6/11/2007","11:50am",-0.17,51.67,51.82,51.32,5313142 "MCD",51.52,"6/11/2007","11:51am",+0.11,51.47,51.62,50.98,2105814 "MMM",85.53,"6/11/2007","11:50am",-0.41,85.94,85.98,85.39,879300 "MO",70.43,"6/11/2007","11:50am",+0.13,70.25,70.50,69.76,3873057 "MRK",50.78,"6/11/2007","11:51am",+0.64,50.30,50.87,50.04,4316300 "MSFT",30.18,"6/11/2007","11:56am",+0.13,30.05,30.25,29.93,18458900 "PFE",26.50,"6/11/2007","11:51am",-0.02,26.50,26.53,26.31,7882446 "PG",63.04,"6/11/2007","11:50am",-0.03,62.80,63.12,62.75,2384246 "T",40.15,"6/11/2007","11:51am",-0.11,40.20,40.25,39.89,5143600 "UTX",69.85,"6/11/2007","11:50am",-0.38,69.85,70.20,69.51,824600 "VZ",43.29,"6/11/2007","11:51am",+0.22,42.95,43.30,42.89,3253445 "WMT",49.84,"6/11/2007","11:51am",-0.24,49.90,50.00,49.55,3812233 "XOM",83.26,"6/11/2007","11:51am",+0.58,82.68,83.35,82.35,4722700 "AA",39.62,"6/11/2007","11:56am",-0.04,39.67,40.18,39.43,1573080 "AIG",71.6254,"6/11/2007","11:56am",+0.0954,71.29,71.68,71.15,1630242 "AXP",63.14,"6/11/2007","11:56am",+0.10,62.79,63.19,62.42,1839830 "BA",97.97,"6/11/2007","11:55am",-0.22,98.25,98.79,97.74,1056000 "C",53.58,"6/11/2007","11:56am",+0.25,53.20,53.58,52.81,3892094 "CAT",78.76,"6/11/2007","11:55am",+0.24,78.32,78.99,78.06,1252852 "DD",50.73,"6/11/2007","11:56am",-0.40,51.13,51.21,50.59,1632600 "DIS",34.24,"6/11/2007","11:56am",+0.04,34.28,34.44,34.12,2414850 "GE",37.44,"6/11/2007","11:56am",+0.12,37.07,37.49,37.05,9766201 "GM",31.32,"6/11/2007","11:56am",+0.32,31.00,31.62,30.90,5949301 "HD",37.74,"6/11/2007","11:56am",-0.21,37.78,37.83,37.62,3692669 "HON",57.00,"6/11/2007","11:56am",-0.38,57.25,57.40,56.91,1521042 "HPQ",46.10,"6/11/2007","11:56am",+0.40,45.80,46.29,45.46,4001456 "IBM",103.62,"6/11/2007","11:56am",+0.55,102.87,103.71,102.50,2026100 "INTC",22.00,"6/11/2007","12:01pm",+0.17,21.70,22.01,21.69,18388938 "JNJ",62.66,"6/11/2007","11:55am",+0.53,62.89,62.89,62.15,3236322 "JPM",50.54,"6/11/2007","11:56am",+0.13,50.41,50.56,50.05,3019600 "KO",51.52,"6/11/2007","11:55am",-0.15,51.67,51.82,51.32,5345042 "MCD",51.53,"6/11/2007","11:56am",+0.12,51.47,51.62,50.98,2134514 "MMM",85.44,"6/11/2007","11:56am",-0.50,85.94,85.98,85.39,905400 "MO",70.42,"6/11/2007","11:56am",+0.12,70.25,70.50,69.76,3926257 "MRK",50.79,"6/11/2007","11:56am",+0.65,50.30,50.87,50.04,4401700 "MSFT",30.18,"6/11/2007","12:01pm",+0.13,30.05,30.25,29.93,18749244 "PFE",26.46,"6/11/2007","11:56am",-0.06,26.50,26.53,26.31,8528842 "PG",63.04,"6/11/2007","11:56am",-0.03,62.80,63.12,62.75,2418246 "T",40.16,"6/11/2007","11:56am",-0.10,40.20,40.25,39.89,5294500 "UTX",69.8227,"6/11/2007","11:56am",-0.4073,69.85,70.20,69.51,840000 "VZ",43.28,"6/11/2007","11:56am",+0.21,42.95,43.31,42.89,3317345 "WMT",49.87,"6/11/2007","11:56am",-0.21,49.90,50.00,49.55,3928833 "XOM",83.30,"6/11/2007","11:56am",+0.62,82.68,83.39,82.35,4932400 "AA",39.615,"6/11/2007","12:00pm",-0.045,39.67,40.18,39.43,1587980 "AIG",71.63,"6/11/2007","12:01pm",+0.10,71.29,71.68,71.15,1669942 "AXP",63.142,"6/11/2007","12:01pm",+0.102,62.79,63.19,62.42,1852230 "BA",98.00,"6/11/2007","12:01pm",-0.19,98.25,98.79,97.74,1079000 "C",53.53,"6/11/2007","12:01pm",+0.20,53.20,53.58,52.81,3992194 "CAT",78.76,"6/11/2007","12:01pm",+0.24,78.32,78.99,78.06,1270152 "DD",50.69,"6/11/2007","12:01pm",-0.44,51.13,51.21,50.59,1671400 "DIS",34.21,"6/11/2007","12:00pm",+0.01,34.28,34.44,34.12,2450550 "GE",37.425,"6/11/2007","12:01pm",+0.105,37.07,37.49,37.05,9895501 "GM",31.32,"6/11/2007","12:00pm",+0.32,31.00,31.62,30.90,5989901 "HD",37.75,"6/11/2007","12:01pm",-0.20,37.78,37.83,37.62,3816469 "HON",57.00,"6/11/2007","12:01pm",-0.38,57.25,57.40,56.91,1543942 "HPQ",46.12,"6/11/2007","12:01pm",+0.42,45.80,46.29,45.46,4076156 "IBM",103.63,"6/11/2007","12:00pm",+0.56,102.87,103.71,102.50,2060500 "INTC",21.99,"6/11/2007","12:06pm",+0.16,21.70,22.02,21.69,18564224 "JNJ",62.631,"6/11/2007","12:01pm",+0.501,62.89,62.89,62.15,3282922 "JPM",50.60,"6/11/2007","12:01pm",+0.19,50.41,50.60,50.05,3118700 "KO",51.53,"6/11/2007","12:00pm",-0.14,51.67,51.82,51.32,5393742 "MCD",51.50,"6/11/2007","12:01pm",+0.09,51.47,51.62,50.98,2171014 "MMM",85.43,"6/11/2007","12:00pm",-0.51,85.94,85.98,85.39,922700 "MO",70.40,"6/11/2007","12:01pm",+0.10,70.25,70.50,69.76,3957957 "MRK",50.7475,"6/11/2007","12:01pm",+0.6075,50.30,50.87,50.04,4475600 "MSFT",30.1597,"6/11/2007","12:06pm",+0.1097,30.05,30.25,29.93,18960388 "PFE",26.46,"6/11/2007","12:01pm",-0.06,26.50,26.53,26.31,8624142 "PG",63.03,"6/11/2007","12:01pm",-0.04,62.80,63.12,62.75,2475246 "T",40.20,"6/11/2007","12:01pm",-0.06,40.20,40.25,39.89,5445500 "UTX",69.83,"6/11/2007","12:00pm",-0.40,69.85,70.20,69.51,860200 "VZ",43.28,"6/11/2007","12:00pm",+0.21,42.95,43.31,42.89,3361245 "WMT",49.87,"6/11/2007","12:01pm",-0.21,49.90,50.00,49.55,4143733 "XOM",83.40,"6/11/2007","12:01pm",+0.72,82.68,83.46,82.35,5119800 "AA",39.61,"6/11/2007","12:06pm",-0.05,39.67,40.18,39.43,1627080 "AIG",71.68,"6/11/2007","12:06pm",+0.15,71.29,71.68,71.15,1706542 "AXP",63.0546,"6/11/2007","12:05pm",+0.0146,62.79,63.19,62.42,1880930 "BA",97.92,"6/11/2007","12:06pm",-0.27,98.25,98.79,97.74,1090100 "C",53.55,"6/11/2007","12:06pm",+0.22,53.20,53.65,52.81,4319594 "CAT",78.74,"6/11/2007","12:06pm",+0.22,78.32,78.99,78.06,1288152 "DD",50.70,"6/11/2007","12:06pm",-0.43,51.13,51.21,50.59,1732500 "DIS",34.22,"6/11/2007","12:06pm",+0.02,34.28,34.44,34.12,2493950 "GE",37.4227,"6/11/2007","12:06pm",+0.1027,37.07,37.49,37.05,10023701 "GM",31.32,"6/11/2007","12:06pm",+0.32,31.00,31.62,30.90,6029201 "HD",37.7401,"6/11/2007","12:06pm",-0.2099,37.78,37.83,37.62,3862469 "HON",57.00,"6/11/2007","12:06pm",-0.38,57.25,57.40,56.91,1627642 "HPQ",46.10,"6/11/2007","12:06pm",+0.40,45.80,46.29,45.46,4113656 "IBM",103.67,"6/11/2007","12:06pm",+0.60,102.87,103.71,102.50,2085800 "INTC",21.99,"6/11/2007","12:11pm",+0.16,21.70,22.02,21.69,18898568 "JNJ",62.62,"6/11/2007","12:06pm",+0.49,62.89,62.89,62.15,3318522 "JPM",50.60,"6/11/2007","12:06pm",+0.19,50.41,50.62,50.05,3165700 "KO",51.52,"6/11/2007","12:05pm",-0.15,51.67,51.82,51.32,5416242 "MCD",51.47,"6/11/2007","12:06pm",+0.06,51.47,51.62,50.98,2192514 "MMM",85.41,"6/11/2007","12:05pm",-0.53,85.94,85.98,85.39,941800 "MO",70.41,"6/11/2007","12:06pm",+0.11,70.25,70.50,69.76,3992557 "MRK",50.75,"6/11/2007","12:06pm",+0.61,50.30,50.87,50.04,4518500 "MSFT",30.13,"6/11/2007","12:11pm",+0.08,30.05,30.25,29.93,19296222 "PFE",26.43,"6/11/2007","12:06pm",-0.09,26.50,26.53,26.31,8765142 "PG",63.02,"6/11/2007","12:06pm",-0.05,62.80,63.12,62.75,2498846 "T",40.18,"6/11/2007","12:06pm",-0.08,40.20,40.25,39.89,5544800 "UTX",69.89,"6/11/2007","12:05pm",-0.34,69.85,70.20,69.51,873700 "VZ",43.32,"6/11/2007","12:06pm",+0.25,42.95,43.34,42.89,3454720 "WMT",49.84,"6/11/2007","12:06pm",-0.24,49.90,50.00,49.55,4217433 "XOM",83.40,"6/11/2007","12:06pm",+0.72,82.68,83.48,82.35,5317100 "AA",39.57,"6/11/2007","12:11pm",-0.09,39.67,40.18,39.43,1657480 "AIG",71.68,"6/11/2007","12:11pm",+0.15,71.29,71.70,71.15,1754142 "AXP",63.01,"6/11/2007","12:11pm",-0.03,62.79,63.19,62.42,1905230 "BA",97.856,"6/11/2007","12:11pm",-0.334,98.25,98.79,97.74,1115200 "C",53.57,"6/11/2007","12:11pm",+0.24,53.20,53.65,52.81,4395794 "CAT",78.82,"6/11/2007","12:10pm",+0.30,78.32,78.99,78.06,1308952 "DD",50.71,"6/11/2007","12:10pm",-0.42,51.13,51.21,50.59,1760700 "DIS",34.23,"6/11/2007","12:11pm",+0.03,34.28,34.44,34.12,2537850 "GE",37.4025,"6/11/2007","12:11pm",+0.0825,37.07,37.49,37.05,10173701 "GM",31.27,"6/11/2007","12:11pm",+0.27,31.00,31.62,30.90,6100601 "HD",37.75,"6/11/2007","12:11pm",-0.20,37.78,37.83,37.62,4021569 "HON",57.00,"6/11/2007","12:11pm",-0.38,57.25,57.40,56.91,1666742 "HPQ",46.08,"6/11/2007","12:10pm",+0.38,45.80,46.29,45.46,4173956 "IBM",103.56,"6/11/2007","12:11pm",+0.49,102.87,103.71,102.50,2111700 "INTC",21.98,"6/11/2007","12:16pm",+0.15,21.70,22.02,21.69,18999740 "JNJ",62.56,"6/11/2007","12:11pm",+0.43,62.89,62.89,62.15,3392622 "JPM",50.60,"6/11/2007","12:11pm",+0.19,50.41,50.62,50.05,3283000 "KO",51.53,"6/11/2007","12:11pm",-0.14,51.67,51.82,51.32,5438042 "MCD",51.455,"6/11/2007","12:11pm",+0.045,51.47,51.62,50.98,2250714 "MMM",85.32,"6/11/2007","12:11pm",-0.62,85.94,85.98,85.32,975500 "MO",70.38,"6/11/2007","12:11pm",+0.08,70.25,70.50,69.76,4010457 "MRK",50.75,"6/11/2007","12:10pm",+0.61,50.30,50.87,50.04,4599200 "MSFT",30.13,"6/11/2007","12:16pm",+0.08,30.05,30.25,29.93,19453430 "PFE",26.38,"6/11/2007","12:11pm",-0.14,26.50,26.53,26.31,9435387 "PG",63.01,"6/11/2007","12:11pm",-0.06,62.80,63.12,62.75,2550946 "T",40.13,"6/11/2007","12:11pm",-0.13,40.20,40.25,39.89,5619200 "UTX",69.89,"6/11/2007","12:11pm",-0.34,69.85,70.20,69.51,893500 "VZ",43.31,"6/11/2007","12:11pm",+0.24,42.95,43.34,42.89,3525820 "WMT",49.86,"6/11/2007","12:11pm",-0.22,49.90,50.00,49.55,4320433 "XOM",83.38,"6/11/2007","12:11pm",+0.70,82.68,83.48,82.35,5384300 "AA",39.51,"6/11/2007","12:15pm",-0.15,39.67,40.18,39.43,1707780 "AIG",71.68,"6/11/2007","12:16pm",+0.15,71.29,71.70,71.15,1797642 "AXP",62.98,"6/11/2007","12:15pm",-0.06,62.79,63.19,62.42,1919830 "BA",97.81,"6/11/2007","12:16pm",-0.38,98.25,98.79,97.74,1189800 "C",53.51,"6/11/2007","12:15pm",+0.18,53.20,53.65,52.81,4582294 "CAT",78.87,"6/11/2007","12:16pm",+0.35,78.32,78.99,78.06,1321852 "DD",50.69,"6/11/2007","12:16pm",-0.44,51.13,51.21,50.59,1790800 "DIS",34.21,"6/11/2007","12:16pm",+0.01,34.28,34.44,34.12,2586450 "GE",37.40,"6/11/2007","12:16pm",+0.08,37.07,37.49,37.05,10295201 "GM",31.27,"6/11/2007","12:16pm",+0.27,31.00,31.62,30.90,6373201 "HD",37.752,"6/11/2007","12:16pm",-0.198,37.78,37.83,37.62,4072169 "HON",57.00,"6/11/2007","12:16pm",-0.38,57.25,57.40,56.91,1683042 "HPQ",46.06,"6/11/2007","12:15pm",+0.36,45.80,46.29,45.46,4225556 "IBM",103.54,"6/11/2007","12:16pm",+0.47,102.87,103.71,102.50,2128600 "INTC",21.98,"6/11/2007","12:21pm",+0.15,21.70,22.02,21.69,19143976 "JNJ",62.57,"6/11/2007","12:16pm",+0.44,62.89,62.89,62.15,3439222 "JPM",50.59,"6/11/2007","12:16pm",+0.18,50.41,50.62,50.05,3372000 "KO",51.5418,"6/11/2007","12:16pm",-0.1282,51.67,51.82,51.32,5475442 "MCD",51.44,"6/11/2007","12:16pm",+0.03,51.47,51.62,50.98,2321914 "MMM",85.34,"6/11/2007","12:16pm",-0.60,85.94,85.98,85.32,985300 "MO",70.31,"6/11/2007","12:15pm",+0.01,70.25,70.50,69.76,4067257 "MRK",50.74,"6/11/2007","12:16pm",+0.60,50.30,50.87,50.04,4655400 "MSFT",30.10,"6/11/2007","12:21pm",+0.05,30.05,30.25,29.93,19617530 "PFE",26.38,"6/11/2007","12:16pm",-0.14,26.50,26.53,26.31,9916032 "PG",62.99,"6/11/2007","12:16pm",-0.08,62.80,63.12,62.75,2626746 "T",40.10,"6/11/2007","12:16pm",-0.16,40.20,40.25,39.89,5730400 "UTX",69.89,"6/11/2007","12:15pm",-0.34,69.85,70.20,69.51,919000 "VZ",43.252,"6/11/2007","12:16pm",+0.182,42.95,43.34,42.89,3563720 "WMT",49.882,"6/11/2007","12:16pm",-0.198,49.90,50.00,49.55,4432033 "XOM",83.37,"6/11/2007","12:16pm",+0.69,82.68,83.48,82.35,5492200 "AA",39.48,"6/11/2007","12:21pm",-0.18,39.67,40.18,39.43,1815880 "AIG",71.71,"6/11/2007","12:21pm",+0.18,71.29,71.71,71.15,1835142 "AXP",62.92,"6/11/2007","12:20pm",-0.12,62.79,63.19,62.42,1934230 "BA",97.79,"6/11/2007","12:21pm",-0.40,98.25,98.79,97.74,1228800 "C",53.53,"6/11/2007","12:21pm",+0.20,53.20,53.65,52.81,4665694 "CAT",78.84,"6/11/2007","12:20pm",+0.32,78.32,78.99,78.06,1332852 "DD",50.69,"6/11/2007","12:21pm",-0.44,51.13,51.21,50.59,1803100 "DIS",34.21,"6/11/2007","12:21pm",+0.01,34.28,34.44,34.12,2632250 "GE",37.41,"6/11/2007","12:21pm",+0.09,37.07,37.49,37.05,10406001 "GM",31.29,"6/11/2007","12:21pm",+0.29,31.00,31.62,30.90,6432201 "HD",37.75,"6/11/2007","12:20pm",-0.20,37.78,37.83,37.62,5032769 "HON",56.98,"6/11/2007","12:20pm",-0.40,57.25,57.40,56.91,1693442 "HPQ",46.03,"6/11/2007","12:21pm",+0.33,45.80,46.29,45.46,4304056 "IBM",103.56,"6/11/2007","12:20pm",+0.49,102.87,103.71,102.50,2151600 "INTC",21.963,"6/11/2007","12:26pm",+0.133,21.70,22.02,21.69,19623448 "JNJ",62.56,"6/11/2007","12:21pm",+0.43,62.89,62.89,62.15,3517522 "JPM",50.58,"6/11/2007","12:21pm",+0.17,50.41,50.62,50.05,3435300 "KO",51.52,"6/11/2007","12:21pm",-0.15,51.67,51.82,51.32,5546242 "MCD",51.40,"6/11/2007","12:20pm",-0.01,51.47,51.62,50.98,2357014 "MMM",85.33,"6/11/2007","12:20pm",-0.61,85.94,85.98,85.32,996200 "MO",70.27,"6/11/2007","12:21pm",-0.03,70.25,70.50,69.76,4106257 "MRK",50.72,"6/11/2007","12:21pm",+0.58,50.30,50.87,50.04,4713700 "MSFT",30.12,"6/11/2007","12:26pm",+0.07,30.05,30.25,29.93,19901670 "PFE",26.37,"6/11/2007","12:21pm",-0.15,26.50,26.53,26.31,10036232 "PG",63.01,"6/11/2007","12:20pm",-0.06,62.80,63.12,62.75,2656246 "T",40.09,"6/11/2007","12:21pm",-0.17,40.20,40.25,39.89,5849000 "UTX",69.80,"6/11/2007","12:20pm",-0.43,69.85,70.20,69.51,935600 "VZ",43.24,"6/11/2007","12:20pm",+0.17,42.95,43.34,42.89,3608620 "WMT",49.87,"6/11/2007","12:21pm",-0.21,49.90,50.00,49.55,4485833 "XOM",83.39,"6/11/2007","12:21pm",+0.71,82.68,83.48,82.35,5559000 "AA",39.49,"6/11/2007","12:26pm",-0.17,39.67,40.18,39.43,1856980 "AIG",71.73,"6/11/2007","12:26pm",+0.20,71.29,71.74,71.15,1875942 "AXP",62.90,"6/11/2007","12:26pm",-0.14,62.79,63.19,62.42,1944730 "BA",97.75,"6/11/2007","12:26pm",-0.44,98.25,98.79,97.74,1241700 "C",53.55,"6/11/2007","12:26pm",+0.22,53.20,53.65,52.81,4736994 "CAT",78.87,"6/11/2007","12:25pm",+0.35,78.32,78.99,78.06,1347152 "DD",50.70,"6/11/2007","12:26pm",-0.43,51.13,51.21,50.59,1812800 "DIS",34.20,"6/11/2007","12:26pm",0.00,34.28,34.44,34.12,2667150 "GE",37.41,"6/11/2007","12:26pm",+0.09,37.07,37.49,37.05,10594701 "GM",31.27,"6/11/2007","12:26pm",+0.27,31.00,31.62,30.90,6554401 "HD",37.73,"6/11/2007","12:26pm",-0.22,37.78,37.83,37.62,5061669 "HON",56.96,"6/11/2007","12:26pm",-0.42,57.25,57.40,56.91,1723242 "HPQ",46.02,"6/11/2007","12:26pm",+0.32,45.80,46.29,45.46,4379056 "IBM",103.64,"6/11/2007","12:26pm",+0.57,102.87,103.71,102.50,2198300 "INTC",21.97,"6/11/2007","12:31pm",+0.14,21.70,22.02,21.69,19931936 "JNJ",62.60,"6/11/2007","12:26pm",+0.47,62.89,62.89,62.15,3566938 "JPM",50.61,"6/11/2007","12:26pm",+0.20,50.41,50.62,50.05,3481000 "KO",51.55,"6/11/2007","12:26pm",-0.12,51.67,51.82,51.32,5589842 "MCD",51.37,"6/11/2007","12:26pm",-0.04,51.47,51.62,50.98,2377914 "MMM",85.33,"6/11/2007","12:25pm",-0.61,85.94,85.98,85.32,1000600 "MO",70.23,"6/11/2007","12:26pm",-0.07,70.25,70.50,69.76,4146657 "MRK",50.79,"6/11/2007","12:26pm",+0.65,50.30,50.87,50.04,4803500 "MSFT",30.145,"6/11/2007","12:31pm",+0.095,30.05,30.25,29.93,20108456 "PFE",26.38,"6/11/2007","12:26pm",-0.14,26.50,26.53,26.31,10177632 "PG",62.98,"6/11/2007","12:26pm",-0.09,62.80,63.12,62.75,2697146 "T",40.12,"6/11/2007","12:26pm",-0.14,40.20,40.25,39.89,5959900 "UTX",69.80,"6/11/2007","12:26pm",-0.43,69.85,70.20,69.51,946900 "VZ",43.23,"6/11/2007","12:26pm",+0.16,42.95,43.34,42.89,3648320 "WMT",49.90,"6/11/2007","12:26pm",-0.18,49.90,50.00,49.55,4562633 "XOM",83.43,"6/11/2007","12:26pm",+0.75,82.68,83.48,82.35,5652500 "AA",39.50,"6/11/2007","12:30pm",-0.16,39.67,40.18,39.43,1898080 "AIG",71.74,"6/11/2007","12:31pm",+0.21,71.29,71.74,71.15,1905942 "AXP",62.9008,"6/11/2007","12:31pm",-0.1392,62.79,63.19,62.42,1968730 "BA",97.67,"6/11/2007","12:30pm",-0.52,98.25,98.79,97.63,1294900 "C",53.57,"6/11/2007","12:31pm",+0.24,53.20,53.65,52.81,4767094 "CAT",78.89,"6/11/2007","12:31pm",+0.37,78.32,78.99,78.06,1360152 "DD",50.73,"6/11/2007","12:31pm",-0.40,51.13,51.21,50.59,1833000 "DIS",34.195,"6/11/2007","12:31pm",-0.005,34.28,34.44,34.12,2687550 "GE",37.41,"6/11/2007","12:31pm",+0.09,37.07,37.49,37.05,10633601 "GM",31.25,"6/11/2007","12:31pm",+0.25,31.00,31.62,30.90,6609301 "HD",37.74,"6/11/2007","12:30pm",-0.21,37.78,37.83,37.62,5089969 "HON",56.95,"6/11/2007","12:31pm",-0.43,57.25,57.40,56.91,1741042 "HPQ",46.02,"6/11/2007","12:30pm",+0.32,45.80,46.29,45.46,4453056 "IBM",103.67,"6/11/2007","12:31pm",+0.60,102.87,103.71,102.50,2226700 "INTC",21.96,"6/11/2007","12:36pm",+0.13,21.70,22.02,21.69,20005174 "JNJ",62.60,"6/11/2007","12:31pm",+0.47,62.89,62.89,62.15,3584438 "JPM",50.60,"6/11/2007","12:31pm",+0.19,50.41,50.62,50.05,3513000 "KO",51.56,"6/11/2007","12:30pm",-0.11,51.67,51.82,51.32,5620842 "MCD",51.34,"6/11/2007","12:31pm",-0.07,51.47,51.62,50.98,2442514 "MMM",85.28,"6/11/2007","12:30pm",-0.66,85.94,85.98,85.28,1017900 "MO",70.24,"6/11/2007","12:30pm",-0.06,70.25,70.50,69.76,4185257 "MRK",50.84,"6/11/2007","12:31pm",+0.70,50.30,50.87,50.04,4848100 "MSFT",30.14,"6/11/2007","12:36pm",+0.09,30.05,30.25,29.93,20198736 "PFE",26.39,"6/11/2007","12:31pm",-0.13,26.50,26.53,26.31,10293432 "PG",63.00,"6/11/2007","12:31pm",-0.07,62.80,63.12,62.75,2745846 "T",40.07,"6/11/2007","12:31pm",-0.19,40.20,40.25,39.89,6112900 "UTX",69.81,"6/11/2007","12:31pm",-0.42,69.85,70.20,69.51,958400 "VZ",43.27,"6/11/2007","12:31pm",+0.20,42.95,43.34,42.89,3693720 "WMT",49.92,"6/11/2007","12:31pm",-0.16,49.90,50.00,49.55,4676833 "XOM",83.46,"6/11/2007","12:31pm",+0.78,82.68,83.48,82.35,5711300 "AA",39.47,"6/11/2007","12:35pm",-0.19,39.67,40.18,39.43,1938380 "AIG",71.705,"6/11/2007","12:35pm",+0.175,71.29,71.76,71.15,1958142 "AXP",62.91,"6/11/2007","12:36pm",-0.13,62.79,63.19,62.42,1982630 "BA",97.65,"6/11/2007","12:35pm",-0.54,98.25,98.79,97.61,1324100 "C",53.56,"6/11/2007","12:36pm",+0.23,53.20,53.65,52.81,4843994 "CAT",78.82,"6/11/2007","12:36pm",+0.30,78.32,78.99,78.06,1381852 "DD",50.70,"6/11/2007","12:36pm",-0.43,51.13,51.21,50.59,1854300 "DIS",34.19,"6/11/2007","12:36pm",-0.01,34.28,34.44,34.12,2871150 "GE",37.41,"6/11/2007","12:36pm",+0.09,37.07,37.49,37.05,10703601 "GM",31.28,"6/11/2007","12:36pm",+0.28,31.00,31.62,30.90,6667301 "HD",37.74,"6/11/2007","12:35pm",-0.21,37.78,37.83,37.62,5127869 "HON",56.93,"6/11/2007","12:36pm",-0.45,57.25,57.40,56.91,1772242 "HPQ",46.01,"6/11/2007","12:35pm",+0.31,45.80,46.29,45.46,4478756 "IBM",103.70,"6/11/2007","12:36pm",+0.63,102.87,103.73,102.50,2253600 "INTC",21.96,"6/11/2007","12:41pm",+0.13,21.70,22.02,21.69,20062818 "JNJ",62.59,"6/11/2007","12:36pm",+0.46,62.89,62.89,62.15,3609538 "JPM",50.60,"6/11/2007","12:36pm",+0.19,50.41,50.62,50.05,3631000 "KO",51.58,"6/11/2007","12:36pm",-0.09,51.67,51.82,51.32,5643442 "MCD",51.37,"6/11/2007","12:36pm",-0.04,51.47,51.62,50.98,2479114 "MMM",85.34,"6/11/2007","12:36pm",-0.60,85.94,85.98,85.28,1032100 "MO",70.32,"6/11/2007","12:36pm",+0.02,70.25,70.50,69.76,4255057 "MRK",50.90,"6/11/2007","12:36pm",+0.76,50.30,50.91,50.04,4953900 "MSFT",30.13,"6/11/2007","12:41pm",+0.08,30.05,30.25,29.93,20258588 "PFE",26.41,"6/11/2007","12:36pm",-0.11,26.50,26.53,26.31,10510182 "PG",63.03,"6/11/2007","12:36pm",-0.04,62.80,63.12,62.75,2776446 "T",40.08,"6/11/2007","12:36pm",-0.18,40.20,40.25,39.89,6259175 "UTX",69.81,"6/11/2007","12:35pm",-0.42,69.85,70.20,69.51,964300 "VZ",43.28,"6/11/2007","12:36pm",+0.21,42.95,43.34,42.89,3720320 "WMT",49.94,"6/11/2007","12:36pm",-0.14,49.90,50.00,49.55,4837533 "XOM",83.45,"6/11/2007","12:36pm",+0.77,82.68,83.50,82.35,5799000 "AA",39.48,"6/11/2007","12:41pm",-0.18,39.67,40.18,39.43,1988580 "AIG",71.70,"6/11/2007","12:40pm",+0.17,71.29,71.76,71.15,2020642 "AXP",62.91,"6/11/2007","12:41pm",-0.13,62.79,63.19,62.42,1993830 "BA",97.73,"6/11/2007","12:40pm",-0.46,98.25,98.79,97.59,1344400 "C",53.52,"6/11/2007","12:41pm",+0.19,53.20,53.65,52.81,5118094 "CAT",78.88,"6/11/2007","12:41pm",+0.36,78.32,78.99,78.06,1400252 "DD",50.73,"6/11/2007","12:41pm",-0.40,51.13,51.21,50.59,1866700 "DIS",34.181,"6/11/2007","12:41pm",-0.019,34.28,34.44,34.12,2913850 "GE",37.42,"6/11/2007","12:41pm",+0.10,37.07,37.49,37.05,10856501 "GM",31.32,"6/11/2007","12:41pm",+0.32,31.00,31.62,30.90,6784901 "HD",37.75,"6/11/2007","12:41pm",-0.20,37.78,37.83,37.62,5186969 "HON",56.96,"6/11/2007","12:41pm",-0.42,57.25,57.40,56.91,1802642 "HPQ",46.01,"6/11/2007","12:41pm",+0.31,45.80,46.29,45.46,4507656 "IBM",103.74,"6/11/2007","12:40pm",+0.67,102.87,103.76,102.50,2288300 "INTC",21.96,"6/11/2007","12:46pm",+0.13,21.70,22.02,21.69,20169300 "JNJ",62.62,"6/11/2007","12:41pm",+0.49,62.89,62.89,62.15,3651538 "JPM",50.55,"6/11/2007","12:41pm",+0.14,50.41,50.62,50.05,3814700 "KO",51.56,"6/11/2007","12:40pm",-0.11,51.67,51.82,51.32,5678742 "MCD",51.40,"6/11/2007","12:41pm",-0.01,51.47,51.62,50.98,2510514 "MMM",85.33,"6/11/2007","12:40pm",-0.61,85.94,85.98,85.28,1043700 "MO",70.35,"6/11/2007","12:41pm",+0.05,70.25,70.50,69.76,4300857 "MRK",51.06,"6/11/2007","12:41pm",+0.92,50.30,51.07,50.04,5195700 "MSFT",30.13,"6/11/2007","12:46pm",+0.08,30.05,30.25,29.93,20422172 "PFE",26.41,"6/11/2007","12:41pm",-0.11,26.50,26.53,26.31,10595782 "PG",63.05,"6/11/2007","12:41pm",-0.02,62.80,63.12,62.75,2806346 "T",40.06,"6/11/2007","12:41pm",-0.20,40.20,40.25,39.89,6338875 "UTX",69.81,"6/11/2007","12:41pm",-0.42,69.85,70.20,69.51,968100 "VZ",43.28,"6/11/2007","12:41pm",+0.21,42.95,43.34,42.89,3802620 "WMT",49.92,"6/11/2007","12:41pm",-0.16,49.90,50.00,49.55,4964533 "XOM",83.55,"6/11/2007","12:41pm",+0.87,82.68,83.57,82.35,5902800 "AA",39.48,"6/11/2007","12:46pm",-0.18,39.67,40.18,39.43,2011580 "AIG",71.68,"6/11/2007","12:45pm",+0.15,71.29,71.76,71.15,2066242 "AXP",62.93,"6/11/2007","12:46pm",-0.11,62.79,63.19,62.42,2001530 "BA",97.68,"6/11/2007","12:46pm",-0.51,98.25,98.79,97.59,1387800 "C",53.54,"6/11/2007","12:46pm",+0.21,53.20,53.65,52.81,5168294 "CAT",78.87,"6/11/2007","12:46pm",+0.35,78.32,78.99,78.06,1416552 "DD",50.70,"6/11/2007","12:46pm",-0.43,51.13,51.21,50.59,1884900 "DIS",34.20,"6/11/2007","12:46pm",0.00,34.28,34.44,34.12,2958150 "GE",37.40,"6/11/2007","12:46pm",+0.08,37.07,37.49,37.05,10931901 "GM",31.29,"6/11/2007","12:46pm",+0.29,31.00,31.62,30.90,6930401 "HD",37.75,"6/11/2007","12:46pm",-0.20,37.78,37.83,37.62,5233269 "HON",56.99,"6/11/2007","12:46pm",-0.39,57.25,57.40,56.91,1827642 "HPQ",46.01,"6/11/2007","12:46pm",+0.31,45.80,46.29,45.46,4557656 "IBM",103.75,"6/11/2007","12:46pm",+0.68,102.87,103.78,102.50,2310700 "INTC",22.00,"6/11/2007","12:51pm",+0.17,21.70,22.02,21.69,20568860 "JNJ",62.59,"6/11/2007","12:46pm",+0.46,62.89,62.89,62.15,3702738 "JPM",50.57,"6/11/2007","12:46pm",+0.16,50.41,50.62,50.05,3902300 "KO",51.58,"6/11/2007","12:46pm",-0.09,51.67,51.82,51.32,5707242 "MCD",51.44,"6/11/2007","12:46pm",+0.03,51.47,51.62,50.98,2534014 "MMM",85.33,"6/11/2007","12:45pm",-0.61,85.94,85.98,85.28,1051300 "MO",70.32,"6/11/2007","12:46pm",+0.02,70.25,70.50,69.76,4330057 "MRK",50.97,"6/11/2007","12:46pm",+0.83,50.30,51.07,50.04,5430200 "MSFT",30.13,"6/11/2007","12:51pm",+0.08,30.05,30.25,29.93,20730214 "PFE",26.43,"6/11/2007","12:46pm",-0.09,26.50,26.53,26.31,10698282 "PG",63.05,"6/11/2007","12:46pm",-0.02,62.80,63.12,62.75,2909046 "T",40.06,"6/11/2007","12:46pm",-0.20,40.20,40.25,39.89,6387575 "UTX",69.81,"6/11/2007","12:46pm",-0.42,69.85,70.20,69.51,976200 "VZ",43.28,"6/11/2007","12:46pm",+0.21,42.95,43.34,42.89,3826420 "WMT",49.95,"6/11/2007","12:46pm",-0.13,49.90,50.00,49.55,5329433 "XOM",83.58,"6/11/2007","12:46pm",+0.90,82.68,83.59,82.35,5965700 "AA",39.48,"6/11/2007","12:51pm",-0.18,39.67,40.18,39.43,2052980 "AIG",71.67,"6/11/2007","12:51pm",+0.14,71.29,71.76,71.15,2109942 "AXP",62.90,"6/11/2007","12:50pm",-0.14,62.79,63.19,62.42,2024830 "BA",97.78,"6/11/2007","12:51pm",-0.41,98.25,98.79,97.59,1430600 "C",53.56,"6/11/2007","12:51pm",+0.23,53.20,53.65,52.81,5509594 "CAT",79.00,"6/11/2007","12:50pm",+0.48,78.32,79.00,78.06,1448852 "DD",50.71,"6/11/2007","12:51pm",-0.42,51.13,51.21,50.59,1916300 "DIS",34.192,"6/11/2007","12:50pm",-0.008,34.28,34.44,34.12,3022750 "GE",37.41,"6/11/2007","12:51pm",+0.09,37.07,37.49,37.05,11159401 "GM",31.38,"6/11/2007","12:51pm",+0.38,31.00,31.62,30.90,7135301 "HD",37.76,"6/11/2007","12:51pm",-0.19,37.78,37.83,37.62,5362369 "HON",57.05,"6/11/2007","12:51pm",-0.33,57.25,57.40,56.91,1871542 "HPQ",46.03,"6/11/2007","12:51pm",+0.33,45.80,46.29,45.46,4684356 "IBM",103.87,"6/11/2007","12:51pm",+0.80,102.87,103.89,102.50,2362500 "INTC",22.00,"6/11/2007","12:56pm",+0.17,21.70,22.02,21.69,20977118 "JNJ",62.58,"6/11/2007","12:51pm",+0.45,62.89,62.89,62.15,3790288 "JPM",50.60,"6/11/2007","12:51pm",+0.19,50.41,50.63,50.05,3966000 "KO",51.59,"6/11/2007","12:51pm",-0.08,51.67,51.82,51.32,5755942 "MCD",51.42,"6/11/2007","12:50pm",+0.01,51.47,51.62,50.98,2580014 "MMM",85.36,"6/11/2007","12:50pm",-0.58,85.94,85.98,85.28,1063300 "MO",70.37,"6/11/2007","12:51pm",+0.07,70.25,70.50,69.76,4395657 "MRK",50.97,"6/11/2007","12:51pm",+0.83,50.30,51.07,50.04,5527700 "MSFT",30.17,"6/11/2007","12:56pm",+0.12,30.05,30.25,29.93,21243952 "PFE",26.43,"6/11/2007","12:51pm",-0.09,26.50,26.53,26.31,10870382 "PG",63.04,"6/11/2007","12:51pm",-0.03,62.80,63.12,62.75,3002346 "T",40.01,"6/11/2007","12:51pm",-0.25,40.20,40.25,39.89,6578675 "UTX",69.88,"6/11/2007","12:50pm",-0.35,69.85,70.20,69.51,986100 "VZ",43.30,"6/11/2007","12:51pm",+0.23,42.95,43.34,42.89,3880620 "WMT",49.9786,"6/11/2007","12:51pm",-0.1014,49.90,50.00,49.55,5399333 "XOM",83.67,"6/11/2007","12:51pm",+0.99,82.68,83.67,82.35,6065100 "AA",39.50,"6/11/2007","12:56pm",-0.16,39.67,40.18,39.43,2111680 "AIG",71.68,"6/11/2007","12:56pm",+0.15,71.29,71.76,71.15,2166942 "AXP",63.00,"6/11/2007","12:56pm",-0.04,62.79,63.19,62.42,2038530 "BA",97.71,"6/11/2007","12:56pm",-0.48,98.25,98.79,97.59,1475700 "C",53.5614,"6/11/2007","12:56pm",+0.2314,53.20,53.65,52.81,5665694 "CAT",79.14,"6/11/2007","12:56pm",+0.62,78.32,79.14,78.06,1510252 "DD",50.79,"6/11/2007","12:56pm",-0.34,51.13,51.21,50.59,1934600 "DIS",34.20,"6/11/2007","12:56pm",0.00,34.28,34.44,34.12,3112050 "GE",37.47,"6/11/2007","12:56pm",+0.15,37.07,37.49,37.05,11425501 "GM",31.37,"6/11/2007","12:56pm",+0.37,31.00,31.62,30.90,7225026 "HD",37.77,"6/11/2007","12:56pm",-0.18,37.78,37.83,37.62,5433169 "HON",57.11,"6/11/2007","12:56pm",-0.27,57.25,57.40,56.91,1911042 "HPQ",46.07,"6/11/2007","12:55pm",+0.37,45.80,46.29,45.46,4729356 "IBM",103.97,"6/11/2007","12:56pm",+0.90,102.87,104.00,102.50,2444600 "INTC",22.00,"6/11/2007","1:01pm",+0.17,21.70,22.02,21.69,21559788 "JNJ",62.59,"6/11/2007","12:56pm",+0.46,62.89,62.89,62.15,3837538 "JPM",50.65,"6/11/2007","12:56pm",+0.24,50.41,50.65,50.05,4042900 "KO",51.67,"6/11/2007","12:56pm",0.00,51.67,51.82,51.32,5793842 "MCD",51.44,"6/11/2007","12:56pm",+0.03,51.47,51.62,50.98,2635114 "MMM",85.46,"6/11/2007","12:55pm",-0.48,85.94,85.98,85.28,1082400 "MO",70.35,"6/11/2007","12:56pm",+0.05,70.25,70.50,69.76,4427157 "MRK",50.96,"6/11/2007","12:56pm",+0.82,50.30,51.07,50.04,5634400 "MSFT",30.14,"6/11/2007","1:01pm",+0.09,30.05,30.25,29.93,21696948 "PFE",26.45,"6/11/2007","12:56pm",-0.07,26.50,26.53,26.31,11036032 "PG",63.08,"6/11/2007","12:56pm",+0.01,62.80,63.12,62.75,3066446 "T",40.04,"6/11/2007","12:56pm",-0.22,40.20,40.25,39.89,6709275 "UTX",69.92,"6/11/2007","12:56pm",-0.31,69.85,70.20,69.51,1008800 "VZ",43.32,"6/11/2007","12:56pm",+0.25,42.95,43.34,42.89,3928820 "WMT",50.01,"6/11/2007","12:56pm",-0.07,49.90,50.04,49.55,5568433 "XOM",83.66,"6/11/2007","12:56pm",+0.98,82.68,83.72,82.35,6158700 "AA",39.51,"6/11/2007","1:01pm",-0.15,39.67,40.18,39.43,2207380 "AIG",71.64,"6/11/2007","1:01pm",+0.11,71.29,71.76,71.15,2223342 "AXP",62.96,"6/11/2007","1:01pm",-0.08,62.79,63.19,62.42,2056230 "BA",97.65,"6/11/2007","1:01pm",-0.54,98.25,98.79,97.59,1502900 "C",53.58,"6/11/2007","1:01pm",+0.25,53.20,53.65,52.81,5805794 "CAT",79.07,"6/11/2007","1:00pm",+0.55,78.32,79.14,78.06,1532652 "DD",50.7784,"6/11/2007","1:01pm",-0.3516,51.13,51.21,50.59,1957300 "DIS",34.20,"6/11/2007","1:01pm",0.00,34.28,34.44,34.12,3159250 "GE",37.48,"6/11/2007","1:01pm",+0.16,37.07,37.49,37.05,11769301 "GM",31.34,"6/11/2007","1:01pm",+0.34,31.00,31.62,30.90,7327251 "HD",37.75,"6/11/2007","1:01pm",-0.20,37.78,37.83,37.62,5482469 "HON",56.98,"6/11/2007","1:01pm",-0.40,57.25,57.40,56.91,1964142 "HPQ",46.05,"6/11/2007","1:01pm",+0.35,45.80,46.29,45.46,4778356 "IBM",103.79,"6/11/2007","1:01pm",+0.72,102.87,104.00,102.50,2477300 "INTC",22.01,"6/11/2007","1:06pm",+0.18,21.70,22.02,21.69,21974040 "JNJ",62.55,"6/11/2007","1:01pm",+0.42,62.89,62.89,62.15,4653538 "JPM",50.64,"6/11/2007","1:01pm",+0.23,50.41,50.66,50.05,4113700 "KO",51.61,"6/11/2007","1:01pm",-0.06,51.67,51.82,51.32,5829142 "MCD",51.40,"6/11/2007","1:01pm",-0.01,51.47,51.62,50.98,2668014 "MMM",85.38,"6/11/2007","1:00pm",-0.56,85.94,85.98,85.28,1091500 "MO",70.29,"6/11/2007","1:01pm",-0.01,70.25,70.50,69.76,4483057 "MRK",50.95,"6/11/2007","1:01pm",+0.81,50.30,51.07,50.04,5728600 "MSFT",30.14,"6/11/2007","1:06pm",+0.09,30.05,30.25,29.93,21878754 "PFE",26.44,"6/11/2007","1:01pm",-0.08,26.50,26.53,26.31,11232332 "PG",63.01,"6/11/2007","1:01pm",-0.06,62.80,63.12,62.75,3117846 "T",40.03,"6/11/2007","1:01pm",-0.23,40.20,40.25,39.89,6799375 "UTX",69.90,"6/11/2007","1:00pm",-0.33,69.85,70.20,69.51,1060200 "VZ",43.25,"6/11/2007","1:01pm",+0.18,42.95,43.34,42.89,4008920 "WMT",50.01,"6/11/2007","1:01pm",-0.07,49.90,50.04,49.55,5777993 "XOM",83.57,"6/11/2007","1:01pm",+0.89,82.68,83.72,82.35,6284800 "AA",39.52,"6/11/2007","1:06pm",-0.14,39.67,40.18,39.43,2284880 "AIG",71.69,"6/11/2007","1:06pm",+0.16,71.29,71.76,71.15,2274642 "AXP",62.95,"6/11/2007","1:06pm",-0.09,62.79,63.19,62.42,2065330 "BA",97.65,"6/11/2007","1:06pm",-0.54,98.25,98.79,97.59,1513500 "C",53.57,"6/11/2007","1:06pm",+0.24,53.20,53.65,52.81,5889294 "CAT",79.07,"6/11/2007","1:05pm",+0.55,78.32,79.14,78.06,1549052 "DD",50.79,"6/11/2007","1:06pm",-0.34,51.13,51.21,50.59,1978300 "DIS",34.195,"6/11/2007","1:06pm",-0.005,34.28,34.44,34.12,3179550 "GE",37.48,"6/11/2007","1:06pm",+0.16,37.07,37.49,37.05,11921001 "GM",31.34,"6/11/2007","1:06pm",+0.34,31.00,31.62,30.90,7418551 "HD",37.75,"6/11/2007","1:06pm",-0.20,37.78,37.83,37.62,5559869 "HON",56.99,"6/11/2007","1:06pm",-0.39,57.25,57.40,56.91,2007542 "HPQ",46.0627,"6/11/2007","1:05pm",+0.3627,45.80,46.29,45.46,4829456 "IBM",103.76,"6/11/2007","1:05pm",+0.69,102.87,104.00,102.50,2503900 "INTC",22.04,"6/11/2007","1:11pm",+0.21,21.70,22.05,21.69,22780160 "JNJ",62.58,"6/11/2007","1:06pm",+0.45,62.89,62.89,62.15,4682578 "JPM",50.649,"6/11/2007","1:06pm",+0.239,50.41,50.66,50.05,4156000 "KO",51.68,"6/11/2007","1:06pm",+0.01,51.67,51.82,51.32,5958742 "MCD",51.38,"6/11/2007","1:06pm",-0.03,51.47,51.62,50.98,2710414 "MMM",85.40,"6/11/2007","1:05pm",-0.54,85.94,85.98,85.28,1115800 "MO",70.29,"6/11/2007","1:06pm",-0.01,70.25,70.50,69.76,4510357 "MRK",50.95,"6/11/2007","1:06pm",+0.81,50.30,51.07,50.04,5784900 "MSFT",30.16,"6/11/2007","1:11pm",+0.11,30.05,30.25,29.93,22141974 "PFE",26.50,"6/11/2007","1:06pm",-0.02,26.50,26.53,26.31,11587539 "PG",63.07,"6/11/2007","1:06pm",0.00,62.80,63.12,62.75,3192946 "T",40.13,"6/11/2007","1:06pm",-0.13,40.20,40.25,39.89,6957775 "UTX",69.91,"6/11/2007","1:06pm",-0.32,69.85,70.20,69.51,1075500 "VZ",43.2716,"6/11/2007","1:05pm",+0.2016,42.95,43.34,42.89,4057020 "WMT",50.07,"6/11/2007","1:06pm",-0.01,49.90,50.08,49.55,5985207 "XOM",83.53,"6/11/2007","1:06pm",+0.85,82.68,83.72,82.35,6388600 "AA",39.53,"6/11/2007","1:11pm",-0.13,39.67,40.18,39.43,2319180 "AIG",71.74,"6/11/2007","1:11pm",+0.21,71.29,71.76,71.15,2359542 "AXP",63.12,"6/11/2007","1:11pm",+0.08,62.79,63.19,62.42,2086730 "BA",97.75,"6/11/2007","1:11pm",-0.44,98.25,98.79,97.59,1556900 "C",53.67,"6/11/2007","1:11pm",+0.34,53.20,53.69,52.81,6010594 "CAT",79.17,"6/11/2007","1:11pm",+0.65,78.32,79.19,78.06,1577652 "DD",50.79,"6/11/2007","1:11pm",-0.34,51.13,51.21,50.59,2054300 "DIS",34.22,"6/11/2007","1:11pm",+0.02,34.28,34.44,34.12,3245950 "GE",37.53,"6/11/2007","1:11pm",+0.21,37.07,37.53,37.05,12200301 "GM",31.42,"6/11/2007","1:11pm",+0.42,31.00,31.62,30.90,7518551 "HD",37.78,"6/11/2007","1:11pm",-0.17,37.78,37.83,37.62,5639569 "HON",57.13,"6/11/2007","1:11pm",-0.25,57.25,57.40,56.91,2042642 "HPQ",46.12,"6/11/2007","1:11pm",+0.42,45.80,46.29,45.46,5833149 "IBM",103.86,"6/11/2007","1:11pm",+0.79,102.87,104.00,102.50,2536300 "INTC",22.05,"6/11/2007","1:16pm",+0.22,21.70,22.07,21.69,23218578 "JNJ",62.65,"6/11/2007","1:11pm",+0.52,62.89,62.89,62.15,4727878 "JPM",50.69,"6/11/2007","1:11pm",+0.28,50.41,50.695,50.05,4239700 "KO",51.74,"6/11/2007","1:11pm",+0.07,51.67,51.82,51.32,6054742 "MCD",51.44,"6/11/2007","1:11pm",+0.03,51.47,51.62,50.98,2762114 "MMM",85.53,"6/11/2007","1:11pm",-0.41,85.94,85.98,85.28,1145200 "MO",70.41,"6/11/2007","1:11pm",+0.11,70.25,70.50,69.76,4563257 "MRK",51.12,"6/11/2007","1:11pm",+0.98,50.30,51.13,50.04,5899900 "MSFT",30.17,"6/11/2007","1:16pm",+0.12,30.05,30.25,29.93,22399100 "PFE",26.5275,"6/11/2007","1:11pm",+0.0075,26.50,26.53,26.31,12044278 "PG",63.0995,"6/11/2007","1:11pm",+0.0295,62.80,63.12,62.75,3229446 "T",40.19,"6/11/2007","1:11pm",-0.07,40.20,40.25,39.89,7073875 "UTX",69.97,"6/11/2007","1:11pm",-0.26,69.85,70.20,69.51,1092400 "VZ",43.34,"6/11/2007","1:10pm",+0.27,42.95,43.34,42.88,4847820 "WMT",50.08,"6/11/2007","1:11pm",0.00,49.90,50.12,49.55,6200407 "XOM",83.66,"6/11/2007","1:11pm",+0.98,82.68,83.72,82.35,6490400 "AA",39.56,"6/11/2007","1:16pm",-0.10,39.67,40.18,39.43,2433580 "AIG",71.80,"6/11/2007","1:16pm",+0.27,71.29,71.83,71.15,2440996 "AXP",63.20,"6/11/2007","1:15pm",+0.16,62.79,63.21,62.42,2110030 "BA",97.85,"6/11/2007","1:16pm",-0.34,98.25,98.79,97.59,1590400 "C",53.679,"6/11/2007","1:16pm",+0.349,53.20,53.71,52.81,6103294 "CAT",79.35,"6/11/2007","1:16pm",+0.83,78.32,79.39,78.06,1651052 "DD",50.85,"6/11/2007","1:16pm",-0.28,51.13,51.21,50.59,2111497 "DIS",34.21,"6/11/2007","1:15pm",+0.01,34.28,34.44,34.12,3343750 "GE",37.535,"6/11/2007","1:16pm",+0.215,37.07,37.54,37.05,12385801 "GM",31.45,"6/11/2007","1:16pm",+0.45,31.00,31.62,30.90,7588451 "HD",37.77,"6/11/2007","1:16pm",-0.18,37.78,37.83,37.62,5820967 "HON",57.11,"6/11/2007","1:16pm",-0.27,57.25,57.40,56.91,2057642 "HPQ",46.12,"6/11/2007","1:16pm",+0.42,45.80,46.29,45.46,5922449 "IBM",103.83,"6/11/2007","1:16pm",+0.76,102.87,104.00,102.50,2579600 "INTC",22.06,"6/11/2007","1:21pm",+0.23,21.70,22.08,21.69,23707494 "JNJ",62.64,"6/11/2007","1:16pm",+0.51,62.89,62.89,62.15,4771978 "JPM",50.75,"6/11/2007","1:16pm",+0.34,50.41,50.78,50.05,4349600 "KO",51.73,"6/11/2007","1:16pm",+0.06,51.67,51.82,51.32,6085542 "MCD",51.47,"6/11/2007","1:16pm",+0.06,51.47,51.62,50.98,2786414 "MMM",85.56,"6/11/2007","1:16pm",-0.38,85.94,85.98,85.28,1180100 "MO",70.37,"6/11/2007","1:16pm",+0.07,70.25,70.50,69.76,4636857 "MRK",51.15,"6/11/2007","1:16pm",+1.01,50.30,51.16,50.04,6009400 "MSFT",30.14,"6/11/2007","1:21pm",+0.09,30.05,30.25,29.93,23011740 "PFE",26.51,"6/11/2007","1:16pm",-0.01,26.50,26.54,26.31,12225128 "PG",63.10,"6/11/2007","1:16pm",+0.03,62.80,63.12,62.75,3329546 "T",40.22,"6/11/2007","1:16pm",-0.04,40.20,40.26,39.89,7235875 "UTX",69.95,"6/11/2007","1:16pm",-0.28,69.85,70.20,69.51,1122100 "VZ",43.37,"6/11/2007","1:16pm",+0.30,42.95,43.38,42.88,4953820 "WMT",50.04,"6/11/2007","1:16pm",-0.04,49.90,50.12,49.55,6345707 "XOM",83.58,"6/11/2007","1:16pm",+0.90,82.68,83.72,82.35,6598900 "AA",39.58,"6/11/2007","1:21pm",-0.08,39.67,40.18,39.43,2500980 "AIG",71.90,"6/11/2007","1:21pm",+0.37,71.29,71.89,71.15,2494796 "AXP",63.22,"6/11/2007","1:21pm",+0.18,62.79,63.25,62.42,2135530 "BA",97.81,"6/11/2007","1:20pm",-0.38,98.25,98.79,97.59,1608700 "C",53.76,"6/11/2007","1:21pm",+0.43,53.20,53.77,52.81,6249294 "CAT",79.45,"6/11/2007","1:21pm",+0.93,78.32,79.45,78.06,1689152 "DD",50.89,"6/11/2007","1:21pm",-0.24,51.13,51.21,50.59,2132297 "DIS",34.23,"6/11/2007","1:20pm",+0.03,34.28,34.44,34.12,3370250 "GE",37.53,"6/11/2007","1:21pm",+0.21,37.07,37.56,37.05,12700001 "GM",31.43,"6/11/2007","1:21pm",+0.43,31.00,31.62,30.90,7705751 "HD",37.76,"6/11/2007","1:21pm",-0.19,37.78,37.83,37.62,5879467 "HON",57.17,"6/11/2007","1:21pm",-0.21,57.25,57.40,56.91,2107242 "HPQ",46.17,"6/11/2007","1:21pm",+0.47,45.80,46.29,45.46,5961749 "IBM",103.81,"6/11/2007","1:21pm",+0.74,102.87,104.00,102.50,2608100 "INTC",22.04,"6/11/2007","1:26pm",+0.21,21.70,22.08,21.69,24314782 "JNJ",62.66,"6/11/2007","1:21pm",+0.53,62.89,62.89,62.15,4802578 "JPM",50.83,"6/11/2007","1:21pm",+0.42,50.41,50.84,50.05,4419800 "KO",51.79,"6/11/2007","1:21pm",+0.12,51.67,51.82,51.32,6135742 "MCD",51.48,"6/11/2007","1:21pm",+0.07,51.47,51.62,50.98,2853314 "MMM",85.64,"6/11/2007","1:21pm",-0.30,85.94,85.98,85.28,1258800 "MO",70.41,"6/11/2007","1:21pm",+0.11,70.25,70.50,69.76,4723085 "MRK",51.22,"6/11/2007","1:21pm",+1.08,50.30,51.23,50.04,6148300 "MSFT",30.1301,"6/11/2007","1:25pm",+0.0801,30.05,30.25,29.93,23292696 "PFE",26.50,"6/11/2007","1:21pm",-0.02,26.50,26.54,26.31,12653153 "PG",63.10,"6/11/2007","1:21pm",+0.03,62.80,63.12,62.75,3736946 "T",40.28,"6/11/2007","1:21pm",+0.02,40.20,40.29,39.89,7365200 "UTX",70.02,"6/11/2007","1:21pm",-0.21,69.85,70.20,69.51,1158800 "VZ",43.43,"6/11/2007","1:21pm",+0.36,42.95,43.44,42.88,5053620 "WMT",50.02,"6/11/2007","1:21pm",-0.06,49.90,50.12,49.55,6585307 "XOM",83.66,"6/11/2007","1:21pm",+0.98,82.68,83.72,82.35,6705800 "AA",39.52,"6/11/2007","1:26pm",-0.14,39.67,40.18,39.43,2540480 "AIG",71.85,"6/11/2007","1:26pm",+0.32,71.29,71.90,71.15,2549996 "AXP",63.18,"6/11/2007","1:26pm",+0.14,62.79,63.26,62.42,2157330 "BA",97.71,"6/11/2007","1:26pm",-0.48,98.25,98.79,97.59,1636600 "C",53.72,"6/11/2007","1:26pm",+0.39,53.20,53.77,52.81,6427294 "CAT",79.20,"6/11/2007","1:26pm",+0.68,78.32,79.46,78.06,1768752 "DD",50.86,"6/11/2007","1:26pm",-0.27,51.13,51.21,50.59,2150397 "DIS",34.22,"6/11/2007","1:26pm",+0.02,34.28,34.44,34.12,3496150 "GE",37.50,"6/11/2007","1:26pm",+0.18,37.07,37.56,37.05,12934301 "GM",31.40,"6/11/2007","1:26pm",+0.40,31.00,31.62,30.90,7805351 "HD",37.75,"6/11/2007","1:26pm",-0.20,37.78,37.83,37.62,5995967 "HON",57.20,"6/11/2007","1:26pm",-0.18,57.25,57.40,56.91,2149442 "HPQ",46.14,"6/11/2007","1:26pm",+0.44,45.80,46.29,45.46,6019149 "IBM",103.70,"6/11/2007","1:26pm",+0.63,102.87,104.00,102.50,2638000 "INTC",22.04,"6/11/2007","1:31pm",+0.21,21.70,22.08,21.69,24406300 "JNJ",62.63,"6/11/2007","1:26pm",+0.50,62.89,62.89,62.15,4835178 "JPM",50.81,"6/11/2007","1:26pm",+0.40,50.41,50.84,50.05,4480900 "KO",51.78,"6/11/2007","1:26pm",+0.11,51.67,51.85,51.32,6233852 "MCD",51.44,"6/11/2007","1:26pm",+0.03,51.47,51.62,50.98,2881414 "MMM",85.62,"6/11/2007","1:26pm",-0.32,85.94,85.98,85.28,1282500 "MO",70.36,"6/11/2007","1:26pm",+0.06,70.25,70.50,69.76,4767885 "MRK",51.23,"6/11/2007","1:26pm",+1.09,50.30,51.27,50.04,6334000 "MSFT",30.12,"6/11/2007","1:31pm",+0.07,30.05,30.25,29.93,23578844 "PFE",26.50,"6/11/2007","1:26pm",-0.02,26.50,26.54,26.31,14778927 "PG",63.09,"6/11/2007","1:26pm",+0.02,62.80,63.12,62.75,3928946 "T",40.31,"6/11/2007","1:26pm",+0.05,40.20,40.33,39.89,7505100 "UTX",69.98,"6/11/2007","1:26pm",-0.25,69.85,70.20,69.51,1210900 "VZ",43.40,"6/11/2007","1:26pm",+0.33,42.95,43.45,42.88,5123120 "WMT",49.96,"6/11/2007","1:26pm",-0.12,49.90,50.12,49.55,6687607 "XOM",83.57,"6/11/2007","1:26pm",+0.89,82.68,83.72,82.35,6797200 "AA",39.53,"6/11/2007","1:31pm",-0.13,39.67,40.18,39.43,2572580 "AIG",71.85,"6/11/2007","1:31pm",+0.32,71.29,71.90,71.15,2602596 "AXP",63.26,"6/11/2007","1:31pm",+0.22,62.79,63.26,62.42,2172130 "BA",97.82,"6/11/2007","1:31pm",-0.37,98.25,98.79,97.59,1659000 "C",53.74,"6/11/2007","1:31pm",+0.41,53.20,53.77,52.81,6462694 "CAT",79.20,"6/11/2007","1:31pm",+0.68,78.32,79.46,78.06,1829052 "DD",50.85,"6/11/2007","1:31pm",-0.28,51.13,51.21,50.59,2171097 "DIS",34.24,"6/11/2007","1:31pm",+0.04,34.28,34.44,34.12,3579950 "GE",37.52,"6/11/2007","1:31pm",+0.20,37.07,37.56,37.05,13012501 "GM",31.42,"6/11/2007","1:31pm",+0.42,31.00,31.62,30.90,7927151 "HD",37.76,"6/11/2007","1:31pm",-0.19,37.78,37.83,37.62,6189667 "HON",57.21,"6/11/2007","1:31pm",-0.17,57.25,57.40,56.91,2172142 "HPQ",46.14,"6/11/2007","1:31pm",+0.44,45.80,46.29,45.46,6057949 "IBM",103.63,"6/11/2007","1:31pm",+0.56,102.87,104.00,102.50,2660504 "INTC",22.04,"6/11/2007","1:36pm",+0.21,21.70,22.08,21.69,24689632 "JNJ",62.61,"6/11/2007","1:31pm",+0.48,62.89,62.89,62.15,4868578 "JPM",50.80,"6/11/2007","1:31pm",+0.39,50.41,50.84,50.05,4526800 "KO",51.75,"6/11/2007","1:31pm",+0.08,51.67,51.85,51.32,6294152 "MCD",51.43,"6/11/2007","1:31pm",+0.02,51.47,51.62,50.98,2901114 "MMM",85.59,"6/11/2007","1:31pm",-0.35,85.94,85.98,85.28,1297000 "MO",70.34,"6/11/2007","1:31pm",+0.04,70.25,70.50,69.76,4818885 "MRK",51.24,"6/11/2007","1:31pm",+1.10,50.30,51.27,50.04,6438900 "MSFT",30.125,"6/11/2007","1:36pm",+0.075,30.05,30.25,29.93,23983122 "PFE",26.51,"6/11/2007","1:31pm",-0.01,26.50,26.54,26.31,14886127 "PG",63.08,"6/11/2007","1:31pm",+0.01,62.80,63.13,62.75,3971046 "T",40.31,"6/11/2007","1:31pm",+0.05,40.20,40.33,39.89,7610100 "UTX",70.05,"6/11/2007","1:31pm",-0.18,69.85,70.20,69.51,1239200 "VZ",43.39,"6/11/2007","1:31pm",+0.32,42.95,43.45,42.88,5198120 "WMT",50.00,"6/11/2007","1:31pm",-0.08,49.90,50.12,49.55,6808307 "XOM",83.60,"6/11/2007","1:31pm",+0.92,82.68,83.72,82.35,6858500 "AA",39.54,"6/11/2007","1:36pm",-0.12,39.67,40.18,39.43,2615380 "AIG",71.90,"6/11/2007","1:36pm",+0.37,71.29,71.90,71.15,2648396 "AXP",63.27,"6/11/2007","1:36pm",+0.23,62.79,63.28,62.42,2187930 "BA",97.88,"6/11/2007","1:36pm",-0.31,98.25,98.79,97.59,1700700 "C",53.73,"6/11/2007","1:36pm",+0.40,53.20,53.77,52.81,6558794 "CAT",79.20,"6/11/2007","1:36pm",+0.68,78.32,79.46,78.06,1869352 "DD",50.89,"6/11/2007","1:36pm",-0.24,51.13,51.21,50.59,2184797 "DIS",34.22,"6/11/2007","1:36pm",+0.02,34.28,34.44,34.12,3654250 "GE",37.51,"6/11/2007","1:36pm",+0.19,37.07,37.56,37.05,13175301 "GM",31.45,"6/11/2007","1:36pm",+0.45,31.00,31.62,30.90,8227251 "HD",37.76,"6/11/2007","1:36pm",-0.19,37.78,37.83,37.62,6273567 "HON",57.24,"6/11/2007","1:36pm",-0.14,57.25,57.40,56.91,2203642 "HPQ",46.17,"6/11/2007","1:36pm",+0.47,45.80,46.29,45.46,6121849 "IBM",103.59,"6/11/2007","1:36pm",+0.52,102.87,104.00,102.50,2702104 "INTC",22.02,"6/11/2007","1:41pm",+0.19,21.70,22.08,21.69,25010946 "JNJ",62.60,"6/11/2007","1:36pm",+0.47,62.89,62.89,62.15,4923778 "JPM",50.77,"6/11/2007","1:36pm",+0.36,50.41,50.84,50.05,4660900 "KO",51.79,"6/11/2007","1:36pm",+0.12,51.67,51.85,51.32,6365852 "MCD",51.42,"6/11/2007","1:36pm",+0.01,51.47,51.62,50.98,2935214 "MMM",85.57,"6/11/2007","1:36pm",-0.37,85.94,85.98,85.28,1313500 "MO",70.37,"6/11/2007","1:36pm",+0.07,70.25,70.50,69.76,4877785 "MRK",51.24,"6/11/2007","1:36pm",+1.10,50.30,51.28,50.04,6515500 "MSFT",30.12,"6/11/2007","1:41pm",+0.07,30.05,30.25,29.93,24255316 "PFE",26.491,"6/11/2007","1:36pm",-0.029,26.50,26.54,26.31,15109927 "PG",63.12,"6/11/2007","1:36pm",+0.05,62.80,63.13,62.75,4002446 "T",40.30,"6/11/2007","1:36pm",+0.04,40.20,40.33,39.89,7712400 "UTX",70.10,"6/11/2007","1:36pm",-0.13,69.85,70.20,69.51,1247400 "VZ",43.40,"6/11/2007","1:36pm",+0.33,42.95,43.45,42.88,5341720 "WMT",49.95,"6/11/2007","1:36pm",-0.13,49.90,50.12,49.55,6914407 "XOM",83.56,"6/11/2007","1:36pm",+0.88,82.68,83.72,82.35,6917800 "AA",39.53,"6/11/2007","1:41pm",-0.13,39.67,40.18,39.43,2647380 "AIG",71.87,"6/11/2007","1:41pm",+0.34,71.29,71.90,71.15,2686796 "AXP",63.28,"6/11/2007","1:41pm",+0.24,62.79,63.32,62.42,2205330 "BA",97.82,"6/11/2007","1:41pm",-0.37,98.25,98.79,97.59,1725200 "C",53.67,"6/11/2007","1:41pm",+0.34,53.20,53.77,52.81,6740594 "CAT",79.11,"6/11/2007","1:41pm",+0.59,78.32,79.46,78.06,1880652 "DD",50.861,"6/11/2007","1:41pm",-0.269,51.13,51.21,50.59,2199797 "DIS",34.21,"6/11/2007","1:41pm",+0.01,34.28,34.44,34.12,3734350 "GE",37.52,"6/11/2007","1:41pm",+0.20,37.07,37.56,37.05,13566001 "GM",31.47,"6/11/2007","1:41pm",+0.47,31.00,31.62,30.90,8330251 "HD",37.75,"6/11/2007","1:41pm",-0.20,37.78,37.83,37.62,6627167 "HON",57.17,"6/11/2007","1:41pm",-0.21,57.25,57.40,56.91,2246142 "HPQ",46.14,"6/11/2007","1:41pm",+0.44,45.80,46.29,45.46,6190049 "IBM",103.54,"6/11/2007","1:41pm",+0.47,102.87,104.00,102.50,2715404 "INTC",22.02,"6/11/2007","1:46pm",+0.19,21.70,22.08,21.69,25285316 "JNJ",62.54,"6/11/2007","1:41pm",+0.41,62.89,62.89,62.15,5098378 "JPM",50.77,"6/11/2007","1:41pm",+0.36,50.41,50.84,50.05,5025900 "KO",51.725,"6/11/2007","1:41pm",+0.055,51.67,51.85,51.32,6390552 "MCD",51.391,"6/11/2007","1:41pm",-0.019,51.47,51.62,50.98,2972614 "MMM",85.58,"6/11/2007","1:41pm",-0.36,85.94,85.98,85.28,1337300 "MO",70.29,"6/11/2007","1:41pm",-0.01,70.25,70.50,69.76,4907285 "MRK",51.21,"6/11/2007","1:41pm",+1.07,50.30,51.28,50.04,6587800 "MSFT",30.13,"6/11/2007","1:46pm",+0.08,30.05,30.25,29.93,24497374 "PFE",26.49,"6/11/2007","1:41pm",-0.03,26.50,26.54,26.31,15538627 "PG",63.13,"6/11/2007","1:41pm",+0.06,62.80,63.14,62.75,4048446 "T",40.31,"6/11/2007","1:41pm",+0.05,40.20,40.34,39.89,7832500 "UTX",70.07,"6/11/2007","1:41pm",-0.16,69.85,70.20,69.51,1264700 "VZ",43.38,"6/11/2007","1:41pm",+0.31,42.95,43.45,42.88,5395220 "WMT",49.90,"6/11/2007","1:41pm",-0.18,49.90,50.12,49.55,7009107 "XOM",83.53,"6/11/2007","1:41pm",+0.85,82.68,83.72,82.35,7007700 "AA",39.55,"6/11/2007","1:46pm",-0.11,39.67,40.18,39.43,2672280 "AIG",71.88,"6/11/2007","1:46pm",+0.35,71.29,71.90,71.15,2724196 "AXP",63.28,"6/11/2007","1:46pm",+0.24,62.79,63.32,62.42,2214330 "BA",97.98,"6/11/2007","1:46pm",-0.21,98.25,98.79,97.59,1773500 "C",53.61,"6/11/2007","1:46pm",+0.28,53.20,53.77,52.81,6867594 "CAT",79.07,"6/11/2007","1:46pm",+0.55,78.32,79.46,78.06,1923652 "DD",50.86,"6/11/2007","1:46pm",-0.27,51.13,51.21,50.59,2221297 "DIS",34.201,"6/11/2007","1:46pm",+0.001,34.28,34.44,34.12,3795650 "GE",37.52,"6/11/2007","1:46pm",+0.20,37.07,37.56,37.05,13711301 "GM",31.50,"6/11/2007","1:46pm",+0.50,31.00,31.62,30.90,8678851 "HD",37.77,"6/11/2007","1:46pm",-0.18,37.78,37.83,37.62,6687867 "HON",57.162,"6/11/2007","1:46pm",-0.218,57.25,57.40,56.91,2293142 "HPQ",46.13,"6/11/2007","1:46pm",+0.43,45.80,46.29,45.46,6259749 "IBM",103.57,"6/11/2007","1:46pm",+0.50,102.87,104.00,102.50,2740104 "INTC",22.02,"6/11/2007","1:51pm",+0.19,21.70,22.08,21.69,25632092 "JNJ",62.54,"6/11/2007","1:46pm",+0.41,62.89,62.89,62.15,5147978 "JPM",50.80,"6/11/2007","1:46pm",+0.39,50.41,50.84,50.05,5114400 "KO",51.75,"6/11/2007","1:46pm",+0.08,51.67,51.85,51.32,6441352 "MCD",51.40,"6/11/2007","1:46pm",-0.01,51.47,51.62,50.98,3049814 "MMM",85.59,"6/11/2007","1:46pm",-0.35,85.94,85.98,85.28,1347600 "MO",70.28,"6/11/2007","1:46pm",-0.02,70.25,70.50,69.76,4959585 "MRK",51.18,"6/11/2007","1:46pm",+1.04,50.30,51.28,50.04,6651700 "MSFT",30.11,"6/11/2007","1:51pm",+0.06,30.05,30.25,29.93,24742128 "PFE",26.44,"6/11/2007","1:46pm",-0.08,26.50,26.54,26.31,15813727 "PG",63.12,"6/11/2007","1:46pm",+0.05,62.80,63.14,62.75,4099546 "T",40.3073,"6/11/2007","1:46pm",+0.0473,40.20,40.34,39.89,7901800 "UTX",70.10,"6/11/2007","1:46pm",-0.13,69.85,70.20,69.51,1284300 "VZ",43.42,"6/11/2007","1:46pm",+0.35,42.95,43.45,42.88,5473215 "WMT",49.94,"6/11/2007","1:46pm",-0.14,49.90,50.12,49.55,7155307 "XOM",83.66,"6/11/2007","1:46pm",+0.98,82.68,83.72,82.35,7157900 "AA",39.55,"6/11/2007","1:51pm",-0.11,39.67,40.18,39.43,2705680 "AIG",71.84,"6/11/2007","1:51pm",+0.31,71.29,71.90,71.15,2760896 "AXP",63.245,"6/11/2007","1:51pm",+0.205,62.79,63.32,62.42,2226930 "BA",97.96,"6/11/2007","1:51pm",-0.23,98.25,98.79,97.59,1793700 "C",53.63,"6/11/2007","1:51pm",+0.30,53.20,53.77,52.81,6926294 "CAT",79.04,"6/11/2007","1:51pm",+0.52,78.32,79.46,78.06,1958652 "DD",50.83,"6/11/2007","1:51pm",-0.30,51.13,51.21,50.59,2232897 "DIS",34.20,"6/11/2007","1:50pm",0.00,34.28,34.44,34.12,3823150 "GE",37.52,"6/11/2007","1:51pm",+0.20,37.07,37.56,37.05,13797801 "GM",31.47,"6/11/2007","1:51pm",+0.47,31.00,31.62,30.90,8801851 "HD",37.77,"6/11/2007","1:51pm",-0.18,37.78,37.83,37.62,6761167 "HON",57.13,"6/11/2007","1:51pm",-0.25,57.25,57.40,56.91,2327542 "HPQ",46.12,"6/11/2007","1:51pm",+0.42,45.80,46.29,45.46,6316649 "IBM",103.55,"6/11/2007","1:51pm",+0.48,102.87,104.00,102.50,2777904 "INTC",22.03,"6/11/2007","1:56pm",+0.20,21.70,22.08,21.69,26065954 "JNJ",62.53,"6/11/2007","1:51pm",+0.40,62.89,62.89,62.15,5210478 "JPM",50.81,"6/11/2007","1:51pm",+0.40,50.41,50.84,50.05,5222100 "KO",51.75,"6/11/2007","1:51pm",+0.08,51.67,51.85,51.32,6558652 "MCD",51.39,"6/11/2007","1:51pm",-0.02,51.47,51.62,50.98,3074814 "MMM",85.548,"6/11/2007","1:51pm",-0.392,85.94,85.98,85.28,1372000 "MO",70.27,"6/11/2007","1:50pm",-0.03,70.25,70.50,69.76,4986985 "MRK",51.16,"6/11/2007","1:51pm",+1.02,50.30,51.28,50.04,6821600 "MSFT",30.13,"6/11/2007","1:56pm",+0.08,30.05,30.25,29.93,24970772 "PFE",26.448,"6/11/2007","1:51pm",-0.072,26.50,26.54,26.31,16248877 "PG",63.13,"6/11/2007","1:51pm",+0.06,62.80,63.14,62.75,4157646 "T",40.31,"6/11/2007","1:51pm",+0.05,40.20,40.34,39.89,8018500 "UTX",70.15,"6/11/2007","1:51pm",-0.08,69.85,70.20,69.51,1321400 "VZ",43.44,"6/11/2007","1:51pm",+0.37,42.95,43.45,42.88,5538015 "WMT",49.86,"6/11/2007","1:51pm",-0.22,49.90,50.12,49.55,7285407 "XOM",83.56,"6/11/2007","1:51pm",+0.88,82.68,83.72,82.35,7364600 "AA",39.53,"6/11/2007","1:56pm",-0.13,39.67,40.18,39.43,2721380 "AIG",71.7918,"6/11/2007","1:56pm",+0.2618,71.29,71.90,71.15,2817696 "AXP",63.20,"6/11/2007","1:56pm",+0.16,62.79,63.32,62.42,2237230 "BA",97.89,"6/11/2007","1:56pm",-0.30,98.25,98.79,97.59,1808600 "C",53.60,"6/11/2007","1:56pm",+0.27,53.20,53.77,52.81,7078394 "CAT",79.02,"6/11/2007","1:56pm",+0.50,78.32,79.46,78.06,1980452 "DD",50.83,"6/11/2007","1:56pm",-0.30,51.13,51.21,50.59,2245697 "DIS",34.195,"6/11/2007","1:56pm",-0.005,34.28,34.44,34.12,3863950 "GE",37.52,"6/11/2007","1:56pm",+0.20,37.07,37.56,37.05,13986001 "GM",31.49,"6/11/2007","1:56pm",+0.49,31.00,31.62,30.90,8849851 "HD",37.75,"6/11/2007","1:56pm",-0.20,37.78,37.83,37.62,6819267 "HON",57.11,"6/11/2007","1:56pm",-0.27,57.25,57.40,56.91,2354542 "HPQ",46.122,"6/11/2007","1:56pm",+0.422,45.80,46.29,45.46,6380249 "IBM",103.53,"6/11/2007","1:56pm",+0.46,102.87,104.00,102.50,2802904 "INTC",22.01,"6/11/2007","2:01pm",+0.18,21.70,22.08,21.69,26445260 "JNJ",62.52,"6/11/2007","1:56pm",+0.39,62.89,62.89,62.15,5265678 "JPM",50.77,"6/11/2007","1:56pm",+0.36,50.41,50.84,50.05,5291300 "KO",51.7619,"6/11/2007","1:56pm",+0.0919,51.67,51.85,51.32,6606152 "MCD",51.36,"6/11/2007","1:56pm",-0.05,51.47,51.62,50.98,3110114 "MMM",85.53,"6/11/2007","1:56pm",-0.41,85.94,85.98,85.28,1402100 "MO",70.27,"6/11/2007","1:56pm",-0.03,70.25,70.50,69.76,5016285 "MRK",51.11,"6/11/2007","1:56pm",+0.97,50.30,51.28,50.04,6899000 "MSFT",30.12,"6/11/2007","2:01pm",+0.07,30.05,30.25,29.93,25277544 "PFE",26.44,"6/11/2007","1:56pm",-0.08,26.50,26.54,26.31,16455677 "PG",63.13,"6/11/2007","1:56pm",+0.06,62.80,63.15,62.75,4256946 "T",40.28,"6/11/2007","1:56pm",+0.02,40.20,40.34,39.89,8132800 "UTX",70.154,"6/11/2007","1:56pm",-0.076,69.85,70.20,69.51,1344300 "VZ",43.47,"6/11/2007","1:56pm",+0.40,42.95,43.47,42.88,5621815 "WMT",49.88,"6/11/2007","1:56pm",-0.20,49.90,50.12,49.55,7420907 "XOM",83.52,"6/11/2007","1:56pm",+0.84,82.68,83.72,82.35,7459300 "AA",39.491,"6/11/2007","2:01pm",-0.169,39.67,40.18,39.43,2763280 "AIG",71.77,"6/11/2007","2:01pm",+0.24,71.29,71.90,71.15,2879896 "AXP",63.17,"6/11/2007","2:01pm",+0.13,62.79,63.32,62.42,2261630 "BA",97.83,"6/11/2007","2:01pm",-0.36,98.25,98.79,97.59,1833100 "C",53.55,"6/11/2007","2:01pm",+0.22,53.20,53.77,52.81,7307094 "CAT",79.00,"6/11/2007","2:01pm",+0.48,78.32,79.46,78.06,2025552 "DD",50.85,"6/11/2007","2:01pm",-0.28,51.13,51.21,50.59,2277197 "DIS",34.20,"6/11/2007","2:01pm",0.00,34.28,34.44,34.12,3931450 "GE",37.51,"6/11/2007","2:01pm",+0.19,37.07,37.56,37.05,14260601 "GM",31.50,"6/11/2007","2:01pm",+0.50,31.00,31.62,30.90,8948751 "HD",37.75,"6/11/2007","2:01pm",-0.20,37.78,37.83,37.62,7227367 "HON",57.13,"6/11/2007","2:01pm",-0.25,57.25,57.40,56.91,2422142 "HPQ",46.08,"6/11/2007","2:01pm",+0.38,45.80,46.29,45.46,6426249 "IBM",103.51,"6/11/2007","2:01pm",+0.44,102.87,104.00,102.50,2839204 "INTC",22.02,"6/11/2007","2:06pm",+0.19,21.70,22.08,21.69,26751104 "JNJ",62.50,"6/11/2007","2:01pm",+0.37,62.89,62.89,62.15,5350858 "JPM",50.70,"6/11/2007","2:01pm",+0.29,50.41,50.84,50.05,5396400 "KO",51.76,"6/11/2007","2:01pm",+0.09,51.67,51.85,51.32,6660752 "MCD",51.3727,"6/11/2007","2:01pm",-0.0373,51.47,51.62,50.98,3168414 "MMM",85.58,"6/11/2007","2:01pm",-0.36,85.94,85.98,85.28,1440600 "MO",70.24,"6/11/2007","2:01pm",-0.06,70.25,70.50,69.76,5077085 "MRK",51.09,"6/11/2007","2:01pm",+0.95,50.30,51.28,50.04,6995800 "MSFT",30.12,"6/11/2007","2:06pm",+0.07,30.05,30.25,29.93,25466984 "PFE",26.44,"6/11/2007","2:01pm",-0.08,26.50,26.54,26.31,16750477 "PG",63.12,"6/11/2007","2:01pm",+0.05,62.80,63.15,62.75,4334646 "T",40.27,"6/11/2007","2:01pm",+0.01,40.20,40.34,39.89,8457200 "UTX",70.11,"6/11/2007","2:01pm",-0.12,69.85,70.20,69.51,1362600 "VZ",43.46,"6/11/2007","2:01pm",+0.39,42.95,43.47,42.88,5698015 "WMT",49.79,"6/11/2007","2:01pm",-0.29,49.90,50.12,49.55,7569507 "XOM",83.43,"6/11/2007","2:01pm",+0.75,82.68,83.72,82.35,7716900 "AA",39.50,"6/11/2007","2:06pm",-0.16,39.67,40.18,39.43,2841280 "AIG",71.85,"6/11/2007","2:06pm",+0.32,71.29,71.90,71.15,2945096 "AXP",63.23,"6/11/2007","2:06pm",+0.19,62.79,63.32,62.42,2280130 "BA",97.92,"6/11/2007","2:06pm",-0.27,98.25,98.79,97.59,1847000 "C",53.56,"6/11/2007","2:06pm",+0.23,53.20,53.77,52.81,7466394 "CAT",79.11,"6/11/2007","2:06pm",+0.59,78.32,79.46,78.06,2048252 "DD",50.86,"6/11/2007","2:06pm",-0.27,51.13,51.21,50.59,2309597 "DIS",34.19,"6/11/2007","2:06pm",-0.01,34.28,34.44,34.12,3986950 "GE",37.51,"6/11/2007","2:06pm",+0.19,37.07,37.56,37.05,14444801 "GM",31.50,"6/11/2007","2:06pm",+0.50,31.00,31.62,30.90,9108451 "HD",37.75,"6/11/2007","2:06pm",-0.20,37.78,37.83,37.62,7407867 "HON",57.11,"6/11/2007","2:06pm",-0.27,57.25,57.40,56.91,2462942 "HPQ",46.08,"6/11/2007","2:06pm",+0.38,45.80,46.29,45.46,6509849 "IBM",103.54,"6/11/2007","2:06pm",+0.47,102.87,104.00,102.50,2861904 "INTC",22.03,"6/11/2007","2:11pm",+0.20,21.70,22.08,21.69,26974348 "JNJ",62.50,"6/11/2007","2:06pm",+0.37,62.89,62.89,62.15,5513358 "JPM",50.65,"6/11/2007","2:06pm",+0.24,50.41,50.84,50.05,5565600 "KO",51.77,"6/11/2007","2:06pm",+0.10,51.67,51.85,51.32,6687652 "MCD",51.41,"6/11/2007","2:06pm",0.00,51.47,51.62,50.98,3209614 "MMM",85.53,"6/11/2007","2:06pm",-0.41,85.94,85.98,85.28,1470200 "MO",70.29,"6/11/2007","2:06pm",-0.01,70.25,70.50,69.76,5107185 "MRK",51.05,"6/11/2007","2:06pm",+0.91,50.30,51.28,50.04,7162100 "MSFT",30.13,"6/11/2007","2:11pm",+0.08,30.05,30.25,29.93,25965886 "PFE",26.44,"6/11/2007","2:06pm",-0.08,26.50,26.54,26.31,17179996 "PG",63.14,"6/11/2007","2:06pm",+0.07,62.80,63.15,62.75,4427046 "T",40.26,"6/11/2007","2:06pm",0.00,40.20,40.34,39.89,8620800 "UTX",70.12,"6/11/2007","2:06pm",-0.11,69.85,70.20,69.51,1386800 "VZ",43.44,"6/11/2007","2:06pm",+0.37,42.95,43.47,42.88,5829840 "WMT",49.83,"6/11/2007","2:06pm",-0.25,49.90,50.12,49.55,7695307 "XOM",83.44,"6/11/2007","2:06pm",+0.76,82.68,83.72,82.35,7916100 "AA",39.53,"6/11/2007","2:11pm",-0.13,39.67,40.18,39.43,2880880 "AIG",71.98,"6/11/2007","2:11pm",+0.45,71.29,71.99,71.15,3103496 "AXP",63.38,"6/11/2007","2:11pm",+0.34,62.79,63.39,62.42,2308630 "BA",97.93,"6/11/2007","2:11pm",-0.26,98.25,98.79,97.59,1881100 "C",53.72,"6/11/2007","2:11pm",+0.39,53.20,53.77,52.81,7588294 "CAT",79.19,"6/11/2007","2:11pm",+0.67,78.32,79.46,78.06,2131352 "DD",50.90,"6/11/2007","2:11pm",-0.23,51.13,51.21,50.59,2340097 "DIS",34.21,"6/11/2007","2:11pm",+0.01,34.28,34.44,34.12,4058750 "GE",37.57,"6/11/2007","2:11pm",+0.25,37.07,37.57,37.05,14734801 "GM",31.57,"6/11/2007","2:11pm",+0.57,31.00,31.62,30.90,9594251 "HD",37.76,"6/11/2007","2:11pm",-0.19,37.78,37.83,37.62,7474967 "HON",57.19,"6/11/2007","2:11pm",-0.19,57.25,57.40,56.91,2592542 "HPQ",46.15,"6/11/2007","2:11pm",+0.45,45.80,46.29,45.46,6633249 "IBM",103.59,"6/11/2007","2:11pm",+0.52,102.87,104.00,102.50,2884504 "INTC",22.01,"6/11/2007","2:16pm",+0.18,21.70,22.08,21.69,27482520 "JNJ",62.53,"6/11/2007","2:11pm",+0.40,62.89,62.89,62.15,5618158 "JPM",50.74,"6/11/2007","2:11pm",+0.33,50.41,50.84,50.05,5728500 "KO",51.79,"6/11/2007","2:11pm",+0.12,51.67,51.85,51.32,6761552 "MCD",51.50,"6/11/2007","2:11pm",+0.09,51.47,51.62,50.98,3260114 "MMM",85.57,"6/11/2007","2:11pm",-0.37,85.94,85.98,85.28,1508200 "MO",70.34,"6/11/2007","2:11pm",+0.04,70.25,70.50,69.76,5156485 "MRK",51.21,"6/11/2007","2:11pm",+1.07,50.30,51.28,50.04,7355400 "MSFT",30.16,"6/11/2007","2:16pm",+0.11,30.05,30.25,29.93,26411778 "PFE",26.48,"6/11/2007","2:11pm",-0.04,26.50,26.54,26.31,17302396 "PG",63.1819,"6/11/2007","2:11pm",+0.1119,62.80,63.19,62.75,4533846 "T",40.25,"6/11/2007","2:11pm",-0.01,40.20,40.34,39.89,8839900 "UTX",70.20,"6/11/2007","2:11pm",-0.03,69.85,70.20,69.51,1410200 "VZ",43.49,"6/11/2007","2:11pm",+0.42,42.95,43.49,42.88,5904936 "WMT",49.882,"6/11/2007","2:11pm",-0.198,49.90,50.12,49.55,7831607 "XOM",83.51,"6/11/2007","2:11pm",+0.83,82.68,83.72,82.35,8147200 "AA",39.49,"6/11/2007","2:16pm",-0.17,39.67,40.18,39.43,2953380 "AIG",71.90,"6/11/2007","2:16pm",+0.37,71.29,72.03,71.15,3208796 "AXP",63.31,"6/11/2007","2:16pm",+0.27,62.79,63.42,62.42,2337730 "BA",97.92,"6/11/2007","2:16pm",-0.27,98.25,98.79,97.59,1921100 "C",53.65,"6/11/2007","2:16pm",+0.32,53.20,53.77,52.81,7685194 "CAT",79.08,"6/11/2007","2:16pm",+0.56,78.32,79.46,78.06,2163952 "DD",50.86,"6/11/2007","2:16pm",-0.27,51.13,51.21,50.59,2355597 "DIS",34.20,"6/11/2007","2:16pm",0.00,34.28,34.44,34.12,4227650 "GE",37.54,"6/11/2007","2:16pm",+0.22,37.07,37.61,37.05,15329801 "GM",31.63,"6/11/2007","2:16pm",+0.63,31.00,31.64,30.90,9900251 "HD",37.75,"6/11/2007","2:16pm",-0.20,37.78,37.83,37.62,7614667 "HON",57.16,"6/11/2007","2:16pm",-0.22,57.25,57.40,56.91,2626742 "HPQ",46.14,"6/11/2007","2:16pm",+0.44,45.80,46.29,45.46,6705149 "IBM",103.48,"6/11/2007","2:16pm",+0.41,102.87,104.00,102.50,2932004 "INTC",22.01,"6/11/2007","2:21pm",+0.18,21.70,22.08,21.69,27617028 "JNJ",62.49,"6/11/2007","2:16pm",+0.36,62.89,62.89,62.15,5748158 "JPM",50.71,"6/11/2007","2:16pm",+0.30,50.41,50.84,50.05,5968400 "KO",51.75,"6/11/2007","2:16pm",+0.08,51.67,51.85,51.32,6798052 "MCD",51.40,"6/11/2007","2:16pm",-0.01,51.47,51.62,50.98,3310514 "MMM",85.53,"6/11/2007","2:16pm",-0.41,85.94,85.98,85.28,1531600 "MO",70.33,"6/11/2007","2:16pm",+0.03,70.25,70.50,69.76,5204085 "MRK",51.17,"6/11/2007","2:16pm",+1.03,50.30,51.28,50.04,7421000 "MSFT",30.18,"6/11/2007","2:21pm",+0.13,30.05,30.25,29.93,26830224 "PFE",26.46,"6/11/2007","2:16pm",-0.06,26.50,26.54,26.31,17549496 "PG",63.18,"6/11/2007","2:16pm",+0.11,62.80,63.20,62.75,4586346 "T",40.30,"6/11/2007","2:16pm",+0.04,40.20,40.34,39.89,9022910 "UTX",70.192,"6/11/2007","2:16pm",-0.038,69.85,70.25,69.51,1444200 "VZ",43.52,"6/11/2007","2:16pm",+0.45,42.95,43.535,42.88,6098036 "WMT",49.85,"6/11/2007","2:16pm",-0.23,49.90,50.12,49.55,7900607 "XOM",83.37,"6/11/2007","2:16pm",+0.69,82.68,83.85,82.35,8845100 "AA",39.46,"6/11/2007","2:21pm",-0.20,39.67,40.18,39.43,3029880 "AIG",71.92,"6/11/2007","2:21pm",+0.39,71.29,72.03,71.15,3245196 "AXP",63.35,"6/11/2007","2:21pm",+0.31,62.79,63.42,62.42,2353430 "BA",97.97,"6/11/2007","2:21pm",-0.22,98.25,98.79,97.59,1944500 "C",53.69,"6/11/2007","2:21pm",+0.36,53.20,53.77,52.81,7757194 "CAT",79.12,"6/11/2007","2:21pm",+0.60,78.32,79.46,78.06,2214452 "DD",50.899,"6/11/2007","2:21pm",-0.231,51.13,51.21,50.59,2373297 "DIS",34.23,"6/11/2007","2:21pm",+0.03,34.28,34.44,34.12,4262150 "GE",37.52,"6/11/2007","2:21pm",+0.20,37.07,37.61,37.05,15596301 "GM",31.68,"6/11/2007","2:21pm",+0.68,31.00,31.69,30.90,10286001 "HD",37.7618,"6/11/2007","2:21pm",-0.1882,37.78,37.83,37.62,7739967 "HON",57.28,"6/11/2007","2:21pm",-0.10,57.25,57.40,56.91,2699742 "HPQ",46.16,"6/11/2007","2:21pm",+0.46,45.80,46.29,45.46,6773849 "IBM",103.47,"6/11/2007","2:21pm",+0.40,102.87,104.00,102.50,2958304 "INTC",22.008,"6/11/2007","2:26pm",+0.178,21.70,22.08,21.69,28003456 "JNJ",62.53,"6/11/2007","2:21pm",+0.40,62.89,62.89,62.15,5814358 "JPM",50.721,"6/11/2007","2:21pm",+0.311,50.41,50.84,50.05,6057700 "KO",51.77,"6/11/2007","2:21pm",+0.10,51.67,51.85,51.32,6832552 "MCD",51.37,"6/11/2007","2:21pm",-0.04,51.47,51.62,50.98,3514114 "MMM",85.58,"6/11/2007","2:21pm",-0.36,85.94,85.98,85.28,1584600 "MO",70.39,"6/11/2007","2:21pm",+0.09,70.25,70.50,69.76,5237185 "MRK",51.25,"6/11/2007","2:21pm",+1.11,50.30,51.28,50.04,7541700 "MSFT",30.18,"6/11/2007","2:26pm",+0.13,30.05,30.25,29.93,27263748 "PFE",26.45,"6/11/2007","2:21pm",-0.07,26.50,26.54,26.31,17743896 "PG",63.17,"6/11/2007","2:21pm",+0.10,62.80,63.20,62.75,4649246 "T",40.36,"6/11/2007","2:21pm",+0.10,40.20,40.37,39.89,9204010 "UTX",70.20,"6/11/2007","2:21pm",-0.03,69.85,70.25,69.51,1518400 "VZ",43.57,"6/11/2007","2:21pm",+0.50,42.95,43.58,42.88,6405057 "WMT",49.84,"6/11/2007","2:21pm",-0.24,49.90,50.12,49.55,7979707 "XOM",83.46,"6/11/2007","2:21pm",+0.78,82.68,83.85,82.35,9131100 "AA",39.46,"6/11/2007","2:26pm",-0.20,39.67,40.18,39.43,3106580 "AIG",71.92,"6/11/2007","2:26pm",+0.39,71.29,72.03,71.15,3332596 "AXP",63.34,"6/11/2007","2:26pm",+0.30,62.79,63.42,62.42,2372530 "BA",97.92,"6/11/2007","2:26pm",-0.27,98.25,98.79,97.59,1972600 "C",53.68,"6/11/2007","2:26pm",+0.35,53.20,53.77,52.81,7862194 "CAT",79.06,"6/11/2007","2:26pm",+0.54,78.32,79.46,78.06,2621452 "DD",50.85,"6/11/2007","2:26pm",-0.28,51.13,51.21,50.59,2400597 "DIS",34.21,"6/11/2007","2:26pm",+0.01,34.28,34.44,34.12,4329650 "GE",37.48,"6/11/2007","2:26pm",+0.16,37.07,37.61,37.05,15908401 "GM",31.76,"6/11/2007","2:26pm",+0.76,31.00,31.77,30.90,10673201 "HD",37.76,"6/11/2007","2:26pm",-0.19,37.78,37.83,37.62,7797367 "HON",57.23,"6/11/2007","2:26pm",-0.15,57.25,57.40,56.91,2798642 "HPQ",46.16,"6/11/2007","2:26pm",+0.46,45.80,46.29,45.46,6850349 "IBM",103.52,"6/11/2007","2:26pm",+0.45,102.87,104.00,102.50,2994204 "INTC",22.01,"6/11/2007","2:31pm",+0.18,21.70,22.08,21.69,28166784 "JNJ",62.52,"6/11/2007","2:26pm",+0.39,62.89,62.89,62.15,5858958 "JPM",50.72,"6/11/2007","2:26pm",+0.31,50.41,50.84,50.05,6141900 "KO",51.80,"6/11/2007","2:26pm",+0.13,51.67,51.85,51.32,6873652 "MCD",51.37,"6/11/2007","2:26pm",-0.04,51.47,51.62,50.98,3549714 "MMM",85.55,"6/11/2007","2:26pm",-0.39,85.94,85.98,85.28,1616200 "MO",70.3611,"6/11/2007","2:26pm",+0.0611,70.25,70.50,69.76,5288885 "MRK",51.26,"6/11/2007","2:26pm",+1.12,50.30,51.28,50.04,7641900 "MSFT",30.18,"6/11/2007","2:31pm",+0.13,30.05,30.25,29.93,27477918 "PFE",26.45,"6/11/2007","2:26pm",-0.07,26.50,26.54,26.31,17889796 "PG",63.13,"6/11/2007","2:26pm",+0.06,62.80,63.20,62.75,4759446 "T",40.43,"6/11/2007","2:26pm",+0.17,40.20,40.47,39.89,9441710 "UTX",70.15,"6/11/2007","2:26pm",-0.08,69.85,70.25,69.51,1552400 "VZ",43.58,"6/11/2007","2:26pm",+0.51,42.95,43.61,42.88,6495057 "WMT",49.82,"6/11/2007","2:26pm",-0.26,49.90,50.12,49.55,8014807 "XOM",83.40,"6/11/2007","2:26pm",+0.72,82.68,83.85,82.35,9250000 "AA",39.43,"6/11/2007","2:31pm",-0.23,39.67,40.18,39.42,3146680 "AIG",71.88,"6/11/2007","2:31pm",+0.35,71.29,72.03,71.15,3377696 "AXP",63.30,"6/11/2007","2:31pm",+0.26,62.79,63.42,62.42,2396630 "BA",97.83,"6/11/2007","2:31pm",-0.36,98.25,98.79,97.59,1992300 "C",53.64,"6/11/2007","2:31pm",+0.31,53.20,53.77,52.81,7963894 "CAT",79.00,"6/11/2007","2:31pm",+0.48,78.32,79.46,78.06,2657252 "DD",50.85,"6/11/2007","2:31pm",-0.28,51.13,51.21,50.59,2419197 "DIS",34.20,"6/11/2007","2:31pm",0.00,34.28,34.44,34.12,4416950 "GE",37.48,"6/11/2007","2:31pm",+0.16,37.07,37.61,37.05,16180901 "GM",31.74,"6/11/2007","2:31pm",+0.74,31.00,31.77,30.90,10859101 "HD",37.76,"6/11/2007","2:31pm",-0.19,37.78,37.83,37.62,8074667 "HON",57.20,"6/11/2007","2:31pm",-0.18,57.25,57.40,56.91,2836642 "HPQ",46.15,"6/11/2007","2:31pm",+0.45,45.80,46.29,45.46,6906449 "IBM",103.46,"6/11/2007","2:31pm",+0.39,102.87,104.00,102.50,3014104 "INTC",22.01,"6/11/2007","2:36pm",+0.18,21.70,22.08,21.69,28623660 "JNJ",62.48,"6/11/2007","2:31pm",+0.35,62.89,62.89,62.15,5916458 "JPM",50.68,"6/11/2007","2:31pm",+0.27,50.41,50.84,50.05,6212400 "KO",51.75,"6/11/2007","2:31pm",+0.08,51.67,51.85,51.32,6921352 "MCD",51.31,"6/11/2007","2:31pm",-0.10,51.47,51.62,50.98,3570814 "MMM",85.56,"6/11/2007","2:31pm",-0.38,85.94,85.98,85.28,1632000 "MO",70.34,"6/11/2007","2:31pm",+0.04,70.25,70.50,69.76,5316285 "MRK",51.25,"6/11/2007","2:31pm",+1.11,50.30,51.35,50.04,7816300 "MSFT",30.18,"6/11/2007","2:36pm",+0.13,30.05,30.25,29.93,27776860 "PFE",26.43,"6/11/2007","2:31pm",-0.09,26.50,26.54,26.31,18072696 "PG",63.09,"6/11/2007","2:31pm",+0.02,62.80,63.20,62.75,4849346 "T",40.37,"6/11/2007","2:31pm",+0.11,40.20,40.47,39.89,9632910 "UTX",70.15,"6/11/2007","2:31pm",-0.08,69.85,70.25,69.51,1566500 "VZ",43.53,"6/11/2007","2:31pm",+0.46,42.95,43.61,42.88,6607457 "WMT",49.77,"6/11/2007","2:31pm",-0.31,49.90,50.12,49.55,8080507 "XOM",83.35,"6/11/2007","2:31pm",+0.67,82.68,83.85,82.35,9365400 "AA",39.39,"6/11/2007","2:36pm",-0.27,39.67,40.18,39.38,3233280 "AIG",71.87,"6/11/2007","2:36pm",+0.34,71.29,72.03,71.15,3455896 "AXP",63.22,"6/11/2007","2:36pm",+0.18,62.79,63.42,62.42,2427730 "BA",97.77,"6/11/2007","2:36pm",-0.42,98.25,98.79,97.59,2005500 "C",53.63,"6/11/2007","2:36pm",+0.30,53.20,53.77,52.81,8024794 "CAT",79.01,"6/11/2007","2:36pm",+0.49,78.32,79.46,78.06,2680952 "DD",50.83,"6/11/2007","2:36pm",-0.30,51.13,51.21,50.59,2435997 "DIS",34.19,"6/11/2007","2:36pm",-0.01,34.28,34.44,34.12,4466950 "GE",37.44,"6/11/2007","2:36pm",+0.12,37.07,37.61,37.05,16472401 "GM",31.70,"6/11/2007","2:36pm",+0.70,31.00,31.79,30.90,11121251 "HD",37.75,"6/11/2007","2:36pm",-0.20,37.78,37.83,37.62,8123767 "HON",57.16,"6/11/2007","2:36pm",-0.22,57.25,57.40,56.91,2882942 "HPQ",46.15,"6/11/2007","2:36pm",+0.45,45.80,46.29,45.46,6971049 "IBM",103.42,"6/11/2007","2:36pm",+0.35,102.87,104.00,102.50,3050204 "INTC",22.0203,"6/11/2007","2:41pm",+0.1903,21.70,22.08,21.69,28975326 "JNJ",62.48,"6/11/2007","2:36pm",+0.35,62.89,62.89,62.15,5985258 "JPM",50.67,"6/11/2007","2:36pm",+0.26,50.41,50.84,50.05,6275300 "KO",51.75,"6/11/2007","2:36pm",+0.08,51.67,51.85,51.32,7031052 "MCD",51.33,"6/11/2007","2:36pm",-0.08,51.47,51.62,50.98,3605014 "MMM",85.47,"6/11/2007","2:36pm",-0.47,85.94,85.98,85.28,1656600 "MO",70.33,"6/11/2007","2:36pm",+0.03,70.25,70.50,69.76,5350885 "MRK",51.20,"6/11/2007","2:36pm",+1.06,50.30,51.35,50.04,7984900 "MSFT",30.18,"6/11/2007","2:41pm",+0.13,30.05,30.25,29.93,30696744 "PFE",26.44,"6/11/2007","2:36pm",-0.08,26.50,26.54,26.31,18273796 "PG",63.09,"6/11/2007","2:36pm",+0.02,62.80,63.20,62.75,4902446 "T",40.29,"6/11/2007","2:36pm",+0.03,40.20,40.47,39.89,9901810 "UTX",70.11,"6/11/2007","2:36pm",-0.12,69.85,70.25,69.51,1589000 "VZ",43.50,"6/11/2007","2:36pm",+0.43,42.95,43.61,42.88,6714857 "WMT",49.76,"6/11/2007","2:36pm",-0.32,49.90,50.12,49.55,8135107 "XOM",83.33,"6/11/2007","2:36pm",+0.65,82.68,83.85,82.35,9480100 "AA",39.39,"6/11/2007","2:41pm",-0.27,39.67,40.18,39.34,3314380 "AIG",71.89,"6/11/2007","2:41pm",+0.36,71.29,72.03,71.15,3656596 "AXP",63.23,"6/11/2007","2:41pm",+0.19,62.79,63.42,62.42,2445430 "BA",97.74,"6/11/2007","2:41pm",-0.45,98.25,98.79,97.59,2031200 "C",53.68,"6/11/2007","2:41pm",+0.35,53.20,53.77,52.81,8087994 "CAT",79.04,"6/11/2007","2:41pm",+0.52,78.32,79.46,78.06,2712552 "DD",50.81,"6/11/2007","2:41pm",-0.32,51.13,51.21,50.59,2447897 "DIS",34.19,"6/11/2007","2:41pm",-0.01,34.28,34.44,34.12,4526150 "GE",37.47,"6/11/2007","2:41pm",+0.15,37.07,37.61,37.05,16819600 "GM",31.65,"6/11/2007","2:41pm",+0.65,31.00,31.79,30.90,11306951 "HD",37.76,"6/11/2007","2:41pm",-0.19,37.78,37.83,37.62,8189567 "HON",57.13,"6/11/2007","2:41pm",-0.25,57.25,57.40,56.91,2913842 "HPQ",46.13,"6/11/2007","2:41pm",+0.43,45.80,46.29,45.46,7057949 "IBM",103.44,"6/11/2007","2:41pm",+0.37,102.87,104.00,102.50,3087004 "INTC",22.02,"6/11/2007","2:46pm",+0.19,21.70,22.08,21.69,29252326 "JNJ",62.48,"6/11/2007","2:41pm",+0.35,62.89,62.89,62.15,6046058 "JPM",50.71,"6/11/2007","2:41pm",+0.30,50.41,50.84,50.05,6338800 "KO",51.76,"6/11/2007","2:41pm",+0.09,51.67,51.85,51.32,7064552 "MCD",51.29,"6/11/2007","2:41pm",-0.12,51.47,51.62,50.98,3648414 "MMM",85.50,"6/11/2007","2:41pm",-0.44,85.94,85.98,85.28,1687900 "MO",70.34,"6/11/2007","2:41pm",+0.04,70.25,70.50,69.76,5406985 "MRK",51.18,"6/11/2007","2:41pm",+1.04,50.30,51.35,50.04,8103700 "MSFT",30.22,"6/11/2007","2:46pm",+0.17,30.05,30.25,29.93,31209364 "PFE",26.43,"6/11/2007","2:41pm",-0.09,26.50,26.54,26.31,18438496 "PG",63.11,"6/11/2007","2:41pm",+0.04,62.80,63.20,62.75,4959446 "T",40.27,"6/11/2007","2:41pm",+0.01,40.20,40.47,39.89,10050710 "UTX",70.11,"6/11/2007","2:41pm",-0.12,69.85,70.25,69.51,1626700 "VZ",43.50,"6/11/2007","2:41pm",+0.43,42.95,43.61,42.88,6863457 "WMT",49.79,"6/11/2007","2:41pm",-0.29,49.90,50.12,49.55,8245307 "XOM",83.37,"6/11/2007","2:41pm",+0.69,82.68,83.85,82.35,9563400 "AA",39.33,"6/11/2007","2:46pm",-0.33,39.67,40.18,39.30,3399080 "AIG",71.86,"6/11/2007","2:46pm",+0.33,71.29,72.03,71.15,3740596 "AXP",63.20,"6/11/2007","2:46pm",+0.16,62.79,63.42,62.42,2463030 "BA",97.78,"6/11/2007","2:46pm",-0.41,98.25,98.79,97.59,2065200 "C",53.67,"6/11/2007","2:46pm",+0.34,53.20,53.77,52.81,8200694 "CAT",79.00,"6/11/2007","2:46pm",+0.48,78.32,79.46,78.06,2725452 "DD",50.80,"6/11/2007","2:46pm",-0.33,51.13,51.21,50.59,2457797 "DIS",34.20,"6/11/2007","2:46pm",0.00,34.28,34.44,34.12,4601150 "GE",37.48,"6/11/2007","2:46pm",+0.16,37.07,37.61,37.05,17056300 "GM",31.65,"6/11/2007","2:46pm",+0.65,31.00,31.79,30.90,11359851 "HD",37.75,"6/11/2007","2:46pm",-0.20,37.78,37.83,37.62,8424967 "HON",57.18,"6/11/2007","2:46pm",-0.20,57.25,57.40,56.91,2949842 "HPQ",46.13,"6/11/2007","2:46pm",+0.43,45.80,46.29,45.46,7195349 "IBM",103.49,"6/11/2007","2:46pm",+0.42,102.87,104.00,102.50,3126604 "INTC",22.02,"6/11/2007","2:51pm",+0.19,21.70,22.08,21.69,29404310 "JNJ",62.485,"6/11/2007","2:46pm",+0.355,62.89,62.89,62.15,6102358 "JPM",50.70,"6/11/2007","2:46pm",+0.29,50.41,50.84,50.05,6395400 "KO",51.73,"6/11/2007","2:46pm",+0.06,51.67,51.85,51.32,7104652 "MCD",51.32,"6/11/2007","2:46pm",-0.09,51.47,51.62,50.98,3708627 "MMM",85.50,"6/11/2007","2:46pm",-0.44,85.94,85.98,85.28,1710200 "MO",70.28,"6/11/2007","2:46pm",-0.02,70.25,70.50,69.76,5450985 "MRK",51.19,"6/11/2007","2:46pm",+1.05,50.30,51.35,50.04,8188500 "MSFT",30.16,"6/11/2007","2:51pm",+0.11,30.05,30.25,29.93,31914788 "PFE",26.435,"6/11/2007","2:46pm",-0.085,26.50,26.54,26.31,18623136 "PG",63.11,"6/11/2007","2:46pm",+0.04,62.80,63.20,62.75,5034546 "T",40.29,"6/11/2007","2:46pm",+0.03,40.20,40.47,39.89,10311910 "UTX",70.19,"6/11/2007","2:46pm",-0.04,69.85,70.25,69.51,1662900 "VZ",43.52,"6/11/2007","2:46pm",+0.45,42.95,43.61,42.88,6914257 "WMT",49.76,"6/11/2007","2:46pm",-0.32,49.90,50.12,49.55,8356278 "XOM",83.30,"6/11/2007","2:46pm",+0.62,82.68,83.85,82.35,9689200 "AA",39.33,"6/11/2007","2:51pm",-0.33,39.67,40.18,39.30,3443580 "AIG",71.85,"6/11/2007","2:51pm",+0.32,71.29,72.03,71.15,3833496 "AXP",63.26,"6/11/2007","2:51pm",+0.22,62.79,63.42,62.42,2483430 "BA",97.66,"6/11/2007","2:51pm",-0.53,98.25,98.79,97.59,2085200 "C",53.68,"6/11/2007","2:51pm",+0.35,53.20,53.77,52.81,8317394 "CAT",78.99,"6/11/2007","2:51pm",+0.47,78.32,79.46,78.06,2746952 "DD",50.80,"6/11/2007","2:51pm",-0.33,51.13,51.21,50.59,2480397 "DIS",34.18,"6/11/2007","2:51pm",-0.02,34.28,34.44,34.12,4641650 "GE",37.48,"6/11/2007","2:51pm",+0.16,37.07,37.61,37.05,17252900 "GM",31.62,"6/11/2007","2:51pm",+0.62,31.00,31.79,30.90,11456251 "HD",37.741,"6/11/2007","2:51pm",-0.209,37.78,37.83,37.62,8530067 "HON",57.17,"6/11/2007","2:51pm",-0.21,57.25,57.40,56.91,2989342 "HPQ",46.08,"6/11/2007","2:51pm",+0.38,45.80,46.29,45.46,7295183 "IBM",103.49,"6/11/2007","2:51pm",+0.42,102.87,104.00,102.50,3205604 "INTC",22.03,"6/11/2007","2:56pm",+0.20,21.70,22.08,21.69,29768528 "JNJ",62.45,"6/11/2007","2:51pm",+0.32,62.89,62.89,62.15,6158558 "JPM",50.71,"6/11/2007","2:51pm",+0.30,50.41,50.84,50.05,6486400 "KO",51.76,"6/11/2007","2:51pm",+0.09,51.67,51.85,51.32,7135652 "MCD",51.30,"6/11/2007","2:51pm",-0.11,51.47,51.62,50.98,3729927 "MMM",85.48,"6/11/2007","2:51pm",-0.46,85.94,85.98,85.28,1732900 "MO",70.21,"6/11/2007","2:51pm",-0.09,70.25,70.50,69.76,5501285 "MRK",51.09,"6/11/2007","2:51pm",+0.95,50.30,51.35,50.04,8318900 "MSFT",30.16,"6/11/2007","2:56pm",+0.11,30.05,30.25,29.93,32355638 "PFE",26.43,"6/11/2007","2:51pm",-0.09,26.50,26.54,26.31,18879636 "PG",63.16,"6/11/2007","2:51pm",+0.09,62.80,63.20,62.75,5127246 "T",40.21,"6/11/2007","2:51pm",-0.05,40.20,40.47,39.89,10570410 "UTX",70.15,"6/11/2007","2:51pm",-0.08,69.85,70.25,69.51,1681200 "VZ",43.49,"6/11/2007","2:51pm",+0.42,42.95,43.61,42.88,6963457 "WMT",49.755,"6/11/2007","2:51pm",-0.325,49.90,50.12,49.55,8428278 "XOM",83.24,"6/11/2007","2:51pm",+0.56,82.68,83.85,82.35,9787800 "AA",39.32,"6/11/2007","2:56pm",-0.34,39.67,40.18,39.30,3480480 "AIG",71.86,"6/11/2007","2:56pm",+0.33,71.29,72.03,71.15,3935896 "AXP",63.31,"6/11/2007","2:56pm",+0.27,62.79,63.42,62.42,2509230 "BA",97.78,"6/11/2007","2:56pm",-0.41,98.25,98.79,97.59,2114200 "C",53.70,"6/11/2007","2:56pm",+0.37,53.20,53.77,52.81,8402494 "CAT",79.08,"6/11/2007","2:56pm",+0.56,78.32,79.46,78.06,2766652 "DD",50.84,"6/11/2007","2:56pm",-0.29,51.13,51.21,50.59,2498497 "DIS",34.20,"6/11/2007","2:56pm",0.00,34.28,34.44,34.12,4680250 "GE",37.53,"6/11/2007","2:56pm",+0.21,37.07,37.61,37.05,17452700 "GM",31.682,"6/11/2007","2:56pm",+0.682,31.00,31.79,30.90,11603351 "HD",37.76,"6/11/2007","2:56pm",-0.19,37.78,37.83,37.62,8595367 "HON",57.23,"6/11/2007","2:56pm",-0.15,57.25,57.40,56.91,3114842 "HPQ",46.091,"6/11/2007","2:56pm",+0.391,45.80,46.29,45.46,7387583 "IBM",103.53,"6/11/2007","2:56pm",+0.46,102.87,104.00,102.50,3228704 "INTC",22.02,"6/11/2007","3:01pm",+0.19,21.70,22.08,21.69,30262880 "JNJ",62.53,"6/11/2007","2:56pm",+0.40,62.89,62.89,62.15,6280508 "JPM",50.71,"6/11/2007","2:56pm",+0.30,50.41,50.84,50.05,6635200 "KO",51.82,"6/11/2007","2:56pm",+0.15,51.67,51.85,51.32,7174352 "MCD",51.35,"6/11/2007","2:56pm",-0.06,51.47,51.62,50.98,3766527 "MMM",85.51,"6/11/2007","2:56pm",-0.43,85.94,85.98,85.28,1757600 "MO",70.30,"6/11/2007","2:56pm",0.00,70.25,70.50,69.76,5597885 "MRK",51.15,"6/11/2007","2:56pm",+1.01,50.30,51.35,50.04,8391000 "MSFT",30.16,"6/11/2007","3:01pm",+0.11,30.05,30.25,29.93,32950134 "PFE",26.43,"6/11/2007","2:56pm",-0.09,26.50,26.54,26.31,19031536 "PG",63.12,"6/11/2007","2:56pm",+0.05,62.80,63.21,62.75,5284846 "T",40.26,"6/11/2007","2:56pm",0.00,40.20,40.47,39.89,10708610 "UTX",70.21,"6/11/2007","2:56pm",-0.02,69.85,70.27,69.51,1705800 "VZ",43.53,"6/11/2007","2:56pm",+0.46,42.95,43.61,42.88,7015557 "WMT",49.85,"6/11/2007","2:56pm",-0.23,49.90,50.12,49.55,8513478 "XOM",83.315,"6/11/2007","2:56pm",+0.635,82.68,83.85,82.35,9892200 "AA",39.308,"6/11/2007","3:01pm",-0.352,39.67,40.18,39.30,3525080 "AIG",71.88,"6/11/2007","3:01pm",+0.35,71.29,72.03,71.15,4037796 "AXP",63.33,"6/11/2007","3:01pm",+0.29,62.79,63.42,62.42,2525030 "BA",97.69,"6/11/2007","3:01pm",-0.50,98.25,98.79,97.59,2139700 "C",53.6917,"6/11/2007","3:01pm",+0.3617,53.20,53.77,52.81,8489294 "CAT",79.06,"6/11/2007","3:01pm",+0.54,78.32,79.46,78.06,2812752 "DD",50.82,"6/11/2007","3:01pm",-0.31,51.13,51.21,50.59,2531197 "DIS",34.20,"6/11/2007","3:01pm",0.00,34.28,34.44,34.12,4838550 "GE",37.56,"6/11/2007","3:01pm",+0.24,37.07,37.61,37.05,17708200 "GM",31.71,"6/11/2007","3:01pm",+0.71,31.00,31.79,30.90,11810951 "HD",37.75,"6/11/2007","3:01pm",-0.20,37.78,37.83,37.62,8889067 "HON",57.16,"6/11/2007","3:01pm",-0.22,57.25,57.40,56.91,3138242 "HPQ",46.09,"6/11/2007","3:01pm",+0.39,45.80,46.29,45.46,7434383 "IBM",103.49,"6/11/2007","3:01pm",+0.42,102.87,104.00,102.50,3267604 "INTC",22.01,"6/11/2007","3:06pm",+0.18,21.70,22.08,21.69,30788464 "JNJ",62.53,"6/11/2007","3:01pm",+0.40,62.89,62.89,62.15,6335808 "JPM",50.68,"6/11/2007","3:01pm",+0.27,50.41,50.84,50.05,6741800 "KO",51.81,"6/11/2007","3:01pm",+0.14,51.67,51.85,51.32,7223752 "MCD",51.28,"6/11/2007","3:01pm",-0.13,51.47,51.62,50.98,3825627 "MMM",85.47,"6/11/2007","3:01pm",-0.47,85.94,85.98,85.28,1780900 "MO",70.26,"6/11/2007","3:01pm",-0.04,70.25,70.50,69.76,5686085 "MRK",51.14,"6/11/2007","3:01pm",+1.00,50.30,51.35,50.04,8454600 "MSFT",30.16,"6/11/2007","3:06pm",+0.11,30.05,30.25,29.93,33439226 "PFE",26.42,"6/11/2007","3:01pm",-0.10,26.50,26.54,26.31,19178536 "PG",63.09,"6/11/2007","3:01pm",+0.02,62.80,63.21,62.75,5381146 "T",40.26,"6/11/2007","3:01pm",0.00,40.20,40.47,39.89,10923510 "UTX",70.21,"6/11/2007","3:01pm",-0.02,69.85,70.27,69.51,1741900 "VZ",43.54,"6/11/2007","3:01pm",+0.47,42.95,43.61,42.88,7071957 "WMT",49.7901,"6/11/2007","3:01pm",-0.2899,49.90,50.12,49.55,8630878 "XOM",83.29,"6/11/2007","3:01pm",+0.61,82.68,83.85,82.35,9962400 "AA",39.33,"6/11/2007","3:06pm",-0.33,39.67,40.18,39.25,3559780 "AIG",71.87,"6/11/2007","3:06pm",+0.34,71.29,72.03,71.15,4096696 "AXP",63.30,"6/11/2007","3:06pm",+0.26,62.79,63.42,62.42,2545430 "BA",97.70,"6/11/2007","3:06pm",-0.49,98.25,98.79,97.58,2170600 "C",53.66,"6/11/2007","3:06pm",+0.33,53.20,53.77,52.81,8668794 "CAT",79.0221,"6/11/2007","3:06pm",+0.5021,78.32,79.46,78.06,2871952 "DD",50.84,"6/11/2007","3:06pm",-0.29,51.13,51.21,50.59,2564597 "DIS",34.20,"6/11/2007","3:06pm",0.00,34.28,34.44,34.12,4903050 "GE",37.54,"6/11/2007","3:06pm",+0.22,37.07,37.61,37.05,17946600 "GM",31.76,"6/11/2007","3:06pm",+0.76,31.00,31.79,30.90,11923751 "HD",37.76,"6/11/2007","3:06pm",-0.19,37.78,37.83,37.62,9002967 "HON",57.17,"6/11/2007","3:06pm",-0.21,57.25,57.40,56.91,3177742 "HPQ",46.10,"6/11/2007","3:06pm",+0.40,45.80,46.29,45.46,7517183 "IBM",103.49,"6/11/2007","3:06pm",+0.42,102.87,104.00,102.50,3306904 "INTC",22.03,"6/11/2007","3:11pm",+0.20,21.70,22.08,21.69,31162120 "JNJ",62.50,"6/11/2007","3:06pm",+0.37,62.89,62.89,62.15,6420308 "JPM",50.65,"6/11/2007","3:06pm",+0.24,50.41,50.84,50.05,6851800 "KO",51.80,"6/11/2007","3:06pm",+0.13,51.67,51.85,51.32,7265152 "MCD",51.29,"6/11/2007","3:06pm",-0.12,51.47,51.62,50.98,3892827 "MMM",85.55,"6/11/2007","3:06pm",-0.39,85.94,85.98,85.28,1819600 "MO",70.23,"6/11/2007","3:06pm",-0.07,70.25,70.50,69.76,5764585 "MRK",51.12,"6/11/2007","3:06pm",+0.98,50.30,51.35,50.04,8577100 "MSFT",30.16,"6/11/2007","3:11pm",+0.11,30.05,30.25,29.93,34219296 "PFE",26.41,"6/11/2007","3:06pm",-0.11,26.50,26.54,26.31,19446136 "PG",63.07,"6/11/2007","3:06pm",0.00,62.80,63.21,62.75,5533446 "T",40.23,"6/11/2007","3:06pm",-0.03,40.20,40.47,39.89,11134010 "UTX",70.22,"6/11/2007","3:06pm",-0.01,69.85,70.27,69.51,1781800 "VZ",43.54,"6/11/2007","3:06pm",+0.47,42.95,43.61,42.88,7163857 "WMT",49.795,"6/11/2007","3:06pm",-0.285,49.90,50.12,49.55,8740778 "XOM",83.31,"6/11/2007","3:06pm",+0.63,82.68,83.85,82.35,10075100 "AA",39.37,"6/11/2007","3:11pm",-0.29,39.67,40.18,39.25,3601080 "AIG",71.86,"6/11/2007","3:11pm",+0.33,71.29,72.03,71.15,4171396 "AXP",63.28,"6/11/2007","3:11pm",+0.24,62.79,63.42,62.42,2572030 "BA",97.70,"6/11/2007","3:11pm",-0.49,98.25,98.79,97.58,2202300 "C",53.60,"6/11/2007","3:11pm",+0.27,53.20,53.77,52.81,8910394 "CAT",79.10,"6/11/2007","3:11pm",+0.58,78.32,79.46,78.06,2926952 "DD",50.84,"6/11/2007","3:11pm",-0.29,51.13,51.21,50.59,2592497 "DIS",34.195,"6/11/2007","3:11pm",-0.005,34.28,34.44,34.12,5006550 "GE",37.56,"6/11/2007","3:11pm",+0.24,37.07,37.61,37.05,18148100 "GM",31.81,"6/11/2007","3:11pm",+0.81,31.00,31.82,30.90,12211151 "HD",37.74,"6/11/2007","3:11pm",-0.21,37.78,37.83,37.62,9075667 "HON",57.24,"6/11/2007","3:11pm",-0.14,57.25,57.40,56.91,3219242 "HPQ",46.12,"6/11/2007","3:11pm",+0.42,45.80,46.29,45.46,7596783 "IBM",103.52,"6/11/2007","3:11pm",+0.45,102.87,104.00,102.50,3347004 "INTC",22.03,"6/11/2007","3:16pm",+0.20,21.70,22.08,21.69,31499736 "JNJ",62.48,"6/11/2007","3:11pm",+0.35,62.89,62.89,62.15,6626708 "JPM",50.64,"6/11/2007","3:11pm",+0.23,50.41,50.84,50.05,6957100 "KO",51.80,"6/11/2007","3:11pm",+0.13,51.67,51.85,51.32,7305952 "MCD",51.28,"6/11/2007","3:11pm",-0.13,51.47,51.62,50.98,3951227 "MMM",85.52,"6/11/2007","3:11pm",-0.42,85.94,85.98,85.28,1844000 "MO",70.26,"6/11/2007","3:11pm",-0.04,70.25,70.50,69.76,5802685 "MRK",51.19,"6/11/2007","3:11pm",+1.05,50.30,51.35,50.04,8710500 "MSFT",30.14,"6/11/2007","3:16pm",+0.09,30.05,30.25,29.93,35084560 "PFE",26.42,"6/11/2007","3:11pm",-0.10,26.50,26.54,26.31,19904950 "PG",63.08,"6/11/2007","3:11pm",+0.01,62.80,63.21,62.75,5640146 "T",40.25,"6/11/2007","3:11pm",-0.01,40.20,40.47,39.89,11272135 "UTX",70.21,"6/11/2007","3:11pm",-0.02,69.85,70.27,69.51,1802900 "VZ",43.56,"6/11/2007","3:11pm",+0.49,42.95,43.61,42.88,7294957 "WMT",49.83,"6/11/2007","3:11pm",-0.25,49.90,50.12,49.55,8885778 "XOM",83.3328,"6/11/2007","3:11pm",+0.6528,82.68,83.85,82.35,10226000 "AA",39.36,"6/11/2007","3:16pm",-0.30,39.67,40.18,39.25,3628080 "AIG",71.86,"6/11/2007","3:16pm",+0.33,71.29,72.03,71.15,4253196 "AXP",63.27,"6/11/2007","3:16pm",+0.23,62.79,63.42,62.42,2599530 "BA",97.62,"6/11/2007","3:16pm",-0.57,98.25,98.79,97.58,2240000 "C",53.63,"6/11/2007","3:16pm",+0.30,53.20,53.77,52.81,9064594 "CAT",79.1263,"6/11/2007","3:16pm",+0.6063,78.32,79.46,78.06,2961952 "DD",50.84,"6/11/2007","3:16pm",-0.29,51.13,51.21,50.59,2614197 "DIS",34.19,"6/11/2007","3:16pm",-0.01,34.28,34.44,34.12,5055750 "GE",37.58,"6/11/2007","3:16pm",+0.26,37.07,37.61,37.05,18410300 "GM",31.79,"6/11/2007","3:16pm",+0.79,31.00,31.82,30.90,12429251 "HD",37.73,"6/11/2007","3:16pm",-0.22,37.78,37.83,37.62,9252867 "HON",57.22,"6/11/2007","3:16pm",-0.16,57.25,57.40,56.91,3267342 "HPQ",46.13,"6/11/2007","3:16pm",+0.43,45.80,46.29,45.46,7732283 "IBM",103.52,"6/11/2007","3:16pm",+0.45,102.87,104.00,102.50,3390804 "INTC",22.03,"6/11/2007","3:21pm",+0.20,21.70,22.08,21.69,31747072 "JNJ",62.46,"6/11/2007","3:16pm",+0.33,62.89,62.89,62.15,6718808 "JPM",50.66,"6/11/2007","3:16pm",+0.25,50.41,50.84,50.05,7077000 "KO",51.81,"6/11/2007","3:16pm",+0.14,51.67,51.85,51.32,7356152 "MCD",51.28,"6/11/2007","3:16pm",-0.13,51.47,51.62,50.98,4004827 "MMM",85.50,"6/11/2007","3:16pm",-0.44,85.94,85.98,85.28,1863700 "MO",70.24,"6/11/2007","3:16pm",-0.06,70.25,70.50,69.76,5895585 "MRK",51.17,"6/11/2007","3:16pm",+1.03,50.30,51.35,50.04,8885200 "MSFT",30.18,"6/11/2007","3:21pm",+0.13,30.05,30.25,29.93,35482676 "PFE",26.41,"6/11/2007","3:16pm",-0.11,26.50,26.54,26.31,20145150 "PG",63.07,"6/11/2007","3:16pm",0.00,62.80,63.21,62.75,5824046 "T",40.24,"6/11/2007","3:16pm",-0.02,40.20,40.47,39.89,11393435 "UTX",70.20,"6/11/2007","3:16pm",-0.03,69.85,70.27,69.51,1819800 "VZ",43.56,"6/11/2007","3:16pm",+0.49,42.95,43.61,42.88,7465357 "WMT",49.79,"6/11/2007","3:16pm",-0.29,49.90,50.12,49.55,9009578 "XOM",83.29,"6/11/2007","3:16pm",+0.61,82.68,83.85,82.35,10323500 "AA",39.41,"6/11/2007","3:21pm",-0.25,39.67,40.18,39.25,3683980 "AIG",71.87,"6/11/2007","3:21pm",+0.34,71.29,72.03,71.15,4342596 "AXP",63.28,"6/11/2007","3:21pm",+0.24,62.79,63.42,62.42,2631030 "BA",97.79,"6/11/2007","3:21pm",-0.40,98.25,98.79,97.58,2283500 "C",53.67,"6/11/2007","3:21pm",+0.34,53.20,53.77,52.81,9345794 "CAT",79.18,"6/11/2007","3:21pm",+0.66,78.32,79.46,78.06,3018652 "DD",50.85,"6/11/2007","3:21pm",-0.28,51.13,51.21,50.59,2681597 "DIS",34.189,"6/11/2007","3:21pm",-0.011,34.28,34.44,34.12,5129050 "GE",37.59,"6/11/2007","3:21pm",+0.27,37.07,37.61,37.05,18759500 "GM",31.83,"6/11/2007","3:21pm",+0.83,31.00,31.84,30.90,12621451 "HD",37.74,"6/11/2007","3:21pm",-0.21,37.78,37.83,37.62,9337467 "HON",57.25,"6/11/2007","3:21pm",-0.13,57.25,57.40,56.91,3305486 "HPQ",46.15,"6/11/2007","3:21pm",+0.45,45.80,46.29,45.46,7875783 "IBM",103.58,"6/11/2007","3:21pm",+0.51,102.87,104.00,102.50,3430404 "INTC",22.03,"6/11/2007","3:26pm",+0.20,21.70,22.08,21.69,32604206 "JNJ",62.44,"6/11/2007","3:21pm",+0.31,62.89,62.89,62.15,6819808 "JPM",50.68,"6/11/2007","3:21pm",+0.27,50.41,50.84,50.05,7182200 "KO",51.80,"6/11/2007","3:21pm",+0.13,51.67,51.85,51.32,7430852 "MCD",51.288,"6/11/2007","3:21pm",-0.122,51.47,51.62,50.98,4044427 "MMM",85.54,"6/11/2007","3:21pm",-0.40,85.94,85.98,85.28,1903500 "MO",70.28,"6/11/2007","3:21pm",-0.02,70.25,70.50,69.76,5949185 "MRK",51.13,"6/11/2007","3:21pm",+0.99,50.30,51.35,50.04,9078900 "MSFT",30.15,"6/11/2007","3:26pm",+0.10,30.05,30.25,29.93,36214532 "PFE",26.44,"6/11/2007","3:21pm",-0.08,26.50,26.54,26.31,20502150 "PG",63.10,"6/11/2007","3:21pm",+0.03,62.80,63.21,62.75,5930246 "T",40.26,"6/11/2007","3:21pm",0.00,40.20,40.47,39.89,11546235 "UTX",70.28,"6/11/2007","3:21pm",+0.05,69.85,70.27,69.51,1852300 "VZ",43.58,"6/11/2007","3:21pm",+0.51,42.95,43.61,42.88,7563557 "WMT",49.82,"6/11/2007","3:21pm",-0.26,49.90,50.12,49.55,9134278 "XOM",83.37,"6/11/2007","3:21pm",+0.69,82.68,83.85,82.35,10394600 "AA",39.36,"6/11/2007","3:26pm",-0.30,39.67,40.18,39.25,3734180 "AIG",71.85,"6/11/2007","3:26pm",+0.32,71.29,72.03,71.15,4412096 "AXP",63.26,"6/11/2007","3:26pm",+0.22,62.79,63.42,62.42,2656530 "BA",97.75,"6/11/2007","3:26pm",-0.44,98.25,98.79,97.58,2318700 "C",53.65,"6/11/2007","3:26pm",+0.32,53.20,53.77,52.81,9469794 "CAT",79.14,"6/11/2007","3:26pm",+0.62,78.32,79.46,78.06,3063152 "DD",50.85,"6/11/2007","3:26pm",-0.28,51.13,51.21,50.59,2734497 "DIS",34.20,"6/11/2007","3:26pm",0.00,34.28,34.44,34.12,5218750 "GE",37.61,"6/11/2007","3:26pm",+0.29,37.07,37.61,37.05,19343000 "GM",31.88,"6/11/2007","3:26pm",+0.88,31.00,31.90,30.90,12954222 "HD",37.7382,"6/11/2007","3:26pm",-0.2118,37.78,37.83,37.62,9712367 "HON",57.23,"6/11/2007","3:26pm",-0.15,57.25,57.40,56.91,3354186 "HPQ",46.15,"6/11/2007","3:26pm",+0.45,45.80,46.29,45.46,7964983 "IBM",103.48,"6/11/2007","3:26pm",+0.41,102.87,104.00,102.50,3487304 "INTC",22.05,"6/11/2007","3:31pm",+0.22,21.70,22.08,21.69,33024848 "JNJ",62.38,"6/11/2007","3:26pm",+0.25,62.89,62.89,62.15,6981631 "JPM",50.65,"6/11/2007","3:26pm",+0.24,50.41,50.84,50.05,7366500 "KO",51.78,"6/11/2007","3:26pm",+0.11,51.67,51.85,51.32,7479252 "MCD",51.21,"6/11/2007","3:26pm",-0.20,51.47,51.62,50.98,4117427 "MMM",85.55,"6/11/2007","3:26pm",-0.39,85.94,85.98,85.28,1925800 "MO",70.2525,"6/11/2007","3:26pm",-0.0475,70.25,70.50,69.76,6009485 "MRK",51.09,"6/11/2007","3:26pm",+0.95,50.30,51.35,50.04,9266600 "MSFT",30.165,"6/11/2007","3:31pm",+0.115,30.05,30.25,29.93,36431612 "PFE",26.40,"6/11/2007","3:26pm",-0.12,26.50,26.54,26.31,20767150 "PG",63.08,"6/11/2007","3:26pm",+0.01,62.80,63.21,62.75,5987546 "T",40.25,"6/11/2007","3:26pm",-0.01,40.20,40.47,39.88,13610435 "UTX",70.25,"6/11/2007","3:26pm",+0.02,69.85,70.30,69.51,1883100 "VZ",43.58,"6/11/2007","3:26pm",+0.51,42.95,43.61,42.88,7711757 "WMT",49.80,"6/11/2007","3:26pm",-0.28,49.90,50.12,49.55,9419178 "XOM",83.2854,"6/11/2007","3:26pm",+0.6054,82.68,83.85,82.35,10509700 "AA",39.37,"6/11/2007","3:31pm",-0.29,39.67,40.18,39.25,3767480 "AIG",71.84,"6/11/2007","3:31pm",+0.31,71.29,72.03,71.15,4469996 "AXP",63.27,"6/11/2007","3:31pm",+0.23,62.79,63.42,62.42,2681730 "BA",97.66,"6/11/2007","3:31pm",-0.53,98.25,98.79,97.58,2347600 "C",53.63,"6/11/2007","3:31pm",+0.30,53.20,53.77,52.81,9570094 "CAT",79.15,"6/11/2007","3:31pm",+0.63,78.32,79.46,78.06,3108752 "DD",50.83,"6/11/2007","3:31pm",-0.30,51.13,51.21,50.59,2758297 "DIS",34.20,"6/11/2007","3:31pm",0.00,34.28,34.44,34.12,5359050 "GE",37.61,"6/11/2007","3:31pm",+0.29,37.07,37.61,37.05,19495200 "GM",31.83,"6/11/2007","3:31pm",+0.83,31.00,31.90,30.90,13338922 "HD",37.7275,"6/11/2007","3:31pm",-0.2225,37.78,37.83,37.62,9833219 "HON",57.23,"6/11/2007","3:31pm",-0.15,57.25,57.40,56.91,3411886 "HPQ",46.16,"6/11/2007","3:31pm",+0.46,45.80,46.29,45.46,8108883 "IBM",103.48,"6/11/2007","3:31pm",+0.41,102.87,104.00,102.50,3582104 "INTC",22.02,"6/11/2007","3:36pm",+0.19,21.70,22.08,21.69,33811596 "JNJ",62.38,"6/11/2007","3:31pm",+0.25,62.89,62.89,62.15,7072531 "JPM",50.6225,"6/11/2007","3:31pm",+0.2125,50.41,50.84,50.05,7423100 "KO",51.78,"6/11/2007","3:31pm",+0.11,51.67,51.85,51.32,7527052 "MCD",51.17,"6/11/2007","3:31pm",-0.24,51.47,51.62,50.98,4207427 "MMM",85.50,"6/11/2007","3:31pm",-0.44,85.94,85.98,85.28,1954000 "MO",70.26,"6/11/2007","3:31pm",-0.04,70.25,70.50,69.76,6068285 "MRK",51.15,"6/11/2007","3:31pm",+1.01,50.30,51.35,50.04,9411500 "MSFT",30.14,"6/11/2007","3:36pm",+0.09,30.05,30.25,29.93,37331996 "PFE",26.40,"6/11/2007","3:31pm",-0.12,26.50,26.54,26.31,21355550 "PG",63.11,"6/11/2007","3:31pm",+0.04,62.80,63.21,62.75,6055546 "T",40.24,"6/11/2007","3:31pm",-0.02,40.20,40.47,39.88,13730535 "UTX",70.26,"6/11/2007","3:31pm",+0.03,69.85,70.30,69.51,1985400 "VZ",43.58,"6/11/2007","3:31pm",+0.51,42.95,43.61,42.88,7833157 "WMT",49.80,"6/11/2007","3:31pm",-0.28,49.90,50.12,49.55,9490578 "XOM",83.275,"6/11/2007","3:31pm",+0.595,82.68,83.85,82.35,10612200 "AA",39.30,"6/11/2007","3:36pm",-0.36,39.67,40.18,39.25,3811480 "AIG",71.77,"6/11/2007","3:36pm",+0.24,71.29,72.03,71.15,4581496 "AXP",63.155,"6/11/2007","3:36pm",+0.115,62.79,63.42,62.42,2721130 "BA",97.56,"6/11/2007","3:36pm",-0.63,98.25,98.79,97.55,2383000 "C",53.54,"6/11/2007","3:36pm",+0.21,53.20,53.77,52.81,11390094 "CAT",79.04,"6/11/2007","3:36pm",+0.52,78.32,79.46,78.06,3145152 "DD",50.76,"6/11/2007","3:36pm",-0.37,51.13,51.21,50.59,2813297 "DIS",34.18,"6/11/2007","3:36pm",-0.02,34.28,34.44,34.12,5431250 "GE",37.58,"6/11/2007","3:36pm",+0.26,37.07,37.61,37.05,19977600 "GM",31.79,"6/11/2007","3:36pm",+0.79,31.00,31.90,30.90,13573622 "HD",37.72,"6/11/2007","3:36pm",-0.23,37.78,37.83,37.62,10373519 "HON",57.18,"6/11/2007","3:36pm",-0.20,57.25,57.40,56.91,3490736 "HPQ",46.10,"6/11/2007","3:36pm",+0.40,45.80,46.29,45.46,8224783 "IBM",103.36,"6/11/2007","3:36pm",+0.29,102.87,104.00,102.50,3667004 "INTC",22.02,"6/11/2007","3:41pm",+0.19,21.70,22.08,21.69,34379072 "JNJ",62.33,"6/11/2007","3:36pm",+0.20,62.89,62.89,62.15,7231131 "JPM",50.58,"6/11/2007","3:36pm",+0.17,50.41,50.84,50.05,7580900 "KO",51.73,"6/11/2007","3:36pm",+0.06,51.67,51.85,51.32,7588352 "MCD",51.17,"6/11/2007","3:36pm",-0.24,51.47,51.62,50.98,4327427 "MMM",85.42,"6/11/2007","3:36pm",-0.52,85.94,85.98,85.28,1997700 "MO",70.24,"6/11/2007","3:36pm",-0.06,70.25,70.50,69.76,6182685 "MRK",51.15,"6/11/2007","3:36pm",+1.01,50.30,51.35,50.04,9552400 "MSFT",30.11,"6/11/2007","3:41pm",+0.06,30.05,30.25,29.93,37726620 "PFE",26.41,"6/11/2007","3:36pm",-0.11,26.50,26.54,26.31,21924450 "PG",63.0627,"6/11/2007","3:36pm",-0.0073,62.80,63.21,62.75,6121146 "T",40.23,"6/11/2007","3:36pm",-0.03,40.20,40.47,39.88,13995835 "UTX",70.17,"6/11/2007","3:36pm",-0.06,69.85,70.30,69.51,2031100 "VZ",43.55,"6/11/2007","3:36pm",+0.48,42.95,43.61,42.88,7990757 "WMT",49.811,"6/11/2007","3:36pm",-0.269,49.90,50.12,49.55,9655678 "XOM",83.17,"6/11/2007","3:36pm",+0.49,82.68,83.85,82.35,10797200 "AA",39.28,"6/11/2007","3:41pm",-0.38,39.67,40.18,39.25,3906080 "AIG",71.73,"6/11/2007","3:41pm",+0.20,71.29,72.03,71.15,4691496 "AXP",63.15,"6/11/2007","3:41pm",+0.11,62.79,63.42,62.42,2766030 "BA",97.50,"6/11/2007","3:41pm",-0.69,98.25,98.79,97.48,2453500 "C",53.51,"6/11/2007","3:41pm",+0.18,53.20,53.77,52.81,11535394 "CAT",78.98,"6/11/2007","3:41pm",+0.46,78.32,79.46,78.06,3172952 "DD",50.73,"6/11/2007","3:41pm",-0.40,51.13,51.21,50.59,2869897 "DIS",34.18,"6/11/2007","3:41pm",-0.02,34.28,34.44,34.12,5648050 "GE",37.58,"6/11/2007","3:41pm",+0.26,37.07,37.61,37.05,20658300 "GM",31.73,"6/11/2007","3:41pm",+0.73,31.00,31.90,30.90,13983822 "HD",37.72,"6/11/2007","3:41pm",-0.23,37.78,37.83,37.62,10596819 "HON",57.16,"6/11/2007","3:41pm",-0.22,57.25,57.40,56.91,3548036 "HPQ",46.072,"6/11/2007","3:41pm",+0.372,45.80,46.29,45.46,8398683 "IBM",103.38,"6/11/2007","3:41pm",+0.31,102.87,104.00,102.50,3741504 "INTC",21.992,"6/11/2007","3:46pm",+0.162,21.70,22.08,21.69,35566072 "JNJ",62.29,"6/11/2007","3:41pm",+0.16,62.89,62.89,62.15,7444481 "JPM",50.59,"6/11/2007","3:41pm",+0.18,50.41,50.84,50.05,7794800 "KO",51.74,"6/11/2007","3:41pm",+0.07,51.67,51.85,51.32,7758670 "MCD",51.25,"6/11/2007","3:41pm",-0.16,51.47,51.62,50.98,4427195 "MMM",85.50,"6/11/2007","3:41pm",-0.44,85.94,85.98,85.28,2066900 "MO",70.25,"6/11/2007","3:41pm",-0.05,70.25,70.50,69.76,6272385 "MRK",51.14,"6/11/2007","3:41pm",+1.00,50.30,51.35,50.04,9785300 "MSFT",30.08,"6/11/2007","3:46pm",+0.03,30.05,30.25,29.93,38740488 "PFE",26.43,"6/11/2007","3:41pm",-0.09,26.50,26.54,26.31,22573150 "PG",63.06,"6/11/2007","3:41pm",-0.01,62.80,63.21,62.75,6204746 "T",40.25,"6/11/2007","3:41pm",-0.01,40.20,40.47,39.88,14297535 "UTX",70.12,"6/11/2007","3:41pm",-0.11,69.85,70.30,69.51,2068500 "VZ",43.56,"6/11/2007","3:41pm",+0.49,42.95,43.61,42.88,8283057 "WMT",49.85,"6/11/2007","3:41pm",-0.23,49.90,50.12,49.55,9905878 "XOM",83.1801,"6/11/2007","3:41pm",+0.5001,82.68,83.85,82.35,10980200 "AA",39.28,"6/11/2007","3:46pm",-0.38,39.67,40.18,39.21,4013480 "AIG",71.74,"6/11/2007","3:46pm",+0.21,71.29,72.03,71.15,4893496 "AXP",63.13,"6/11/2007","3:46pm",+0.09,62.79,63.42,62.42,2811230 "BA",97.50,"6/11/2007","3:46pm",-0.69,98.25,98.79,97.44,2545100 "C",53.47,"6/11/2007","3:46pm",+0.14,53.20,53.77,52.81,11812294 "CAT",78.98,"6/11/2007","3:46pm",+0.46,78.32,79.46,78.06,3228752 "DD",50.78,"6/11/2007","3:46pm",-0.35,51.13,51.21,50.59,2971197 "DIS",34.17,"6/11/2007","3:46pm",-0.03,34.28,34.44,34.12,5764350 "GE",37.53,"6/11/2007","3:46pm",+0.21,37.07,37.6202,37.05,21261200 "GM",31.74,"6/11/2007","3:46pm",+0.74,31.00,31.90,30.90,14221022 "HD",37.69,"6/11/2007","3:46pm",-0.26,37.78,37.83,37.62,10829419 "HON",57.13,"6/11/2007","3:46pm",-0.25,57.25,57.40,56.91,3612436 "HPQ",46.06,"6/11/2007","3:46pm",+0.36,45.80,46.29,45.46,8528683 "IBM",103.37,"6/11/2007","3:46pm",+0.30,102.87,104.00,102.50,3846604 "INTC",21.97,"6/11/2007","3:51pm",+0.14,21.70,22.08,21.69,37733764 "JNJ",62.28,"6/11/2007","3:46pm",+0.15,62.89,62.89,62.15,7592281 "JPM",50.57,"6/11/2007","3:46pm",+0.16,50.41,50.84,50.05,7987300 "KO",51.71,"6/11/2007","3:46pm",+0.04,51.67,51.85,51.32,7854570 "MCD",51.31,"6/11/2007","3:46pm",-0.10,51.47,51.62,50.98,4569195 "MMM",85.41,"6/11/2007","3:46pm",-0.53,85.94,85.98,85.28,2120500 "MO",70.24,"6/11/2007","3:46pm",-0.06,70.25,70.50,69.76,6364785 "MRK",51.11,"6/11/2007","3:46pm",+0.97,50.30,51.35,50.04,9941300 "MSFT",30.04,"6/11/2007","3:51pm",-0.01,30.05,30.25,29.93,41124800 "PFE",26.41,"6/11/2007","3:46pm",-0.11,26.50,26.54,26.31,23036750 "PG",63.05,"6/11/2007","3:46pm",-0.02,62.80,63.21,62.75,6374346 "T",40.21,"6/11/2007","3:46pm",-0.05,40.20,40.47,39.88,15620735 "UTX",70.19,"6/11/2007","3:46pm",-0.04,69.85,70.30,69.51,2171000 "VZ",43.569,"6/11/2007","3:46pm",+0.499,42.95,43.61,42.88,8442057 "WMT",49.85,"6/11/2007","3:46pm",-0.23,49.90,50.12,49.55,10081678 "XOM",83.14,"6/11/2007","3:46pm",+0.46,82.68,83.85,82.35,11246800 "AA",39.29,"6/11/2007","3:51pm",-0.37,39.67,40.18,39.21,4132380 "AIG",71.62,"6/11/2007","3:51pm",+0.09,71.29,72.03,71.15,5038729 "AXP",63.09,"6/11/2007","3:51pm",+0.05,62.79,63.42,62.42,2861530 "BA",97.49,"6/11/2007","3:51pm",-0.70,98.25,98.79,97.43,3063600 "C",53.42,"6/11/2007","3:51pm",+0.09,53.20,53.77,52.81,12267394 "CAT",78.78,"6/11/2007","3:51pm",+0.26,78.32,79.46,78.06,3287352 "DD",50.73,"6/11/2007","3:51pm",-0.40,51.13,51.21,50.59,3033697 "DIS",34.135,"6/11/2007","3:51pm",-0.065,34.28,34.44,34.12,5919350 "GE",37.49,"6/11/2007","3:51pm",+0.17,37.07,37.6202,37.05,21532000 "GM",31.72,"6/11/2007","3:51pm",+0.72,31.00,31.90,30.90,14377693 "HD",37.67,"6/11/2007","3:51pm",-0.28,37.78,37.83,37.62,11229419 "HON",57.04,"6/11/2007","3:51pm",-0.34,57.25,57.40,56.91,3682536 "HPQ",46.00,"6/11/2007","3:51pm",+0.30,45.80,46.29,45.46,8652383 "IBM",103.20,"6/11/2007","3:51pm",+0.13,102.87,104.00,102.50,3934104 "INTC",21.97,"6/11/2007","3:56pm",+0.14,21.70,22.08,21.69,38481016 "JNJ",62.22,"6/11/2007","3:51pm",+0.09,62.89,62.89,62.15,7738581 "JPM",50.48,"6/11/2007","3:51pm",+0.07,50.41,50.84,50.05,8466180 "KO",51.65,"6/11/2007","3:51pm",-0.02,51.67,51.85,51.32,7921870 "MCD",51.26,"6/11/2007","3:51pm",-0.15,51.47,51.62,50.98,5379392 "MMM",85.26,"6/11/2007","3:51pm",-0.68,85.94,85.98,85.28,2179200 "MO",70.22,"6/11/2007","3:51pm",-0.08,70.25,70.50,69.76,6468485 "MRK",51.09,"6/11/2007","3:51pm",+0.95,50.30,51.35,50.04,10126700 "MSFT",30.04,"6/11/2007","3:56pm",-0.01,30.05,30.25,29.93,45401260 "PFE",26.36,"6/11/2007","3:51pm",-0.16,26.50,26.54,26.31,23619450 "PG",63.01,"6/11/2007","3:51pm",-0.06,62.80,63.21,62.75,6447846 "T",40.14,"6/11/2007","3:51pm",-0.12,40.20,40.47,39.88,15842235 "UTX",70.19,"6/11/2007","3:51pm",-0.04,69.85,70.30,69.51,2300500 "VZ",43.51,"6/11/2007","3:51pm",+0.44,42.95,43.61,42.88,8663757 "WMT",49.81,"6/11/2007","3:51pm",-0.27,49.90,50.12,49.55,10280178 "XOM",82.99,"6/11/2007","3:51pm",+0.31,82.68,83.85,82.35,11476800 "AA",39.29,"6/11/2007","3:56pm",-0.37,39.67,40.18,39.21,4279480 "AIG",71.63,"6/11/2007","3:56pm",+0.10,71.29,72.03,71.15,5259629 "AXP",63.06,"6/11/2007","3:56pm",+0.02,62.79,63.42,62.42,2932030 "BA",97.48,"6/11/2007","3:56pm",-0.71,98.25,98.79,97.43,3134100 "C",53.46,"6/11/2007","3:56pm",+0.13,53.20,53.77,52.81,12689394 "CAT",78.76,"6/11/2007","3:56pm",+0.24,78.32,79.46,78.06,3364652 "DD",50.70,"6/11/2007","3:56pm",-0.43,51.13,51.21,50.59,3120297 "DIS",34.16,"6/11/2007","3:56pm",-0.04,34.28,34.44,34.12,6061350 "GE",37.47,"6/11/2007","3:56pm",+0.15,37.07,37.6202,37.05,22045900 "GM",31.72,"6/11/2007","3:56pm",+0.72,31.00,31.90,30.90,14702993 "HD",37.67,"6/11/2007","3:56pm",-0.28,37.78,37.83,37.62,11654819 "HON",56.95,"6/11/2007","3:56pm",-0.43,57.25,57.40,56.91,3802936 "HPQ",45.93,"6/11/2007","3:56pm",+0.23,45.80,46.29,45.46,11355083 "IBM",103.01,"6/11/2007","3:56pm",-0.06,102.87,104.00,102.50,4096804 "INTC",21.93,"6/11/2007","4:01pm",+0.10,21.70,22.08,21.69,40860336 "JNJ",62.29,"6/11/2007","3:56pm",+0.16,62.89,62.89,62.15,7933681 "JPM",50.43,"6/11/2007","3:56pm",+0.02,50.41,50.84,50.05,8654880 "KO",51.66,"6/11/2007","3:56pm",-0.01,51.67,51.85,51.32,7987870 "MCD",51.30,"6/11/2007","3:56pm",-0.11,51.47,51.62,50.98,5548192 "MMM",85.18,"6/11/2007","3:56pm",-0.76,85.94,85.98,85.17,2274500 "MO",70.22,"6/11/2007","3:56pm",-0.08,70.25,70.50,69.76,6635085 "MRK",51.06,"6/11/2007","3:56pm",+0.92,50.30,51.35,50.04,10461500 "MSFT",30.02,"6/11/2007","4:00pm",-0.03,30.05,30.25,29.93,46709236 "PFE",26.37,"6/11/2007","3:56pm",-0.15,26.50,26.54,26.31,24178850 "PG",63.02,"6/11/2007","3:56pm",-0.05,62.80,63.21,62.75,6576946 "T",40.0918,"6/11/2007","3:56pm",-0.1682,40.20,40.47,39.88,16125935 "UTX",70.17,"6/11/2007","3:56pm",-0.06,69.85,70.30,69.51,2406200 "VZ",43.48,"6/11/2007","3:56pm",+0.41,42.95,43.61,42.88,9009057 "WMT",49.79,"6/11/2007","3:56pm",-0.29,49.90,50.12,49.55,10441378 "XOM",82.96,"6/11/2007","3:56pm",+0.28,82.68,83.85,82.35,11744600 "AA",39.30,"6/11/2007","4:01pm",-0.36,39.67,40.18,39.14,4516480 "AIG",71.65,"6/11/2007","4:00pm",+0.12,71.29,72.03,71.15,5942029 "AXP",63.06,"6/11/2007","4:00pm",+0.02,62.79,63.42,62.42,3050830 "BA",97.55,"6/11/2007","4:00pm",-0.64,98.25,98.79,97.42,3300500 "C",53.47,"6/11/2007","4:01pm",+0.14,53.20,53.77,52.81,13457894 "CAT",78.75,"6/11/2007","4:00pm",+0.23,78.32,79.46,78.06,3456552 "DD",50.73,"6/11/2007","3:59pm",-0.40,51.13,51.21,50.59,3162997 "DIS",34.15,"6/11/2007","3:59pm",-0.05,34.28,34.44,34.12,6162650 "GE",37.46,"6/11/2007","4:01pm",+0.14,37.07,37.6202,37.05,23163100 "GM",31.77,"6/11/2007","4:00pm",+0.77,31.00,31.90,30.90,15223093 "HD",37.71,"6/11/2007","4:00pm",-0.24,37.78,37.83,37.62,12074119 "HON",57.02,"6/11/2007","3:59pm",-0.36,57.25,57.40,56.91,3911336 "HPQ",45.89,"6/11/2007","4:00pm",+0.19,45.80,46.29,45.46,11960883 "IBM",103.22,"6/11/2007","4:01pm",+0.15,102.87,104.00,102.50,4668204 "INTC",21.93,"6/11/2007","4:01pm",+0.10,21.70,22.08,21.69,41134284 "JNJ",62.27,"6/11/2007","4:00pm",+0.14,62.89,62.89,62.15,8452146 "JPM",50.45,"6/11/2007","4:00pm",+0.04,50.41,50.84,50.05,8869280 "KO",51.65,"6/11/2007","3:59pm",-0.02,51.67,51.85,51.32,8049770 "MCD",51.25,"6/11/2007","4:01pm",-0.16,51.47,51.62,50.98,5969292 "MMM",85.30,"6/11/2007","4:01pm",-0.64,85.94,85.98,85.17,2454400 "MO",70.22,"6/11/2007","4:00pm",-0.08,70.25,70.50,69.76,6887785 "MRK",51.09,"6/11/2007","3:59pm",+0.95,50.30,51.35,50.04,10623065 "MSFT",30.02,"6/11/2007","4:00pm",-0.03,30.05,30.25,29.93,46924636 "PFE",26.37,"6/11/2007","4:00pm",-0.15,26.50,26.54,26.31,25287450 "PG",63.05,"6/11/2007","4:00pm",-0.02,62.80,63.21,62.75,6920246 "T",40.12,"6/11/2007","4:00pm",-0.14,40.20,40.47,39.88,16712535 "UTX",70.18,"6/11/2007","4:00pm",-0.05,69.85,70.30,69.51,2660900 "VZ",43.47,"6/11/2007","3:59pm",+0.40,42.95,43.61,42.88,9156827 "WMT",49.81,"6/11/2007","4:00pm",-0.27,49.90,50.12,49.55,10924878 "XOM",83.06,"6/11/2007","4:00pm",+0.38,82.68,83.85,82.35,12427710 ================================================ FILE: Data/missing.csv ================================================ name,shares,price "AA",15,39.48 "AXP",10,62.58 "BA",5,98.31 "C",,53.08 "CAT",15,78.29 "DD",10,50.75 "DIS",50,N/A "GE",,37.23 "GM",15,31.44 "HD",20,37.67 "HPQ",5,45.81 "IBM",10,102.86 "INTC",,21.84 "JNJ",20,62.25 "JPM",10,50.35 "KO",5,51.65 "MCD",,51.11 "MMM",10,85.60 "MO",,70.09 "MRK",5,50.21 "MSFT",20,30.08 "PFE",,26.40 "PG",5,62.79 "T",10,40.03 "UTX",8,69.81 "VZ",,42.92 "WMT",10,49.78 "XOM",15,82.50 ================================================ FILE: Data/portfolio.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 ================================================ FILE: Data/portfolio2.csv ================================================ name,shares,price "AA",50,27.10 "HPQ",250,43.15 "MSFT",25,50.15 "GE",125,52.10 ================================================ FILE: Data/portfolio3.csv ================================================ "AA",15,39.48 "AXP",10,62.58 "BA",5,98.31 "C",-,53.08 "CAT",15,78.29 "DD",10,50.75 "DIS",-,N/A "GE",-,37.23 "GM",15,31.44 "HD",20,37.67 "HPQ",5,45.81 "IBM",10,102.86 "INTC",-,21.84 "JNJ",20,62.25 "JPM",10,50.35 "KO",5,51.65 "MCD",-,51.11 "MMM",10,85.60 "MO",-,70.09 "MRK",5,50.21 "MSFT",20,30.08 "PFE",-,26.40 "PG",5,62.79 "T",10,40.03 "UTX",8,69.81 "VZ",-,42.92 "WMT",10,49.78 "XOM",15,82.50 ================================================ FILE: Data/portfolio_noheader.csv ================================================ "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 ================================================ FILE: Data/prices.csv ================================================ "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.90 "KFT",26.11 "KO",49.16 "MCD",58.99 "MMM",57.10 "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 ================================================ FILE: Data/stocksim.py ================================================ #!/usr/bin/env python # stocksim.py # # Stock market simulator. This simulator creates stock market # data and provides it in several different ways: # # 1. Makes periodic updates to a log file stocklog.dat # # The purpose of this module is to provide data to the user # in different ways in order to write interesting Python examples import math import time import threading try: import queue except ImportError: import Queue as queue history_file = "dowstocks.csv" # Convert a time string such as "4:00pm" to minutes past midnight def minutes(tm): am_pm = tm[-2:] fields = tm[:-2].split(":") hour = int(fields[0]) minute = int(fields[1]) if hour == 12: hour = 0 if am_pm == 'pm': hour += 12 return hour*60 + minute # Convert time in minutes to a format string def minutes_to_str(m): frac,m = math.modf(m) hours = m//60 minutes = m % 60 seconds = frac * 60 return "%02d:%02d.%02.f" % (hours,minutes,seconds) # Read the stock history file as a list of lists def read_history(filename): result = [] for line in open(filename): str_fields = line.strip().split(",") fields = [eval(x) for x in str_fields] fields[3] = minutes(fields[3]) result.append(fields) return result # Format CSV record def csv_record(fields): s = '"%s",%0.2f,"%s","%s",%0.2f,%0.2f,%0.2f,%0.2f,%d' % tuple(fields) return s class StockTrack(object): def __init__(self,name): self.name = name self.history = [] self.price = 0 self.time = 0 self.index = 0 self.open = 0 self.low = 0 self.high = 0 self.volume = 0 self.initial = 0 self.change = 0 self.date = "" def add_data(self,record): self.history.append(record) def reset(self,time): self.time = time # Sort the history by time self.history.sort(key=lambda t:t[3]) # Find the first entry who's time is behind the given time self.index = 0 while self.index < len(self.history): if self.history[self.index][3] > time: break self.index += 1 self.open = self.history[0][5] self.initial = self.history[0][1] - self.history[0][4] self.date = self.history[0][2] self.update() self.low = self.price self.high = self.price # Calculate interpolated value of a given field based on # current time def interpolate(self,field): first = self.history[self.index][field] next = self.history[self.index+1][field] first_t = self.history[self.index][3] next_t = self.history[self.index+1][3] try: slope = (next - first)/(next_t-first_t) return first + slope*(self.time - first_t) except ZeroDivisionError: return first # Update all computed values def update(self): self.price = round(self.interpolate(1),2) self.volume = int(self.interpolate(-1)) if self.price < self.low: self.low = self.price if self.price >= self.high: self.high = self.price self.change = self.price - self.initial # Increment the time by a delta def incr(self,dt): self.time += dt if self.index < (len(self.history) - 2): while self.index < (len(self.history) - 2) and self.time >= self.history[self.index+1][3]: self.index += 1 self.update() def make_record(self): return [self.name,round(self.price,2),self.date,minutes_to_str(self.time),round(self.change,2),self.open,round(self.high,2), round(self.low,2),self.volume] class MarketSimulator(object): def __init__(self): self.stocks = { } self.prices = { } self.time = 0 self.observers = [] def register(self,observer): self.observers.append(observer) def publish(self,record): for obj in self.observers: obj.update(record) def add_history(self,filename): hist = read_history(filename) for record in hist: if record[0] not in self.stocks: self.stocks[record[0]] = StockTrack(record[0]) self.stocks[record[0]].add_data(record) def reset(self,time): self.time = time for s in list(self.stocks.values()): s.reset(time) # Run forever. Dt is in seconds def run(self,dt): for s in self.stocks: self.prices[s] = self.stocks[s].price self.publish(self.stocks[s].make_record()) while self.time < 1000: for s in self.stocks: self.stocks[s].incr(dt/60.0) # Increment is in minutes if self.stocks[s].price != self.prices[s]: self.prices[s] = self.stocks[s].price self.publish(self.stocks[s].make_record()) time.sleep(dt) self.time += (dt/60.0) class BasicPrinter(object): def update(self,record): print(csv_record(record)) class LogPrinter(object): def __init__(self,filename): self.f = open(filename,"w") def update(self,record): self.f.write(csv_record(record)+"\n") self.f.flush() m = MarketSimulator() m.add_history(history_file) m.reset(minutes("9:30am")) m.register(BasicPrinter()) m.register(LogPrinter("stocklog.csv")) m.run(1) ================================================ FILE: Data/words.txt ================================================ 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 ================================================ FILE: Exercises/README.md ================================================ # Advanced Python Mastery Copyright (C) 2007-2023 David Beazley (dave@dabeaz.com) http://www.dabeaz.com Welcome to the Python Mastery course. This directory, `pythonmaster` is where you find support files related to the class exercises. It is also where you will be doing your work. This course requires the use of Python 3.6 or newer. If you are using Python 2, most of the material still applies, but you will have to make minor code modifications here and there. - [`PythonMastery.pdf`](../PythonMastery.pdf) is a PDF that contains all of the presentation slides. - The [`Exercises/`](index.md) folder is where you find all the class exercises. - The [`Data/`](../Data/) folder is where you find data files, scripts, and other files used by the exercises. - The [`Solutions/`](../Solutions/) folder contains complete solution code for various exercises. Each problem has its own directory. For example, the solution to exercise 3.2 can be found in the [`Solutions/3_2/`](../Solutions/3_2/) directory. Every attempt has been made to make sure exercises work. However, it's possible that you will find typos or minor mistakes. If you find any errors, please let me know so that I can fix them for future editions of the course. ================================================ FILE: Exercises/ex1_1.md ================================================ \[ [Index](index.md) | []() | [Exercise 1.2](ex1_2.md) \] # Exercise 1.1 *Objectives:* - Make sure Python is installed correctly on your machine - Start the interactive interpreter - Edit and run a small program *Files Created:* `art.py` ## (a) Launch Python Start Python3 on your machine. Make sure you can type simple statements such as the "hello world" program: ```python >>> print('Hello World') Hello World >>> ``` In much of this course, you'll want to make sure you can work from the interactive REPL like this. If you're working from a different environment such as IPython or Jupyter Notebooks, that's fine. ## (b) Some Generative Art Create the following program and put it in a file called `art.py`: ```python # art.py import sys import random chars = '\|/' def draw(rows, columns): for r in rows: print(''.join(random.choice(chars) for _ in range(columns))) if __name__ == '__main__': if len(sys.argv) != 3: raise SystemExit("Usage: art.py rows columns") draw(int(sys.argv[1]), int(sys.argv[2])) ``` Make sure you can run this program from the command line or a terminal. ``` bash % python3 art.py 10 20 ``` If you run the above command, you'll get a crash and traceback message. Go fix the problem and run the program again. You should get output like this: ``` bash % python3 art.py 10 20 ||||/\||//\//\|||\|\ ///||\/||\//|\\|\\/\ |\////|//|||\//|/\|| |//\||\/|\///|\|\|/| |/|//|/|/|\\/\/\||// |\/\|\//\\//\|\||\\/ |||\\\\/\\\|/||||\/| \\||\\\|\||||////\\| //\//|/|\\|\//\|||\/ \\\|/\\|/|\\\|/|/\/| bash % ``` ### Important Note It is absolutely essential that you are able to edit, run, and debug ordinary Python programs for the rest of this course. The choice of editor, IDE, or operating system doesn't matter as long as you are able to experiment interactively and create normal Python source files that can execute from the command line. \[ [Solution](soln1_1.md) | [Index](index.md) | [Exercise 1.2](ex1_2.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex1_2.md ================================================ \[ [Index](index.md) | [Exercise 1.1](ex1_1.md) | [Exercise 1.3](ex1_3.md) \] # Exercise 1.2 *Objectives:* - Manipulate various built-in Python objects *Files Created:* None ## Part 1 : Numbers Numerical calculations work about like you would expect in Python. For example: ```python >>> 3 + 4*5 23 >>> 23.45 / 1e-02 2345.0 >>> ``` Be aware that integer division is different in Python 2 and Python 3. ```python >>> 7 / 4 # In python 2, this truncates to 1 1.75 >>> 7 // 4 # Truncating division 1 >>> ``` If you want Python 3 behavior in Python 2, do this: ```python >>> from __future__ import division >>> 7 / 4 1.75 >>> 7 // 4 # Truncating division 1 >>> ``` Numbers have a small set of methods, many of which are actually quite recent and overlooked by even experienced Python programmers. Try some of them. ```python >>> x = 1172.5 >>> x.as_integer_ratio() (2345, 2) >>> x.is_integer() False >>> y = 12345 >>> y.numerator 12345 >>> y.denominator 1 >>> y.bit_length() 14 >>> ``` ## Part 2 : String Manipulation Define a string containing a series of stock ticker symbols like this: ```python >>> symbols = 'AAPL IBM MSFT YHOO SCO' ``` Now, let's experiment with different string operations: ### (a) Extracting individual characters and substrings Strings are arrays of characters. Try extracting a few characters: ```python >>> symbols[0] 'A' >>> symbols[1] 'A' >>> symbols[2] 'P' >>> symbols[-1] # Last character 'O' >>> symbols[-2] # 2nd from last character 'C' >>> ``` Try taking a few slices: ```python >>> symbols[:4] 'AAPL' >>> symbols[-3:] 'SCO' >>> symbols[5:8] 'IBM' >>> ``` ### (b) Strings as read-only objects 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 "", line 1, in TypeError: 'str' object does not support item assignment >>> ``` ### (c) 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 += ' GOOG' >>> symbols ... look at the result ... ``` Now, try adding "HPQ" to the beginning of `symbols` like this: ```python >>> symbols = 'HPQ ' + symbols >>> symbols ... look at the result ... ``` It should be noted in both of these examples, the original string `symbols` is _NOT_ being modified "in place." Instead, a completely new string is created. The variable name `symbols` is just bound to the result. Afterwards, the old string is destroyed since it's not being used anymore. ### (d) Membership testing (substring testing) Experiment with the `in` operator to check for substrings. At the interactive prompt, try these operations: ```python >>> 'IBM' in symbols True >>> 'AA' in symbols True >>> 'CAT' in symbols False >>> ``` Make sure you understand why the check for "AA" returned `True`. ### (e) String Methods At the Python interactive prompt, try experimenting with some of the string methods. ```python >>> symbols.lower() 'hpq aapl ibm msft yhoo sco goog' >>> symbols 'HPQ AAPL IBM MSFT YHOO SCO GOOG' ``` 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() >>> lowersyms 'hpq aapl ibm msft yhoo sco goog' >>> ``` Try some more operations: ```python >>> symbols.find('MSFT') 13 >>> symbols[13:17] 'MSFT' >>> symbols = symbols.replace('SCO','') >>> symbols 'HPQ AAPL IBM MSFT YHOO GOOG' >>> ``` ## Part 3 : List Manipulation In the first part, you worked with strings containing stock symbols. For example: ```python >>> symbols = 'HPQ AAPL IBM MSFT YHOO GOOG' >>> ``` Define the above variable and split it into a list of names using the `split()` operation of strings: ```python >>> symlist = symbols.split() >>> symlist ['HPQ', 'AAPL', 'IBM', 'MSFT', 'YHOO', 'GOOG' ] >>> ``` ### (a) Extracting and reassigning list elements Lists work like arrays where you can look up and modify elements by numerical index. Try a few lookups: ```python >>> symlist[0] 'HPQ' >>> symlist[1] 'AAPL' >>> symlist[-1] 'GOOG' >>> symlist[-2] 'YHOO' >>> ``` Try reassigning one of the items: ```python >>> symlist[2] = 'AIG' >>> symlist ['HPQ', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'GOOG' ] >>> ``` ### (b) 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 ... ``` ### (c) Membership tests Use the `in` operator to check if `'AIG'`,`'AA'`, and `'CAT'` are in the list of symbols. ```python >>> 'AIG' in symlist True >>> 'AA' in symlist False >>> ``` ### (d) Appending, inserting, and deleting items Use the `append()` method to add the symbol `'RHT'` to end of `symlist`. ```python >>> symlist.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 >>> symlist.insert(1,'AA') >>> symlist ['HPQ', 'AA', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'GOOG', 'RHT'] >>> ``` Use the `remove()` method to remove `'MSFT'` from the list. ```python >>> symlist.remove('MSFT') >>> symlist ['HPQ', 'AA', 'AAPL', 'AIG', 'YHOO', 'GOOG', 'RHT'] ``` Try calling `remove()` again to see what happens if the item can't be found. ```python >>> symlist.remove('MSFT') ... watch what happens ... >>> ``` Use the `index()` method to find the position of `'YHOO'` in the list. ```python >>> symlist.index('YHOO') 4 >>> symlist[4] 'YHOO' >>> ``` ### (e) List 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. ### (f) 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 = [symlist, nums] >>> items [['YHOO', 'RHT', 'HPQ', 'GOOG', 'AIG', 'AAPL', 'AA'], [101, 102, 103]] ``` Pay close attention to the above output. `items` is a list with two elements. Each element is list. Try some nested list lookups: ```python >>> items[0] ['YHOO', 'RHT', 'HPQ', 'GOOG', 'AIG', 'AAPL', 'AA'] >>> items[0][1] 'RHT' >>> items[0][1][2] 'T' >>> items[1] [101, 102, 103] >>> items[1][1] 102 >>> ``` ## Part 4 : Dictionaries In last few parts, you've simply worked with stock symbols. However, suppose you wanted to map stock symbols to other data such as the price? Use a dictionary: ```python >>> prices = { 'IBM': 91.1, 'GOOG': 490.1, 'AAPL':312.23 } >>> ``` A dictionary maps keys to values. Here's how to access: ```python >>> prices['IBM'] 91.1 >>> prices['IBM'] = 123.45 >>> prices['HPQ'] = 26.15 >>> prices {'GOOG': 490.1, 'AAPL': 312.23, 'IBM': 123.45, 'HPQ': 26.15} >>> ``` To get a list of keys, use this: ```python >>> list(prices) ['GOOG', 'AAPL', 'IBM', 'HPQ'] >>> ``` To delete a value, use `del` ```python >>> del prices['AAPL'] >>> prices {'GOOG': 490.1, 'IBM': 123.45, 'HPQ': 26.15} >>> ``` \[ [Solution](soln1_2.md) | [Index](index.md) | [Exercise 1.1](ex1_1.md) | [Exercise 1.3](ex1_3.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex1_3.md ================================================ \[ [Index](index.md) | [Exercise 1.2](ex1_2.md) | [Exercise 1.4](ex1_4.md) \] # Exercise 1.3 *Objectives:* - Review basic file I/O *Files Created:* `pcost.py` ## (a) Working with files The file `Data/portfolio.dat` contains a list of lines with information on a portfolio of stocks. The file looks like this: ``` 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 first column is the stock name, the second column is the number of shares, and the third column is the purchase price of a single share. 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. To do this, compute the sum of the second column multiplied by the third column. \[ [Solution](soln1_3.md) | [Index](index.md) | [Exercise 1.2](ex1_2.md) | [Exercise 1.4](ex1_4.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex1_4.md ================================================ \[ [Index](index.md) | [Exercise 1.3](ex1_3.md) | [Exercise 1.5](ex1_5.md) \] # Exercise 1.4 *Objectives:* - Review of how to define simple functions - Exception handling *Files Created:* None *Files Modified:* `pcost.py` ## (a) Defining a function Take the program `pcost.py` that you wrote in the last exercise and convert it into a function `portfolio_cost(filename)` that takes a filename as input, reads the portfolio data in that file, and returns the total cost of the portfolio as a floating point number. Once you written the function, have your program call the function by simply adding this statement at the end: ```python print(portfolio_cost('Data/portfolio.dat')) ``` Run your program and make sure it produces the same output as before. ## (b) Adding Error Handling When writing programs that process data, it is common to encounter errors related to bad data (malformed, missing fields, etc.). Modify your `pcost.py` program to read the data file `Data/portfolio3.dat` and run it (hint: it should crash). Modify your function slightly so that it is able to recover from lines with bad data. For example, the conversion functions `int()` and `float()` raise a `ValueError` exception if they can't convert the input. Use `try` and `except` to catch and print a warning message about lines that can't be parsed. For example: ``` Couldn't parse: 'C - 53.08\n' Reason: invalid literal for int() with base 10: '-' Couldn't parse: 'DIS - 34.20\n' Reason: invalid literal for int() with base 10: '-' ... ``` Try running your program on the `Data/portfolio3.dat` file again. It should run successfully despite printed warning messages. ## (c) Interactive Experimentation Run your `pcost.py` program and call the `portfolio_cost()` function directly from the interactive interpreter. ```python >>> portfolio_cost('Data/portfolio.dat') 44671.15 >>> portfolio_cost('Data/portfolio2.dat') 19908.75 >>> ``` Note: To do this, you might have to run python using the `-i` option. For example: ``` bash % python3 -i pcost.py ``` We are going to be writing a lot of programs where you define functions and experiment interactively. Make sure you know how to do this. \[ [Solution](soln1_4.md) | [Index](index.md) | [Exercise 1.3](ex1_3.md) | [Exercise 1.5](ex1_5.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex1_5.md ================================================ \[ [Index](index.md) | [Exercise 1.4](ex1_4.md) | [Exercise 1.6](ex1_6.md) \] # Exercise 1.5 *Objectives:* - Review of how to define a simple object *Files Created:* `stock.py` ## (a) Defining a simple object Create a file `stock.py` and define the following class: ```python class Stock: def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def cost(self): return self.shares * self.price ``` Once you have done this, run your program and experiment with your new `Stock` object: ```python >>> s = Stock('GOOG',100,490.10) >>> s.name 'GOOG' >>> s.shares 100 >>> s.price 490.1 >>> s.cost() 49010.0 >>> print('%10s %10d %10.2f' % (s.name, s.shares, s.price)) GOOG 100 490.10 >>> t = Stock('IBM', 50, 91.5) >>> t.cost() 4575.0 >>> ``` \[ [Solution](soln1_5.md) | [Index](index.md) | [Exercise 1.4](ex1_4.md) | [Exercise 1.6](ex1_6.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex1_6.md ================================================ \[ [Index](index.md) | [Exercise 1.5](ex1_5.md) | [Exercise 2.1](ex2_1.md) \] # Exercise 1.6 *Objectives:* - Defining modules - Using the import statement *Files Created:* None **Note:** For this exercise involving modules, it is critically important to make sure you are running Python in a proper environment. You may need to check the value of `sys.path` if you can't get import statements to work. Ask for assistance if everything seems broken. Before starting this exercise, first restart your Python interpreter session. If using IDLE, click on the shell window and look for a menu option "Shell > Restart Shell". You should get a message like this: ```python >>> ##################== RESTART ##################== >>> ``` If you are using Unix, simply exit Python and restart the interpreter. ## (a) Using the import statement In previous exercises, you wrote two programs `pcost.py` and `stock.py`. Use the `import` statement to load these programs and use their functionality: ```python >>> import pcost 44671.15 >>> pcost.portfolio_cost('Data/portfolio2.dat') 19908.75 >>> from stock import Stock >>> s = Stock('GOOG', 100, 490.10) >>> s.name 'GOOG' >>> s.cost() 49010.0 >>> ``` If you can't get the above statements to work, you might have placed your programs in a funny directory. Make sure you are running Python in the same directory as your files or that the directory is included on `sys.path`. ## (b) Main Module In your `pcost.py` program, the last statement called a function and printed out the result. Modify the program so that this step only occurs if the program is run as the main program. Now, try running the program two ways: First, run the program as main: ``` bash % python3 pcost.py 44671.25 bash % ``` Next, run the program as a library import. You should not see any output. ```python >>> import pcost >>> ``` \[ [Solution](soln1_6.md) | [Index](index.md) | [Exercise 1.5](ex1_5.md) | [Exercise 2.1](ex2_1.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex2_1.md ================================================ \[ [Index](index.md) | [Exercise 1.6](ex1_6.md) | [Exercise 2.2](ex2_2.md) \] # Exercise 2.1 *Objectives:* - Figure out the most memory-efficient way to store a lot of data. - Learn about different ways of representing records including tuples, dictionaries, classes, and named tuples. In this exercise, we look at different choices for representing data structures with an eye towards memory use and efficiency. A lot of people use Python to perform various kinds of data analysis so knowing about different options and their tradeoffs is useful information. ## (a) Stuck on the bus The file `Data/ctabus.csv` is a CSV file containing daily ridership data for the Chicago Transit Authority (CTA) bus system from January 1, 2001 to August 31, 2013. It contains approximately 577000 rows of data. Use Python to view a few lines of data to see what it looks like: ```python >>> f = open('Data/ctabus.csv') >>> next(f) 'route,date,daytype,rides\n' >>> next(f) '3,01/01/2001,U,7354\n' >>> next(f) '4,01/01/2001,U,9288\n' >>> ``` There are 4 columns of data. - route: Column 0. The bus route name. - date: Column 1. A date string of the form MM/DD/YYYY. - daytype: Column 2. A day type code (U=Sunday/Holiday, A=Saturday, W=Weekday) - rides: Column 3. Total number of riders (integer) The `rides` column records the total number of people who boarded a bus on that route on a given day. Thus, from the example, 7354 people rode the number 3 bus on January 1, 2001. ## (b) Basic memory use of text Let's get a baseline of the memory required to work with this datafile. First, restart Python and try a very simple experiment of simply grabbing the file and storing its data in a single string: ```python >>> # --- RESTART >>> import tracemalloc >>> f = open('Data/ctabus.csv') >>> tracemalloc.start() >>> data = f.read() >>> len(data) 12361039 >>> current, peak = tracemalloc.get_traced_memory() >>> current 12369664 >>> peak 24730766 >>> ``` Your results might vary somewhat, but you should see current memory use in the range of 12MB with a peak of 24MB. What happens if you read the entire file into a list of strings instead? Restart Python and try this: ```python >>> # --- RESTART >>> import tracemalloc >>> f = open('Data/ctabus.csv') >>> tracemalloc.start() >>> lines = f.readlines() >>> len(lines) 577564 >>> current, peak = tracemalloc.get_traced_memory() >>> current 45828030 >>> peak 45867371 >>> ``` You should see the memory use go up significantly into the range of 40-50MB. Point to ponder: what might be the source of that extra overhead? ## (c) A List of Tuples In practice, you might read the data into a list and convert each line into some other data structure. Here is a program `readrides.py` that reads the entire file into a list of tuples using the `csv` module: ```python # readrides.py import csv def read_rides_as_tuples(filename): ''' Read the bus ride data as a list of tuples ''' records = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = (route, date, daytype, rides) records.append(record) return records if __name__ == '__main__': import tracemalloc tracemalloc.start() rows = read_rides_as_tuples('Data/ctabus.csv') print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory()) ``` Run this program using `python3 -i readrides.py` and look at the resulting contents of `rows`. You should get a list of tuples like this: ```python >>> len(rows) 577563 >>> rows[0] ('3', '01/01/2001', 'U', 7354) >>> rows[1] ('4', '01/01/2001', 'U', 9288) ``` Look at the resulting memory use. It should be substantially higher than in part (b). ## (d) Memory Use of Other Data Structures Python has many different choices for representing data structures. For example: ```python # A tuple row = (route, date, daytype, rides) # A dictionary row = { 'route': route, 'date': date, 'daytype': daytype, 'rides': rides, } # A class class Row: def __init__(self, route, date, daytype, rides): self.route = route self.date = date self.daytype = daytype self.rides = rides # A named tuple from collections import namedtuple Row = namedtuple('Row', ['route', 'date', 'daytype', 'rides']) # A class with __slots__ class Row: __slots__ = ['route', 'date', 'daytype', 'rides'] def __init__(self, route, date, daytype, rides): self.route = route self.date = date self.daytype = daytype self.rides = rides ``` Your task is as follows: Create different versions of the `read_rides()` function that use each of these data structures to represent a single row of data. Then, find out the resulting memory use of each option. Find out which approach offers the most efficient storage if you were working with a lot of data all at once. \[ [Solution](soln2_1.md) | [Index](index.md) | [Exercise 1.6](ex1_6.md) | [Exercise 2.2](ex2_2.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex2_2.md ================================================ \[ [Index](index.md) | [Exercise 2.1](ex2_1.md) | [Exercise 2.3](ex2_3.md) \] # Exercise 2.2 *Objectives:* - Work with various containers - List/Set/Dict Comprehensions - Collections module - Data analysis challenge Most Python programmers are generally familiar with lists, dictionaries, tuples, and other basic datatypes. In this exercise, we'll put that knowledge to work to solve various data analysis problems. ## (a) Preliminaries To get started, let's review some basics with a slightly simpler dataset-- a portfolio of stock holdings. Create a file `readport.py` and put this code in it: ```python # readport.py import csv # A function that reads a file into a list of dicts def read_portfolio(filename): portfolio = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = { 'name' : row[0], 'shares' : int(row[1]), 'price' : float(row[2]) } portfolio.append(record) return portfolio ``` This file reads some simple stock market data in the file `Data/portfolio.csv`. Use the function to read the file and look at the results: ```python >>> portfolio = read_portfolio('Data/portfolio.csv') >>> 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}] >>> ``` In this data, each row consists of a stock name, a number of held shares, and a purchase price. There are multiple entries for certain stock names such as MSFT and IBM. ## (b) Comprehensions List, set, and dictionary comprehensions can be a useful tool for manipulating data. For example, try these operations: ```python >>> # Find all holdings more than 100 shares >>> [s for s in portfolio if s['shares'] > 100] [{'name': 'CAT', 'shares': 150, 'price': 83.44}, {'name': 'MSFT', 'shares': 200, 'price': 51.23}] >>> # Compute total cost (shares * price) >>> sum([s['shares']*s['price'] for s in portfolio]) 44671.15 >>> >>> # Find all unique stock names (set) >>> { s['name'] for s in portfolio } {'MSFT', 'IBM', 'AA', 'GE', 'CAT'} >>> >>> # Count the total shares of each of stock >>> totals = { s['name']: 0 for s in portfolio } >>> for s in portfolio: totals[s['name']] += s['shares'] >>> totals {'AA': 100, 'IBM': 150, 'CAT': 150, 'MSFT': 250, 'GE': 95} >>> ``` ## (c) Collections The `collections` module has a variety of classes for more specialized data manipulation. For example, the last example could be solved with a `Counter` like this: ```python >>> from collections import Counter >>> totals = Counter() >>> for s in portfolio: totals[s['name']] += s['shares'] >>> totals Counter({'MSFT': 250, 'IBM': 150, 'CAT': 150, 'AA': 100, 'GE': 95}) >>> ``` Counters are interesting in that they support other kinds of operations such as ranking and mathematics. For example: ```python >>> # Get the two most common holdings >>> totals.most_common(2) [('MSFT', 250), ('IBM', 150)] >>> >>> # Adding counters together >>> more = Counter() >>> more['IBM'] = 75 >>> more['AA'] = 200 >>> more['ACME'] = 30 >>> more Counter({'AA': 200, 'IBM': 75, 'ACME': 30}) >>> totals Counter({'MSFT': 250, 'IBM': 150, 'CAT': 150, 'AA': 100, 'GE': 95}) >>> totals + more Counter({'AA': 300, 'MSFT': 250, 'IBM': 225, 'CAT': 150, 'GE': 95, 'ACME': 30}) >>> ``` The `defaultdict` object can be used to group data. For example, suppose you want to make it easy to find all matching entries for a given name such as IBM. Try this: ```python >>> from collections import defaultdict >>> byname = defaultdict(list) >>> for s in portfolio: byname[s['name']].append(s) >>> byname['IBM'] [{'name': 'IBM', 'shares': 50, 'price': 91.1}, {'name': 'IBM', 'shares': 100, 'price': 70.44}] >>> byname['AA'] [{'name': 'AA', 'shares': 100, 'price': 32.2}] >>> ``` The key feature that makes this work is that a defaultdict automatically initializes elements for you--allowing an insertion of a new element and an `append()` operation to be combined together. ## (d) Data Analysis Challenge In the last exercise you just wrote some code to read CSV-data related to the Chicago Transit Authority. For example, you can grab the data as dictionaries like this: ```python >>> import readrides >>> rows = readrides.read_rides_as_dicts('Data/ctabus.csv') >>> ``` It would be a shame to do all of that work and then do nothing with the data. In this exercise, your task is this: write a program to answer the following three questions: 1. How many bus routes exist in Chicago? 2. How many people rode the number 22 bus on February 2, 2011? What about any route on any date of your choosing? 3. What is the total number of rides taken on each bus route? 4. What five bus routes had the greatest ten-year increase in ridership from 2001 to 2011? You are free to use any technique whatsoever to answer the above questions as long as it's part of the Python standard library (i.e., built-in datatypes, standard library modules, etc.). \[ [Solution](soln2_2.md) | [Index](index.md) | [Exercise 2.1](ex2_1.md) | [Exercise 2.3](ex2_3.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex2_3.md ================================================ \[ [Index](index.md) | [Exercise 2.2](ex2_2.md) | [Exercise 2.4](ex2_4.md) \] # Exercise 2.3 *Objectives:* - Iterate like a pro *Files Modified:* None. Iteration is an essential Python skill. In this exercise, we look at a number of common iteration idioms. Start the exercise by grabbing some rows of data from a CSV file. ```python >>> import csv >>> f = open('Data/portfolio.csv') >>> f_csv = csv.reader(f) >>> headers = next(f_csv) >>> headers ['name', 'shares', 'price'] >>> rows = list(f_csv) >>> from pprint import pprint >>> pprint(rows) [['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']] >>> ``` ## (a) Basic Iteration and Unpacking The `for` statement iterates over any sequence of data. For example: ```python >>> 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'] >>> ``` Unpack the values into separate variables if you need to: ```python >>> for name, shares, price in rows: print(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 >>> ``` It's somewhat common to use `_` or `__` as a throw-away variable if you don't care about one or more of the values. For example: ```python >>> for name, _, price in rows: print(name, price) AA 32.20 IBM 91.10 CAT 83.44 MSFT 51.23 GE 40.37 MSFT 65.10 IBM 70.44 >>> ``` If you don't know how many values are being unpacked, you can use `*` as a wildcard. Try this experiment in grouping the data by name: ```python >>> from collections import defaultdict >>> byname = defaultdict(list) >>> for name, *data in rows: byname[name].append(data) >>> byname['IBM'] [['50', '91.10'], ['100', '70.44']] >>> byname['CAT'] [['150', '83.44']] >>> for shares, price in byname['IBM']: print(shares, price) 50 91.10 100 70.44 >>> ``` ## (b) Counting with enumerate() `enumerate()` is a useful function if you ever need to keep a counter or index while iterating. For example, suppose you wanted an extra row number: ```python >>> for rowno, row in enumerate(rows): print(rowno, row) 0 ['AA', '100', '32.20'] 1 ['IBM', '50', '91.10'] 2 ['CAT', '150', '83.44'] 3 ['MSFT', '200', '51.23'] 4 ['GE', '95', '40.37'] 5 ['MSFT', '50', '65.10'] 6 ['IBM', '100', '70.44'] >>> ``` You can combine this with unpacking if you're careful about how you structure it: ```python >>> for rowno, (name, shares, price) in enumerate(rows): print(rowno, name, shares, price) 0 AA 100 32.20 1 IBM 50 91.10 2 CAT 150 83.44 3 MSFT 200 51.23 4 GE 95 40.37 5 MSFT 50 65.10 6 IBM 100 70.44 >>> ``` ## (c) Using the zip() function The `zip()` function is most commonly used to pair data. For example, recall that you created a `headers` variable: ```python >>> headers ['name', 'shares', 'price'] >>> ``` This might be useful to combine with the other row data: ```python >>> row = rows[0] >>> row ['AA', '100', '32.20'] >>> for col, val in zip(headers, row): print(col, val) name AA shares 100 price 32.20 >>> ``` Or maybe you can use it to make a dictionary: ```python >>> dict(zip(headers, row)) {'name': 'AA', 'shares': '100', 'price': '32.20'} >>> ``` Or maybe a sequence of dictionaries: ```python >>> for row in rows: record = dict(zip(headers, row)) print(record) {'name': 'AA', 'shares': '100', 'price': '32.20'} {'name': 'IBM', 'shares': '50', 'price': '91.10'} {'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.10'} {'name': 'IBM', 'shares': '100', 'price': '70.44'} >>> ``` ## (d) Generator Expressions A generator expression is almost exactly the same as a list comprehension except that it does not create a list. Instead, it creates an object that produces the results incrementally--typically for consumption by iteration. Try a simple example: ```python >>> nums = [1,2,3,4,5] >>> squares = (x*x for x in nums) >>> squares at 0x37caa8> >>> for n in squares: print(n) 1 4 9 16 25 >>> ``` You will notice that a generator expression can only be used once. Watch what happens if you do the for-loop again: ```python >>> for n in squares: print(n) >>> ``` You can manually get the results one-at-a-time if you use the `next()` function. Try this: ```python >>> squares = (x*x for x in nums) >>> next(squares) 1 >>> next(squares) 4 >>> next(squares) 9 >>> ``` Keeping typing `next()` to see what happens when there is no more data. If the task you are performing is more complicated, you can still take advantage of generators by writing a generator function and using the `yield` statement instead. For example: ```python >>> def squares(nums): for x in nums: yield x*x >>> for n in squares(nums): print(n) 1 4 9 16 25 >>> ``` We'll return to generator functions a little later in the course--for now, just view such functions as having the interesting property of feeding values to the `for`-statement. ## (e) Generator Expressions and Reduction Functions Generator expressions are especially useful for feeding data into functions such as `sum()`, `min()`, `max()`, `any()`, etc. Try some examples using the portfolio data from earlier. Carefully observe that these examples are missing some extra square brackets ([]) that appeared when using list comprehensions. ```python >>> from readport import read_portfolio >>> portfolio = read_portfolio('Data/portfolio.csv') >>> sum(s['shares']*s['price'] for s in portfolio) 44671.15 >>> min(s['shares'] for s in portfolio) 50 >>> any(s['name'] == 'IBM' for s in portfolio) True >>> all(s['name'] == 'IBM' for s in portfolio) False >>> sum(s['shares'] for s in portfolio if s['name'] == 'IBM') 150 >>> ``` Here is a subtle use of a generator expression in making comma separated values: ```python >>> s = ('GOOG',100,490.10) >>> ','.join(s) ... observe that it fails ... >>> ','.join(str(x) for x in s) # This works 'GOOG,100,490.1' >>> ``` The syntax in the above examples takes some getting used to, but the critical point is that none of the operations ever create a fully populated list of results. This gives you a big memory savings. However, you do need to make sure you don't go overboard with the syntax. ## (f) Saving a lot of memory In [Exercise 2.1](ex2_1.md) you wrote a function `read_rides_as_dicts()` that read the CTA bus data into a list of dictionaries. Using it requires a lot of memory. For example, let's find the day on which the route 22 bus had the greatest ridership: ```python >>> import tracemalloc >>> tracemalloc.start() >>> import readrides >>> rows = readrides.read_rides_as_dicts('Data/ctabus.csv') >>> rt22 = [row for row in rows if row['route'] == '22'] >>> max(rt22, key=lambda row: row['rides']) {'date': '06/11/2008', 'route': '22', 'daytype': 'W', 'rides': 26896} >>> tracemalloc.get_traced_memory() ... look at result. Should be around 220MB >>> ``` Now, let's try an example involving generators. Restart Python and try this: ```python >>> # RESTART >>> import tracemalloc >>> tracemalloc.start() >>> import csv >>> f = open('Data/ctabus.csv') >>> f_csv = csv.reader(f) >>> headers = next(f_csv) >>> rows = (dict(zip(headers,row)) for row in f_csv) >>> rt22 = (row for row in rows if row['route'] == '22') >>> max(rt22, key=lambda row: int(row['rides'])) {'date': '06/11/2008', 'route': '22', 'daytype': 'W', 'rides': 26896} >>> tracemalloc.get_traced_memory() ... look at result. Should be a LOT smaller than before >>> ``` Keep in mind that you just processed the entire dataset as if it was stored as a sequence of dictionaries. Yet, nowhere did you actually create and store a list of dictionaries. Not all problems can be structured in this way, but if you can work with data in an iterative manner, generator expressions can save a huge amount of memory. \[ [Solution](soln2_3.md) | [Index](index.md) | [Exercise 2.2](ex2_2.md) | [Exercise 2.4](ex2_4.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex2_4.md ================================================ \[ [Index](index.md) | [Exercise 2.3](ex2_3.md) | [Exercise 2.5](ex2_5.md) \] # Exercise 2.4 *Objectives:* - Make a new primitive type In most programs, you use the primitive types such as `int`, `float`, and `str` to represent data. However, you're not limited to just those types. The standard library has modules such as the `decimal` and `fractions` module that implement new primitive types. You can also make your own types as long as you understand the underlying protocols which make Python objects work. In this exercise, we'll make a new primitive type. There are a lot of little details to worry about, but this will give you a general sense for what's required. ## (a) Mutable Integers Python integers are normally immutable. However, suppose you wanted to make a mutable integer object. Start off by making a class like this: ```python # mutint.py class MutInt: __slots__ = ['value'] def __init__(self, value): self.value = value ``` Try it out: ```python >>> a = MutInt(3) >>> a <__main__.MutInt object at 0x10e79d408> >>> a.value 3 >>> a.value = 42 >>> a.value 42 >>> a + 10 Traceback (most recent call last): File "", line 1, in TypeError: unsupported operand type(s) for +: 'MutInt' and 'int' >>> ``` That's all very exciting except that nothing really works with this new `MutInt` object. Printing is horrible, none of the math operators work, and it's basically rather useless. Well, except for the fact that its value is mutable--it does have that. ## (b) Fixing output You can fix output by giving the object methods such as `__str__()`, `__repr__()`, and `__format__()`. For example: ```python # mint.py class MutInt: __slots__ = ['value'] def __init__(self, value): self.value = value def __str__(self): return str(self.value) def __repr__(self): return f'MutInt({self.value!r})' def __format__(self, fmt): return format(self.value, fmt) ``` Try it out: ```python >>> a = MutInt(3) >>> print(a) 3 >>> a MutInt(3) >>> f'The value is {a:*^10d}' The value is ****3***** >>> a.value = 42 >>> a MutInt(42) >>> ``` ## (c) Math Operators You can make an object work with various math operators if you implement the appropriate methods for it. However, it's your responsibility to recognize other types of data and implement the appropriate conversion code. Modify the `MutInt` class by giving it an `__add__()` method as follows: ```python class MutInt: __slots__ = ['value'] def __init__(self, value): self.value = value ... def __add__(self, other): if isinstance(other, MutInt): return MutInt(self.value + other.value) elif isinstance(other, int): return MutInt(self.value + other) else: return NotImplemented ``` With this change, you should find that you can add both integers and mutable integers. The result is a `MutInt` instance. Adding other kinds of numbers results in an error: ```python >>> a = MutInt(3) >>> b = a + 10 >>> b MutInt(13) >>> b.value = 23 >>> c = a + b >>> c MutInt(26) >>> a + 3.5 Traceback (most recent call last): File "", line 1, in TypeError: unsupported operand type(s) for +: 'MutInt' and 'float' >>> ``` One problem with the code is that it doesn't work when the order of operands is reversed. Consider: ```python >>> a + 10 MutInt(13) >>> 10 + a Traceback (most recent call last): File "", line 1, in TypeError: unsupported operand type(s) for +: 'int' and 'MutInt' >>> ``` This is occurring because the `int` type has no knowledge of `MutInt` and it's confused. This can be fixed by adding an `__radd__()` method. This method is called if the first attempt to call `__add__()` didn't work with the provided object. ```python class MutInt: __slots__ = ['value'] def __init__(self, value): self.value = value ... def __add__(self, other): if isinstance(other, MutInt): return MutInt(self.value + other.value) elif isinstance(other, int): return MutInt(self.value + other) else: return NotImplemented __radd__ = __add__ # Reversed operands ``` With this change, you'll find that addition works: ```python >>> a = MutInt(3) >>> a + 10 MutInt(13) >>> 10 + a MutInt(13) >>> ``` Since our integer is mutable, you can also make it recognize the in-place add-update operator `+=` by implementing the `__iadd__()` method: ```python class MutInt: __slots__ = ['value'] def __init__(self, value): self.value = value ... def __iadd__(self, other): if isinstance(other, MutInt): self.value += other.value return self elif isinstance(other, int): self.value += other return self else: return NotImplemented ``` This allows for interesting uses like this: ```python >>> a = MutInt(3) >>> b = a >>> a += 10 >>> a MutInt(13) >>> b # Notice that b also changes MutInt(13) >>> ``` That might seem kind of strange that `b` also changes, but there are subtle features like this with built-in Python objects. For example: ```python >>> a = [1,2,3] >>> b = a >>> a += [4,5] >>> a [1, 2, 3, 4, 5] >>> b [1, 2, 3, 4, 5] >>> c = (1,2,3) >>> d = c >>> c += (4,5) >>> c (1, 2, 3, 4, 5) >>> d # Explain difference from lists (1, 2, 3) >>> ``` ## (d) Comparisons One problem is that comparisons still don't work. For example: ```python >>> a = MutInt(3) >>> b = MutInt(3) >>> a == b False >>> a == 3 False >>> ``` You can fix this by adding an `__eq__()` method. Further methods such as `__lt__()`, `__le__()`, `__gt__()`, `__ge__()` can be used to implement other comparisons. For example: ```python class MutInt: __slots__ = ['value'] def __init__(self, value): self.value = value ... def __eq__(self, other): if isinstance(other, MutInt): return self.value == other.value elif isinstance(other, int): return self.value == other else: return NotImplemented def __lt__(self, other): if isinstance(other, MutInt): return self.value < other.value elif isinstance(other, int): return self.value < other else: return NotImplemented ``` Try it: ```python >>> a = MutInt(3) >>> b = MutInt(3) >>> a == b True >>> c = MutInt(4) >>> a < c True >>> a <= c Traceback (most recent call last): File "", line 1, in TypeError: '<=' not supported between instances of 'MutInt' and 'MutInt' >>> ``` The reason the `<=` operator is failing is that no `__le__()` method was provided. You could code it separately, but an easier way to get it is to use the `@total_ordering` decorator: ```python from functools import total_ordering @total_ordering class MutInt: __slots__ = ['value'] def __init__(self, value): self.value = value ... def __eq__(self, other): if isinstance(other, MutInt): return self.value == other.value elif isinstance(other, int): return self.value == other else: return NotImplemented def __lt__(self, other): if isinstance(other, MutInt): return self.value < other.value elif isinstance(other, int): return self.value < other else: return NotImplemented ``` `@total_ordering` fills in the missing comparison methods for you as long as you minimally provide an equality operator and one of the other relations. ## (e) Conversions Your new primitive type is almost complete. You might want to give it the ability to work with some common conversions. For example: ```python >>> a = MutInt(3) >>> int(a) Traceback (most recent call last): File "", line 1, in TypeError: int() argument must be a string, a bytes-like object or a number, not 'MutInt' >>> float(a) Traceback (most recent call last): File "", line 1, in TypeError: float() argument must be a string, a bytes-like object or a number, not 'MutInt' >>> ``` You can give your class an `__int__()` and `__float__()` method to fix this: ```python from functools import total_ordering @total_ordering class MutInt: __slots__ = ['value'] def __init__(self, value): self.value = value ... def __int__(self): return self.value def __float__(self): return float(self.value) ``` Now, you can properly convert: ```python >>> a = MutInt(3) >>> int(a) 3 >>> float(a) 3.0 >>> ``` As a general rule, Python never automatically converts data though. Thus, even though you gave the class an `__int__()` method, `MutInt` is still not going to work in all situations when an integer might be expected. For example, indexing: ```python >>> names = ['Dave', 'Guido', 'Paula', 'Thomas', 'Lewis'] >>> a = MutInt(1) >>> names[a] Traceback (most recent call last): File "", line 1, in TypeError: list indices must be integers or slices, not MutInt >>> ``` This can be fixed by giving `MutInt` an `__index__()` method that produces an integer. Modify the class like this: ```python from functools import total_ordering @total_ordering class MutInt: __slots__ = ['value'] def __init__(self, value): self.value = value ... def __int__(self): return self.value __index__ = __int__ # Make indexing work ``` **Discussion** Making a new primitive datatype is actually one of the most complicated programming tasks in Python. There are a lot of edge cases and low-level issues to worry about--especially with regard to how your type interacts with other Python types. Probably the key thing to keep in mind is that you can customize almost every aspect of how an object interacts with the rest of Python if you know the underlying protocols. If you're going to do this, it's advisable to look at the existing code for something similar to what you're trying to make. \[ [Solution](soln2_4.md) | [Index](index.md) | [Exercise 2.3](ex2_3.md) | [Exercise 2.5](ex2_5.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex2_5.md ================================================ \[ [Index](index.md) | [Exercise 2.4](ex2_4.md) | [Exercise 2.6](ex2_6.md) \] # Exercise 2.5 *Objectives:* - Look at memory allocation behavior of lists and dicts - Make a custom container *Files Created:* None ## (a) List growth Python lists are highly optimized for performing `append()` operations. Each time a list grows, it grabs a larger chunk of memory than it actually needs with the expectation that more data will be added to the list later. If new items are added and space is available, the `append()` operation stores the item without allocating more memory. Experiment with this feature of lists by using the `sys.getsizeof()` function on a list and appending a few more items. ```python >>> import sys >>> items = [] >>> sys.getsizeof(items) 64 >>> items.append(1) >>> sys.getsizeof(items) 96 >>> items.append(2) >>> sys.getsizeof(items) # Notice how the size does not increase 96 >>> items.append(3) >>> sys.getsizeof(items) # It still doesn't increase here 96 >>> items.append(4) >>> sys.getsizeof(items) # Not yet. 96 >>> items.append(5) >>> sys.getsizeof(items) # Notice the size has jumped 128 >>> ``` A list stores its items by reference. So, the memory required for each item is a single memory address. On a 64-bit machine, an address is typically 8 bytes. However, if Python has been compiled for 32-bits, it might be 4 bytes and the numbers for the above example will be half of what's shown. ## (b) Dictionary/Class Growth Python dictionaries (and classes) allow up to 5 values to be stored before their reserved memory doubles. Investigate by making a dictionary and adding a few more values to it: ```python >>> row = { 'route': '22', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354 } >>> sys.getsizeof(row) 240 >>> row['a'] = 1 >>> sys.getsizeof(row) 240 >>> row['b'] = 2 >>> sys.getsizeof(row) 368 >>> ``` Does the memory go down if you delete the item you just added? Food for thought: If you are creating large numbers of records, representing each record as a dictionary might not be the most efficient approach--you could be paying a heavy price for the convenience of having a dictionary. It might be better to consider the use of tuples, named tuples, or classes that define `__slots__`. ## (c) Changing Your Orientation (to Columns) You can often save a lot of memory if you change your view of data. For example, what happens if you read all of the bus data into a columns using this function? ```python # readrides.py ... def read_rides_as_columns(filename): ''' Read the bus ride data into 4 lists, representing columns ''' routes = [] dates = [] daytypes = [] numrides = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: routes.append(row[0]) dates.append(row[1]) daytypes.append(row[2]) numrides.append(int(row[3])) return dict(routes=routes, dates=dates, daytypes=daytypes, numrides=numrides) ``` In theory, this function should save a lot of memory. Let's analyze it before trying it. First, the datafile contained 577563 rows of data where each row contained four values. If each row is stored as a dictionary, then those dictionaries are minimally 240 bytes in size. ```python >>> nrows = 577563 # Number of rows in original file >>> nrows * 240 138615120 >>> ``` So, that's 138MB just for the dictionaries themselves. This does not include any of the values actually stored in the dictionaries. By switching to columns, the data is stored in 4 separate lists. Each list requires 8 bytes per item to store a pointer. So, here's a rough estimate of the list requirements: ```python >>> nrows * 4 * 8 18482016 >>> ``` That's about 18MB in list overhead. So, switching to a column orientation should save about 120MB of memory solely from eliminating all of the extra information that needs to be stored in dictionaries. Try using this function to read the bus data and look at the memory use. ```python >>> import tracemalloc >>> tracemalloc.start() >>> columns = read_rides_as_columns('Data/ctabus.csv') >>> tracemalloc.get_traced_memory() ... look at the result ... >>> ``` Does the result reflect the expected savings in memory from our rough calculations above? ## (d) Making a Custom Container - The Great Fake Out Storing the data in columns offers a much better memory savings, but the data is now rather weird to work with. In fact, none of our earlier analysis code from [Exercise 2.2](ex2_2.md) can work with columns. The reason everything is broken is that you've broken the data abstraction that was used in earlier exercises--namely the assumption that data is stored as a list of dictionaries. This can be fixed if you're willing to make a custom container object that "fakes" it. Let's do that. The earlier analysis code assumes the data is stored in a sequence of records. Each record is represented as a dictionary. Let's start by making a new "Sequence" class. In this class, we store the four columns of data that were being using in the `read_rides_as_columns()` function. ```python # readrides.py import collections ... class RideData(collections.abc.Sequence): def __init__(self): self.routes = [] # Columns self.dates = [] self.daytypes = [] self.numrides = [] ``` Try creating a `RideData` instance. You'll find that it fails with an error message like this: ```python >>> records = RideData() Traceback (most recent call last): ... TypeError: Can't instantiate abstract class RideData with abstract methods __getitem__, __len__ >>> ``` Carefully read the error message. It tells us what we need to implement. Let's add a `__len__()` and `__getitem__()` method. In the `__getitem__()` method, we'll make a dictionary. In addition, we'll create an `append()` method that takes a dictionary and unpacks it into 4 separate `append()` operations. ```python # readrides.py ... class RideData(collections.abc.Sequence): def __init__(self): # Each value is a list with all the values (a column) self.routes = [] self.dates = [] self.daytypes = [] self.numrides = [] def __len__(self): # All lists assumed to have the same length return len(self.routes) def __getitem__(self, index): return { 'route': self.routes[index], 'date': self.dates[index], 'daytype': self.daytypes[index], 'rides': self.numrides[index] } def append(self, d): self.routes.append(d['route']) self.dates.append(d['date']) self.daytypes.append(d['daytype']) self.numrides.append(d['rides']) ``` If you've done this correctly, you should be able to drop this object into the previously written `read_rides_as_dicts()` function. It involves changing only one line of code: ```python # readrides.py ... def read_rides_as_dicts(filename): ''' Read the bus ride data as a list of dicts ''' records = RideData() # <--- CHANGE THIS with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = { 'route': route, 'date': date, 'daytype': daytype, 'rides' : rides } records.append(record) return records ``` If you've done this right, old code should work exactly as it did before. For example: ```python >>> rows = readrides.read_rides_as_dicts('Data/ctabus.csv') >>> rows >>> len(rows) 577563 >>> rows[0] {'route': '3', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354} >>> rows[1] {'route': '4', 'date': '01/01/2001', 'daytype': 'U', 'rides': 9288} >>> rows[2] {'route': '6', 'date': '01/01/2001', 'daytype': 'U', 'rides': 6048} >>> ``` Run your earlier CTA code from [Exercise 2.2](ex2_2.md). It should work without modification, but use substantially less memory. ## (e) Challenge What happens when you take a slice of ride data? ```python >>> r = rows[0:10] >>> r ... look at result ... >>> ``` It's probably going to look a little crazy. Can you modify the `RideData` class so that it produces a proper slice that looks like a list of dictionaries? For example, like this: ```python >>> rows = readrides.read_rides_as_dicts('Data/ctabus.csv') >>> rows >>> len(rows) 577563 >>> r = rows[0:10] >>> r >>> len(r) 10 >>> r[0] {'route': '3', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354} >>> r[1] {'route': '4', 'date': '01/01/2001', 'daytype': 'U', 'rides': 9288} >>> ``` \[ [Solution](soln2_5.md) | [Index](index.md) | [Exercise 2.4](ex2_4.md) | [Exercise 2.6](ex2_6.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex2_6.md ================================================ \[ [Index](index.md) | [Exercise 2.5](ex2_5.md) | [Exercise 3.1](ex3_1.md) \] # Exercise 2.6 *Objectives:* - Explore the power of having first-class objects. - Better understand Python's memory model *Files Created:* `reader.py` In previous exercises, you wrote various functions for reading CSV data in different files. Surely this is a problem that could be generalized in some way. In this exercise, we explore that idea. ## (a) First-class Data In the file `Data/portfolio.csv`, you read data organized as columns that look like this: ```python "AA",100,32.20 "IBM",50,91.10 ... ``` In previous code, this data was processed by hard-coding all of the type conversions. For example: ```python rows = csv.reader(f) 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 operations. Make a Python list that contains the conversions you want to carry out on each column: ```python >>> coltypes = [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. 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'] >>> ``` Zip the column types with the row and look at the result: ```python >>> r = list(zip(coltypes, row)) >>> r [(, 'AA'), (, '100'), (,'32.20')] >>> ``` You will notice that this has paired a type conversion with a value. For example, `int` is paired with the value `'100'`. Now, try this: ```python >>> record = [func(val) for func, val in zip(coltypes, row)] >>> record ['AA', 100, 32.2] >>> ``` 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). You can take it a step further and make dictionaries by using the column headers. For example: ```python >>> dict(zip(headers, record)) {'name': 'AA', 'shares': 100, 'price': 32.2} >>> ``` If you prefer, you can perform all of these steps at once using a dictionary comprehension: ```python >>> { name:func(val) for name, func, val in zip(headers, coltypes, row) } {'name': 'AA', 'shares': 100, 'price': 32.2} >>> ``` ## (b) Making a Parsing Utility Function Create a new file `reader.py`. In that file, define a utility function `read_csv_as_dicts()` that reads a file of CSV data into a list of dictionaries where the user specifies the type conversions for each column. Here is how it should work: ```python >>> import reader >>> portfolio = reader.read_csv_as_dicts('Data/portfolio.csv', [str,int,float]) >>> for s in portfolio: print(s) {'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} >>> ``` Or, if you wanted to read the CTA data: ```python >>> rows = reader.read_csv_as_dicts('Data/ctabus.csv', [str,str,str,int]) >>> len(rows) 577563 >>> rows[0] {'daytype': 'U', 'route': '3', 'rides': 7354, 'date': '01/01/2001'} >>> ``` ## (c) Memory Revisited In [Exercise 2.1](ex2_1.md) we explored memory use and in [Exercise 2.2](ex2_2.md), we determined that there were 181 unique bus routes in Chicago. ```python >>> routes = { row['route'] for row in rows } >>> len(routes) 181 >>> ``` Question: How many unique route string objects are contained in the ride data? Instead of building a set of routes, build a set of object ids instead: ```python >>> routeids = { id(row['route']) for row in rows } >>> len(routeids) 542305 >>> ``` Think about this for a moment--there are only 181 distinct route names, but the resulting list of dictionaries contains 542305 different route strings. Maybe this is something that could be fixed with a bit of caching or object reuse. As it turns out, Python has a function that can be used to cache strings, `sys.intern()`. For example: ```python >>> a = 'hello world' >>> b = 'hello world' >>> a is b False >>> import sys >>> a = sys.intern(a) >>> b = sys.intern(b) >>> a is b True >>> ``` Restart Python and try this: ```python >>> # ------------------ RESTART ---------``` >>> import tracemalloc >>> tracemalloc.start() >>> from sys import intern >>> import reader >>> rows = reader.read_csv_as_dicts('Data/ctabus.csv', [intern, str, str, int]) >>> routeids = { id(row['route']) for row in rows } >>> len(routeids) 181 >>> ``` Take a look at the memory use. ```python >>> tracemalloc.get_traced_memory() ... look at result ... >>> ``` The memory should drop quite a bit. Observation: There is a lot of repetition involving dates as well. What happens if you also cache the date strings? ```python >>> # ------------------ RESTART ---------``` >>> import tracemalloc >>> tracemalloc.start() >>> from sys import intern >>> import reader >>> rows = reader.read_csv_as_dicts('Data/ctabus.csv', [intern, intern, str, int]) >>> tracemalloc.get_traced_memory() ... look at result ... >>> ``` ## (d) Special Challenge Project In [Exercise 2.5](ex2_5.md), we created a class `RideData` that stored all of the bus data in columns, but actually presented the data to a user as a sequence of dictionaries. It saved a lot of memory through various forms of magic. Can you generalize that idea? Specifically, can you make a general purpose function `read_csv_as_columns()` that works like this: ```python >>> data = read_csv_as_columns('Data/ctabus.csv', types=[str, str, str, int]) >>> data <__main__.DataCollection object at 0x102b45048> >>> len(data) 577563 >>> data[0] {'route': '3', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354} >>> data[1] {'route': '4', 'date': '01/01/2001', 'daytype': 'U', 'rides': 9288} >>> data[2] {'route': '6', 'date': '01/01/2001', 'daytype': 'U', 'rides': 6048} >>> ``` This function is supposed to be general purpose--you can give it any file and a list of the column types and it will read the data. The data is read into a class `DataCollection` that stores the data as columns internally. The data presents itself as a sequence of dictionaries when accessed however. Try using this function with the string interning trick in the last part. How much memory does it take to store all of the ride data now? Can you still use this function with your earlier CTA analysis code? ## (e) Deep Thought In this exercise, you have written two functions, `read_csv_as_dicts()` and `read_csv_as_columns()`. These functions present data to the user in the same way. For example: ```python >>> data1 = read_csv_as_dicts('Data/ctabus.csv', [str, str, str, int]) >>> len(data1) 577563 >>> data1[0] {'route': '3', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354} >>> data1[1] {'route': '4', 'date': '01/01/2001', 'daytype': 'U', 'rides': 9288} >>> >>> data2 = read_csv_as_columns('Data/ctabus.csv', [str, str, str, int]) >>> len(data2) 577563 >>> data2[0] {'route': '3', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354} >>> data2[1] {'route': '4', 'date': '01/01/2001', 'daytype': 'U', 'rides': 9288} >>> ``` In fact, you can use either function in the CTA data analysis code that you wrote. Yet, under the covers completely different things are happening. The `read_csv_as_columns()` function is storing the data in a different representation. It's relying on Python sequence protocols (magic methods) to present information to you in a more useful way. This is really part of a much bigger programming concept of "Data Abstraction". When writing programs, the way in which data is presented is often more important than how the data is actually put together under the hood. Although we're presenting the data as a sequence of dictionaries, there's a great deal of flexibility in how that actually happens behind the scenes. That's a powerful idea and something to think about when writing your own programs. \[ [Solution](soln2_6.md) | [Index](index.md) | [Exercise 2.5](ex2_5.md) | [Exercise 3.1](ex3_1.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex3_1.md ================================================ \[ [Index](index.md) | [Exercise 2.6](ex2_6.md) | [Exercise 3.2](ex3_2.md) \] # Exercise 3.1 *Objectives:* - Define a simple class *Files Modified:* `stock.py` In [Exercise 1.5](ex1_5.md), you defined a simple class `Stock` for representing a holding of stock. In this exercise, we're simply going to add a few features to that class as well as write some utility functions. ## (a) Adding a new method Add a new method `sell(nshares)` to Stock that sells a certain number of shares by decrementing the share count. Have it work like this: ```python >>> s = Stock('GOOG',100,490.10) >>> s.shares 100 >>> s.sell(25) >>> s.shares 75 >>> ``` ## (b) Reading a portfolio Add a function `read_portfolio()` to your `stock.py` program that reads a file of portfolio data into a list of `Stock` objects. Here's how it should work: ```python >>> portfolio = read_portfolio('Data/portfolio.csv') >>> for s in portfolio: print(s) <__main__.Stock object at 0x3902f0> <__main__.Stock object at 0x390270> <__main__.Stock object at 0x390330> <__main__.Stock object at 0x390370> <__main__.Stock object at 0x3903b0> <__main__.Stock object at 0x3903f0> <__main__.Stock object at 0x390430> >>> ``` You already wrote a similar function as part of [Exercise 2.3](ex2_3.md). Design discussion: Should `read_portfolio()` be a separate function or part of the class definition? ## (c) Printing a Table Take the data read in part (b) and use it to make a nicely formatted table. For example: ```python >>> portfolio = read_portfolio('Data/portfolio.csv') >>> for s in portfolio: print('%10s %10d %10.2f' % (s.name, s.shares, s.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 >>> ``` Take this code and put it in a function `print_portfolio()` that produces the same output, but additionally adds some table headers. For example: ```python >>> portfolio = read_portfolio('Data/portfolio.csv') >>> print_portfolio(portfolio) 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 >>> ``` \[ [Solution](soln3_1.md) | [Index](index.md) | [Exercise 2.6](ex2_6.md) | [Exercise 3.2](ex3_2.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex3_2.md ================================================ \[ [Index](index.md) | [Exercise 3.1](ex3_1.md) | [Exercise 3.3](ex3_3.md) \] # Exercise 3.2 *Objectives:* - Learn about attribute access - Learn how use getattr(), setattr(), and related functions. - Experiment with bound methods. *Files Created:* `tableformat.py` ## (a) The Three Operations The entire Python object system consists of just three core operations: getting, setting, and deleting of attributes. Normally, these are accessed via the dot (.) like this: ```python >>> s = Stock('GOOG', 100, 490.1) >>> s.name # get 'GOOG' >>> s.shares = 50 # set >>> del s.shares # delete >>> ``` The three operations are also available as functions. For example: ```python >>> getattr(s, 'name') # Same as s.name 'GOOG' >>> setattr(s, 'shares', 50) # Same as s.shares = 50 >>> delattr(s, 'shares') # Same as del s.shares >>> ``` An additional function `hasattr()` can be used to probe an object for the existence of an attribute: ```python >>> hasattr(s, 'name') True >>> hasattr(s, 'blah') False >>> ``` ## (b) Using getattr() The `getattr()` function is extremely useful for writing code that processes objects in an extremely generic way. To illustrate, consider this example which prints out a set of user-defined attributes: ```python >>> s= Stock('GOOG', 100, 490.1) >>> fields = ['name','shares','price'] >>> for name in fields: print(name, getattr(s, name)) name GOOG shares 100 price 490.1 >>> ``` ## (c) Table Output In [Exercise 3.1](ex3_1.md), you wrote a function `print_portfolio()` that made a nicely formatted table. That function was custom tailored to a list of `Stock` objects. However, it can be completely generalized to work with any list of objects using the technique in part (b). Create a new module called `tableformat.py`. In that program, write a function `print_table()` that takes a sequence (list) of objects, a list of attribute names, and prints a nicely formatted table. For example: ```python >>> import stock >>> import tableformat >>> portfolio = stock.read_portfolio('Data/portfolio.csv') >>> tableformat.print_table(portfolio, ['name','shares','price']) name shares price ---------- ---------- ---------- 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 >>> tableformat.print_table(portfolio,['shares','name']) shares name ---------- ---------- 100 AA 50 IBM 150 CAT 200 MSFT 95 GE 50 MSFT 100 IBM >>> ``` For simplicity, just have the `print_table()` function print each field in a 10-character wide column. ## (d) Bound Methods It may be surprising, but method calls are layered onto the machinery used for simple attributes. Essentially, a method is an attribute that executes when you add the required parentheses () to call it like a function. For example: ```python >>> s = Stock('GOOG',100,490.10) >>> s.cost # Looks up the method > >>> s.cost() # Looks up and calls the method 49010.0 >>> # Same operations using getattr() >>> getattr(s, 'cost') > >>> getattr(s, 'cost')() 49010.0 >>> ``` A bound method is attached to the object where it came from. If that object is modified, the method will see the modifications. You can view the original object by inspecting the `__self__` attribute of the method. ```python >>> c = s.cost >>> c() 49010.0 >>> s.shares = 75 >>> c() 36757.5 >>> c.__self__ <__main__.Stock object at 0x409530> >>> c.__func__ >>> c.__func__(c.__self__) # This is what happens behind the scenes of calling c() 36757.5 >>> ``` Try it with the `sell()` method just to make sure you understand the mechanics: ```python >>> f = s.sell >>> f.__func__(f.__self__, 25) # Same as s.sell(25) >>> s.shares 50 >>> ``` \[ [Solution](soln3_2.md) | [Index](index.md) | [Exercise 3.1](ex3_1.md) | [Exercise 3.3](ex3_3.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex3_3.md ================================================ \[ [Index](index.md) | [Exercise 3.2](ex3_2.md) | [Exercise 3.4](ex3_4.md) \] # Exercise 3.3 *Objectives:* - Learn about class variables and class methods *Files Modified:* `stock.py`, `reader.py` Instances of the `Stock` class defined in the previous exercise are normally created as follows: ```python >>> s = Stock('GOOG', 100, 490.1) >>> ``` However, the `read_portfolio()` function also creates instances from rows of data read from files. For example, code such as the following is used: ```python >>> import csv >>> f = open('Data/portfolio.csv') >>> rows = csv.reader(f) >>> headers = next(rows) >>> row = next(rows) >>> row ['AA', '100', '32.20'] >>> s = Stock(row[0], int(row[1]), float(row[2])) >>> ``` ## (a) Alternate constructors Perhaps the creation of a `Stock` from a row of raw data is better handled by an alternate constructor. Modify the `Stock` class so that it has a `types` class variable and `from_row()` class method like this: ```python class Stock: types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls.types, row)] return cls(*values) ... ``` Here's how to test it: ```python >>> row = ['AA', '100', '32.20'] >>> s = Stock.from_row(row) >>> s.name 'AA' >>> s.shares 100 >>> s.price 32.2 >>> s.cost() 3220.0000000000005 >>> ``` Carefully observe how the string values in the row have been converted to a proper type. ## (b) Class variables and inheritance Class variables such as `types` are sometimes used as a customization mechanism when inheritance is used. For example, in the `Stock` class, the types can be easily changed in a subclass. Try this example which changes the `price` attribute to a `Decimal` instance (which is often better suited to financial calculations): ```python >>> from decimal import Decimal >>> class DStock(Stock): types = (str, int, Decimal) >>> row = ['AA', '100', '32.20'] >>> s = DStock.from_row(row) >>> s.price Decimal('32.20') >>> s.cost() Decimal('3220.0') >>> ``` **Design Discussion** The problem being addressed in this exercise concerns the conversion of data read from a file. Would it make sense to perform these conversions in the `__init__()` method of the `Stock` class instead? For example: ```python class Stock: def __init__(self, name, shares, price): self.name = str(name) self.shares = int(shares) self.price = float(price) ``` By doing this, you would convert a row of data as follows: ```python >>> row = ['AA', '100', '32.20'] >>> s = Stock(*row) >>> s.name 'AA' >>> s.shares 100 >>> s.price 32.2 >>> ``` Is this good or bad? What are your thoughts? On the whole, I think it's a questionable design since it mixes two different things together--the creation of an instance and the conversion of data. Plus, the implicit conversions in `__init__()` limit flexibility and might introduce weird bugs if a user isn't paying careful attention. ## (c) Reading Objects Change the `read_portfolio()` function to use the new `Stock.from_row()` method that you wrote. Point of thought: Which version of code do you like better? The version of `read_portfolio()` that performed the type conversions or the one that performs conversions in the `Stock.from_row()` method? Think about data abstraction. Can you modify `read_portfolio()` to create objects using a class other than `Stock`? For example, can the user select the class that they want? ## (d) Generalizing A useful feature of class-methods is that you can use them to put a highly uniform instance creation interface on a wide variety of classes and write general purpose utility functions that use them. Earlier, you created a file `reader.py` that had some functions for reading CSV data. Add the following `read_csv_as_instances()` function to the file which accepts a class as input and uses the class `from_row()` method to create a list of instances: ```python # reader.py ... def read_csv_as_instances(filename, cls): ''' Read a CSV file into a list of instances ''' records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: records.append(cls.from_row(row)) return records ``` Get rid of the `read_portfolio()` function--you don't need that anymore. If you want to read a list of `Stock` objects, do this: ```python >>> # Read a portfolio of Stock instances >>> from reader import read_csv_as_instances >>> from stock import Stock >>> portfolio = read_csv_as_instances('Data/portfolio.csv', Stock) >>> portfolio [<__main__.Stock object at 0x100674748>, <__main__.Stock object at 0x1006746d8>, <__main__.Stock object at 0x1006747b8>, <__main__.Stock object at 0x100674828>, <__main__.Stock object at 0x100674898>, <__main__.Stock object at 0x100674908>, <__main__.Stock object at 0x100674978>] >>> ``` Here is another example of how you might use `read_csv_as_instances()` with a completely different class: ```python >>> class Row: def __init__(self, route, date, daytype, numrides): self.route = route self.date = date self.daytype = daytype self.numrides = numrides @classmethod def from_row(cls, row): return cls(row[0], row[1], row[2], int(row[3])) >>> rides = read_csv_as_instances('Data/ctabus.csv', Row) >>> len(rides) 577563 >>> ``` **Discussion** This exercise illustrates the two most common uses of class variables and class methods. Class variables are often used to hold a global parameter (e.g., a configuration setting) that is shared across all instances. Sometimes subclasses will inherit from the base class and override the setting to change behavior. Class methods are most commonly used to implement alternate constructors as shown. A common way to spot such class methods is to look for the word "from" in the name. For example, here is an example on built-in dictionaries: ```python >>> d = dict.fromkeys(['a','b','c'], 0) # class method >>> d {'a': 0, 'c': 0, 'b': 0} >>> ``` \[ [Solution](soln3_3.md) | [Index](index.md) | [Exercise 3.2](ex3_2.md) | [Exercise 3.4](ex3_4.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex3_4.md ================================================ \[ [Index](index.md) | [Exercise 3.3](ex3_3.md) | [Exercise 3.5](ex3_5.md) \] # Exercise 3.4 *Objectives:* - Learn how to encapsulate object internals using private attributes, properties, and slots *Files Modified:* `stock.py` ## (a) Private attributes As a general rule, attributes that are internal to a class should have a leading underscore. In the previous exercise, the `Stock` class had a `types` class variable that was used for converting rows of data. Change the code so that this variable has a leading underscore on it. ## (b) Properties for computed attributes Earlier, you defined a class `Stock`. For example: ```python >>> s = Stock('GOOG',100,490.10) >>> s.name 'GOOG' >>> s.shares 100 >>> s.price 490.1 >>> s.cost() 49010.0 >>> ``` Using a property, turn `cost()` into an attribute that no longer requires the parentheses. For example: ```python >>> s = Stock('GOOG', 100, 490.1) >>> s.cost # Property. Computes the cost 49010.0 >>> ``` ## (c) Enforcing Validation Rules Using properties and private attributes, modify the `shares` attribute of the `Stock` class so that it can only be assigned a non-negative integer value. In addition, modify the `price` attribute so that it can only be assigned a non-negative floating point value. The new object should work almost exactly the same as the old one except for extra type and value checking. ```python >>> s = Stock('GOOG', 100, 490.10) >>> s.shares = 50 # OK >>> s.shares = '50' Traceback (most recent call last): ... TypeError: Expected integer >>> s.shares = -10 Traceback (most recent call last): ... ValueError: shares must be >= 0 >>> s.price = 123.45 # OK >>> s.price = '123.45' Traceback (most recent call last): ... TypeError: Expected float >>> s.price = -10.0 Traceback (most recent call last): ... ValueError: price must be >= 0 >>> ``` ## (d) Adding `__slots__` Modify your new `Stock` class to use `__slots__`. You will find that you have to use a different set of attribute names than before--specifically, you will have to list the private attribute names (e.g., if a property is storing a value in an attribute `_shares`, that is the name you list in `__slots__`). Verify that the class still works and that you can no longer add new attributes. ```python >>> s = Stock('GOOG', 100, 490.10) >>> s.spam = 42 Traceback (most recent call last): File "", line 1, in AttributeError: 'Stock' object has no attribute 'spam' >>> ``` ## (e) Reconciling Types In the current `Stock` class, there is a `_types` class variable that gives conversions when reading from a file, but there are also properties that are enforcing types. Who is in charge of this show? Fix the property definitions so that they use the types specified in the `_types` class variable. Make sure the properties work when types are changed via subclassing. For example: ```python >>> from decimal import Decimal >>> class DStock(Stock): _types = (str, int, Decimal) >>> s = DStock('AA', 50, Decimal('91.1')) >>> s.price = 92.3 Traceback (most recent call last): ... TypeError: Expected a Decimal >>> ``` **Discussion** The resulting `Stock` class at the end of this exercise is a muddled mess of properties, type checking, constructors, and other details. Imagine how unpleasant it would be to maintain code that featured dozens or hundreds of such class definitions. We're going to figure out how to simplify things considerably, but it's going to take some time and some more advanced techniques. Stay tuned. \[ [Solution](soln3_4.md) | [Index](index.md) | [Exercise 3.3](ex3_3.md) | [Exercise 3.5](ex3_5.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex3_5.md ================================================ \[ [Index](index.md) | [Exercise 3.4](ex3_4.md) | [Exercise 3.6](ex3_6.md) \] # Exercise 3.5 *Objectives:* - Learn how to use inheritance to write extensible code. - See a practical use of inheritance by writing a program that must output data in a variety of user-selectable formats such as plain-text, CSV, and HTML. *Files Modified:* `tableformat.py` One major use of classes in Python is in writing code that be extended/adapted in various ways. To illustrate, in [Exercise 3.2](ex3_2.md) you created a function `print_table()` that made tables. You used this to make output from the `portfolio` list. For example: ```python >>> import stock >>> import reader >>> import tableformat >>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock) >>> tableformat.print_table(portfolio, ['name','shares','price']) name shares price ---------- ---------- ---------- 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 >>> ``` Suppose you wanted the `print_table()` function to be able to make tables in any number of output formats such as CSV, XML, HTML, Excel, etc. Trying to modify the function to support all of those output formats at once would be painful. A better way to do this involves moving the output-related formatting code to a class and using inheritance to implement different output formats. ## (a) Defining a generic formatter class Add the following class definition to the `tableformat.py` file: ```python class TableFormatter: def headings(self, headers): raise NotImplementedError() def row(self, rowdata): raise NotImplementedError() ``` Now, modify the `print_table()` function so that it accepts a `TableFormatter` instance and invokes methods on it to produce output: ```python def print_table(records, fields, formatter): formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) ``` These two classes are meant to be used together. For example: ``` >>> import stock, reader, tableformat >>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock) >>> formatter = tableformat.TableFormatter() >>> tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter) Traceback (most recent call last): ... NotImplementedError >>> ``` For now, it doesn't do much of anything interesting. You'll fix this in the next section. ## (b) Implementing a concrete formatter The `TableFormatter` isn't meant to be used by itself. Instead, it is merely a base for other classes that will implement the formatting. Add the following class to `tableformat.py`: ```python class TextTableFormatter(TableFormatter): def headings(self, headers): print(' '.join('%10s' % h for h in headers)) print(('-'*10 + ' ')*len(headers)) def row(self, rowdata): print(' '.join('%10s' % d for d in rowdata)) ``` Now, use your new class as follows: ```python >>> import stock, reader, tableformat >>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock) >>> formatter = tableformat.TextTableFormatter() >>> tableformat.print_table(portfolio, ['name','shares','price'], formatter) name shares price ---------- ---------- ---------- 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 >>> ``` ## (c) Adding More Implementations Create a class `CSVTableFormatter` that allows output to be generated in CSV format: ```python >>> import stock, reader, tableformat >>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock) >>> formatter = tableformat.CSVTableFormatter() >>> tableformat.print_table(portfolio, ['name','shares','price'], formatter) name,shares,price 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 >>> ``` Create a class `HTMLTableFormatter` that generates output in HTML format: ```python >>> import stock, reader, tableformat >>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock) >>> formatter = tableformat.HTMLTableFormatter() >>> tableformat.print_table(portfolio, ['name','shares','price'], formatter) name shares price 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 >>> ``` ## (d) Making it Easier To Choose One problem with using inheritance is the added complexity of picking different classes to use (e.g., remembering the names, using the right `import` statements, etc.). A factory function can simplify this. Add a function `create_formatter()` to your `tableformat.py` file that allows a user to more easily make a formatter by specifying a format such as `'text'`, `'csv'`, or `'html'`. For example: ```python >>> from tableformat import create_formatter, print_table >>> formatter = create_formatter('html') >>> print_table(portfolio, ['name','shares','price'], formatter) name shares price 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 >>> ``` **Discussion** The `TableFormatter` class in this exercise is an example of something known as an "Abstract Base Class." It's not something that's meant to be used directly. Instead, it's serving as a kind of interface specification for a program component--in this case the various output formats. Essentially, the code that produces the table will be programmed against the abstract base class with the expectation that a user will provide a suitable implementation. As long as all of the required methods have been implemented, it should all just "work" (fingers crossed). \[ [Solution](soln3_5.md) | [Index](index.md) | [Exercise 3.4](ex3_4.md) | [Exercise 3.6](ex3_6.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex3_6.md ================================================ \[ [Index](index.md) | [Exercise 3.5](ex3_5.md) | [Exercise 3.7](ex3_7.md) \] # Exercise 3.6 *Objectives:* - Learn how to customize the behavior of objects by redefining special methods. - Change the way that user-defined objects get printed - Make objects comparable - Create a context manager *Files Created:* None *Files Modified:* `stock.py` ## (a) Better output for representing objects All Python objects have two string representations. The first representation is created by string conversion via `str()` (which is called by `print`). The string representation is usually a nicely formatted version of the object meant for humans. The second representation is a code representation of the object created by `repr()` (or simply by viewing a value in the interactive shell). The code representation typically shows you the code that you have to type to get the object. Here is an example that illustrates using dates: ```python >>> from datetime import date >>> d = date(2008, 7, 5) >>> print(d) # uses str() 2008-07-05 >>> d # uses repr() datetime.date(2008, 7, 5) >>> ``` There are several techniques for obtaining the `repr()` string in output: ```python >>> print('The date is', repr(d)) The date is datetime.date(2008, 7, 5) >>> print(f'The date is {d!r}') The date is datetime.date(2008, 7, 5) >>> print('The date is %r' % d) The date is datetime.date(2008, 7, 5) >>> ``` Modify the `Stock` object that you've created so that the `__repr__()` method produces more useful output. For example: ```python >>> goog = Stock('GOOG', 100, 490.10) >>> goog Stock('GOOG', 100, 490.1) >>> ``` See what happens when you read a portfolio of stocks and view the resulting list after you have made these changes. For example: ```python >>> import stock, reader >>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock) >>> portfolio [Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44), Stock('MSFT', 200, 51.23), Stock('GE', 95, 40.37), Stock('MSFT', 50, 65.1), Stock('IBM', 100, 70.44)] >>> ``` ## (b) Making objects comparable What happens if you create two identical `Stock` objects and try to compare them? Find out: ```python >>> a = Stock('GOOG', 100, 490.1) >>> b = Stock('GOOG', 100, 490.1) >>> a == b False >>> ``` You can fix this by giving the `Stock` class an `__eq__()` method. For example: ```python class Stock: ... def __eq__(self, other): return isinstance(other, Stock) and ((self.name, self.shares, self.price) == (other.name, other.shares, other.price)) ... ``` Make this change and try comparing two objects again. ## (c) A Context Manager In [Exercise 3.5](ex3_5.md), you made it possible for users to make nicely formatted tables. For example: ```python >>> from tableformat import create_formatter >>> formatter = create_formatter('text') >>> print_table(portfolio, ['name','shares','price'], formatter) name shares price ---------- ---------- ---------- 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 >>> ``` One issue with the code is that all tables are printed to standard out (`sys.stdout`). Suppose you wanted to redirect the output to a file or some other location. In the big picture, you might modify all of the table formatting code to allow a different output file. However, in a pinch, you could also solve this with a context manager. Define the following context manager: ```python >>> import sys >>> class redirect_stdout: def __init__(self, out_file): self.out_file = out_file def __enter__(self): self.stdout = sys.stdout sys.stdout = self.out_file return self.out_file def __exit__(self, ty, val, tb): sys.stdout = self.stdout ``` This context manager works by making a temporary patch to `sys.stdout` to cause all output to redirect to a different file. On exit, the patch is reverted. Try it out: ```python >>> from tableformat import create_formatter >>> formatter = create_formatter('text') >>> with redirect_stdout(open('out.txt', 'w')) as file: tableformat.print_table(portfolio, ['name','shares','price'], formatter) file.close() >>> # Inspect the file >>> print(open('out.txt').read()) name shares price ---------- ---------- ---------- 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 >>> ``` \[ [Solution](soln3_6.md) | [Index](index.md) | [Exercise 3.5](ex3_5.md) | [Exercise 3.7](ex3_7.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex3_7.md ================================================ \[ [Index](index.md) | [Exercise 3.6](ex3_6.md) | [Exercise 3.8](ex3_8.md) \] # Exercise 3.7 *Objectives:* - Type checking and interfaces - Abstract base classes *Files Modified:* `tableformat.py` In [Exercise 3.5](ex3_5.md), we modified the `tableformat.py` file to have a `TableFormatter` class and to use various subclasses for different output formats. In this exercise, we extend that code a bit more. ## (a) Interfaces and type checking Modify the `print_table()` function so that it checks if the supplied formatter instance inherits from `TableFormatter`. If not, raise a `TypeError`. Your new code should catch situations like this: ```python >>> import stock, reader, tableformat >>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock) >>> class MyFormatter: def headings(self,headers): pass def row(self,rowdata): pass >>> tableformat.print_table(portfolio, ['name','shares','price'], MyFormatter()) Traceback (most recent call last): ... TypeError: Expected a TableFormatter >>> ``` Adding a check like this might add some degree of safety to the program. However you should still be aware that type-checking is rather weak in Python. There is no guarantee that the object passed as a formatter will work correctly even if it happens to inherit from the proper base class. This next part addresses that issue. ## (b) Abstract Base Classes Modify the `TableFormatter` base class so that it is defined as a proper abstract base class using the `abc` module. Once you have done that, try this experiment: ```python >>> class NewFormatter(TableFormatter): def headers(self, headings): pass def row(self, rowdata): pass >>> f = NewFormatter() Traceback (most recent call last): File "", line 1, in TypeError: Can't instantiate abstract class NewFormatter with abstract methods headings >>> ``` Here, the abstract base class caught a spelling error in the class--the fact that the `headings()` method was incorrectly given as `headers()`. ## (c) Algorithm Template Classes The file `reader.py` contains two functions, `read_csv_as_dicts()` and `read_csv_as_instances()`. Both of those functions are almost identical--there is just one tiny bit of code that's different. Maybe that code could be consolidated into a class definition of some sort. Add the following class to the `reader.py` file: ```python # reader.py import csv from abc import ABC, abstractmethod class CSVParser(ABC): def parse(self, filename): records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = self.make_record(headers, row) records.append(record) return records @abstractmethod def make_record(self, headers, row): pass ``` This code provides a shell (or template) of the CSV parsing functionality. To use it, you subclass it, add any additional attributes you might need, and redefine the `make_record()` method. For example: ```python class DictCSVParser(CSVParser): def __init__(self, types): self.types = types def make_record(self, headers, row): return { name: func(val) for name, func, val in zip(headers, self.types, row) } class InstanceCSVParser(CSVParser): def __init__(self, cls): self.cls = cls def make_record(self, headers, row): return self.cls.from_row(row) ``` Add the above classes to the `reader.py` file. Here's how you would use one of them: ```python >>> from reader import DictCSVParser >>> parser = DictCSVParser([str, int, float]) >>> port = parser.parse('Data/portfolio.csv') >>> ``` It works, but it's kind of annoying. To fix this, reimplement the `read_csv_as_dicts()` and `read_csv_as_instances()` functions to use these classes. Your refactored code should work exactly the same way that it did before. For example: ```python >>> import reader >>> import stock >>> port = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock) >>> ``` \[ [Solution](soln3_7.md) | [Index](index.md) | [Exercise 3.6](ex3_6.md) | [Exercise 3.8](ex3_8.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex3_8.md ================================================ \[ [Index](index.md) | [Exercise 3.7](ex3_7.md) | [Exercise 4.1](ex4_1.md) \] # Exercise 3.8 *Objectives:* - Learn about mixin classes and cooperative inheritance *Files Modified:* `tableformat.py` ## (a) The Trouble with Column Formatting If you go all the way back to [Exercise 3.1](ex3_1.md), you wrote a function `print_portfolio()` that produced a table like this: ```python >>> portfolio = read_portfolio('Data/portfolio.csv') >>> print_portfolio(portfolio) 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 `print_table()` function developed in the last several exercises almost replaces this functionality--almost. The one problem that it has is that it can't precisely format the content of each column. For example, notice how the values in the `price` column are precisely formatted with 2 decimal points. The `TableFormatter` class and related subclasses can't do that. One way to fix it would be to modify the `print_table()` function to accept an additional formats argument. For example, maybe something like this: ```python >>> def print_table(records, fields, formats, formatter): formatter.headings(fields) for r in records: rowdata = [(fmt % getattr(r, fieldname)) for fieldname,fmt in zip(fields,formats)] formatter.row(rowdata) >>> import stock, reader >>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock) >>> from tableformat import TextTableFormatter >>> formatter = TextTableFormatter() >>> print_table(portfolio, ['name','shares','price'], ['%s','%d','%0.2f'], formatter) 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 >>> ``` Yes, you could modify `print_table()` like this, but is that the right place to do it? The whole idea of all of the `TableFormatter` classes is that they could be used in different kinds of applications. Column formatting is something that could be useful elsewhere, not just in the `print_table()` function. Another possible approach might be to change the interface to the `TableFormatter` class in some way. For example, maybe adding a third method to apply formatting. ```python class TableFormatter: def headings(self, headers): ... def format(self, rowdata): ... def row(self, rowdata): ... ``` The problem here is that any time you change the interface on a class, you're going to have to refactor all of the existing code to work with it. Specifically, you'd have to modify all of the already written `TableFormatter` subclasses and all of the code written to use them. Let's not do that. As an alternative, a user could use inheritance to customize a specific formatter in order to inject some formatting into it. For example, try this experiment: ```python >>> from tableformat import TextTableFormatter, print_table >>> class PortfolioFormatter(TextTableFormatter): def row(self, rowdata): formats = ['%s','%d','%0.2f'] rowdata = [(fmt % d) for fmt, d in zip(formats, rowdata)] super().row(rowdata) >>> formatter = PortfolioFormatter() >>> print_table(portfolio, ['name','shares','price'], formatter) 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 >>> ``` Yes, that works, but it's also a bit clumsy and weird. The user has to pick a specific formatter to customize. On top of that, they have to implement the actual column formatting code themselves. Surely there is a different way to do this. ## (b) Going Sideways In the `tableformat.py` file, add the following class definition: ```python class ColumnFormatMixin: formats = [] def row(self, rowdata): rowdata = [(fmt % d) for fmt, d in zip(self.formats, rowdata)] super().row(rowdata) ``` This class contains a single method `row()` that applies formatting to the row contents. A class variable `formats` is used to hold the format codes. This class is used via multiple inheritance. For example: ```python >>> import stock, reader >>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock) >>> from tableformat import TextTableFormatter, ColumnFormatMixin, print_table >>> class PortfolioFormatter(ColumnFormatMixin, TextTableFormatter): formats = ['%s', '%d', '%0.2f'] >>> formatter = PortfolioFormatter() >>> print_table(portfolio, ['name','shares','price'], formatter) 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 ``` This whole approach works because the `ColumnFormatMixin` class is meant to be mixed together with another class that provides the required `row()` method. Make another class that makes a formatter print the table headers in all-caps: ```python class UpperHeadersMixin: def headings(self, headers): super().headings([h.upper() for h in headers]) ``` Try it out and notice that the headers are now uppercase: ```python >>> from tableformat import TextTableFormatter, UpperHeadersMixin >>> class PortfolioFormatter(UpperHeadersMixin, TextTableFormatter): pass >>> formatter = PortfolioFormatter() >>> print_table(portfolio, ['name','shares','price'], formatter) NAME SHARES PRICE ---------- ---------- ---------- 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 >>> ``` This is really the whole idea on "mixins." The creator of a library can provide a basic set of classes such as `TextTableFormatter`, `CSVTableFormatter`, and so forth to start. Then, a collection of add-on classes can be provided to make those classes behave in different ways. ## (c) Making it Sane Using mixins can be a useful tool for framework builders for reducing the amount of code that needs to be written. However, forcing users to remember how to properly compose classes and use multiple inheritance can fry their brains. In [Exercise 3.5](ex3_5.md), you wrote a function `create_formatter()` that made it easier to create a custom formatter. Take that function and extend it to understand a few optional arguments related to the mixin classes. For example: ```python >>> from tableformat import create_formatter >>> formatter = create_formatter('csv', column_formats=['"%s"','%d','%0.2f']) >>> print_table(portfolio, ['name','shares','price'], formatter) 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 >>> formatter = create_formatter('text', upper_headers=True) >>> print_table(portfolio, ['name','shares','price'], formatter) NAME SHARES PRICE ---------- ---------- ---------- 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 >>> ``` Under the covers the `create_formatter()` function will properly compose the classes and return a proper `TableFormatter` instance. \[ [Solution](soln3_8.md) | [Index](index.md) | [Exercise 3.7](ex3_7.md) | [Exercise 4.1](ex4_1.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex4_1.md ================================================ \[ [Index](index.md) | [Exercise 3.8](ex3_8.md) | [Exercise 4.2](ex4_2.md) \] # Exercise 4.1 *Objectives:* - Learn more about how objects are represented. - Learn how attribute assignment and lookup works. - Better understand the role of a class definition *Files Created:* None *Files Modified:* None Start this exercise, by going back to a simple version of the `Stock` class you created. At the interactive prompt, define a new class called `SimpleStock` that looks like this: ```python >>> class SimpleStock: def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def cost(self): return self.shares * self.price >>> ``` Once you have defined this class, create a few instances. ```python >>> goog = SimpleStock('GOOG',100,490.10) >>> ibm = SimpleStock('IBM',50, 91.23) >>> ``` ## (a) Representation of Instances At the interactive shell, inspect the underlying dictionaries of the two instances you created: ```python >>> goog.__dict__ ... look at the output ... >>> ibm.__dict__ ... look at the output ... >>> ``` ## (b) Modification of Instance Data Try setting a new attribute on one of the above instances: ```python >>> goog.date = "6/11/2007" >>> goog.__dict__ ... look at output ... >>> ibm.__dict__ ... look at output ... >>> ``` In the above output, you'll notice that the `goog` instance has a attribute `date` whereas the `ibm` instance does not. It is important to note that Python really doesn't place any restrictions on attributes. For example, the attributes of an instance are not limited to those set up in the `__init__()` method. Instead of setting an attribute, try placing a new value directly into the `__dict__` object: ```python >>> goog.__dict__['time'] = '9:45am' >>> goog.time '9:45am' >>> ``` Here, you really notice the fact that an instance is a layer on top of a dictionary. ## (c) The role of classes The definitions that make up a class definition are shared by all instances of that class. Notice, that all instances have a link back to their associated class: ```python >>> goog.__class__ ... look at output ... >>> ibm.__class__ ... look at output ... >>> ``` Try calling a method on the instances: ```python >>> goog.cost() 49010.0 >>> ibm.cost() 4561.5 >>> ``` Notice that the name 'cost' is not defined in either `goog.__dict__` or `ibm.__dict__`. Instead, it is being supplied by the class dictionary. Try this: ```python >>> SimpleStock.__dict__['cost'] ... look at output ... >>> ``` Try calling the `cost()` method directly through the dictionary: ```python >>> SimpleStock.__dict__['cost'](goog) 49010.00 >>> SimpleStock.__dict__['cost'](ibm) 4561.5 >>> ``` Notice how you are calling the function defined in the class definition and how the `self` argument gets the instance. If you add a new value to the class, it becomes a class variable that's visible to all instances. Try it: ```python >>> SimpleStock.spam = 42 >>> ibm.spam 42 >>> goog.spam 42 >>> ``` Observe that `spam` is not part of the instance dictionary. ```python >>> ibm.__dict__ ... look at the output ... >>> ``` Instead, it's part of the class dictionary: ```python >>> SimpleStock.__dict__['spam'] 42 >>> ``` Essentially this is all a class really is--it's a collection of values shared by instances. \[ [Solution](soln4_1.md) | [Index](index.md) | [Exercise 3.8](ex3_8.md) | [Exercise 4.2](ex4_2.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex4_2.md ================================================ \[ [Index](index.md) | [Exercise 4.1](ex4_1.md) | [Exercise 4.3](ex4_3.md) \] # Exercise 4.2 *Objectives:* - Learn more about the behavior of inheritance - Understand the behavior of super(). - More cooperative inheritance. *Files Created:* `validate.py` ## (a) The directions of inheritance Python has two different "directions" of inheritance. The first is found in the concept of "single inheritance" where a series of classes inherit from a single parent. For example, try this example: ```python >>> class A: def spam(self): print('A.spam') >>> class B(A): def spam(self): print('B.spam') super().spam() >>> class C(B): def spam(self): print('C.spam') super().spam() >>> C.__mro__ (, , , ) >>> c = C() >>> c.spam() C.spam B.spam A.spam >>> ``` Observe that the `__mro__` attribute of class `C` encodes all of its ancestors in order. When you invoke the `spam()` method, it walks the MRO class-by-class up the hierarchy. With multiple inheritance, you get a different kind of inheritance that allows different classes to be composed together. Try this example: ``` >>> class Base: def spam(self): print('Base.spam') >>> class X(Base): def spam(self): print('X.spam') super().spam() >>> class Y(Base): def spam(self): print('Y.spam') super().spam() >>> class Z(Base): def spam(self): print('Z.spam') super().spam() >>> ``` Notice that all of the classes above inherit from a common parent `Base`. However, the classes `X`, `Y`, and `Z` are not directly related to each other (there is no inheritance chain linking those classes together). However, watch what happens in multiple inheritance: ```python >>> class M(X,Y,Z): pass >>> M.__mro__ (, , , , , ) >>> m = M() >>> m.spam() X.spam Y.spam Z.spam Base.spam >>> ``` Here, you see all of the classes stack together in the order supplied by the subclass. Suppose the subclass rearranges the class order: ```python >>> class N(Z,Y,X): pass >>> N.__mro__ (, , , , , ) >>> n = N() >>> n.spam() Z.spam Y.spam X.spam Base.spam >>> ``` Here, you see the order of the parents flip around. Carefully pay attention to what `super()` is doing in both cases. It doesn't delegate to the immediate parent of each class--instead, it moves to the next class on the MRO. Not only that, the exact order is controlled by the child. This is pretty weird. Also notice that the common parent `Base` serves to terminate the chain of `super()` operations. Specifically, the `Base.spam()` method does not call any further methods. It also appears at the end of the MRO since it is the parent to all of the classes being composed together. ## (b) Build a Value Checker In [Exercise 3.4](ex3_4.md), you added some properties to the `Stock` class that checked attributes for different types and values (e.g., shares had to be a positive integer). Let's play with that idea a bit. Start by creating a file `validate.py` and defining the following base class: ```python # validate.py class Validator: @classmethod def check(cls, value): return value ``` Now, let's make some classes for type checking: ```python class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'Expected {cls.expected_type}') return super().check(value) class Integer(Typed): expected_type = int class Float(Typed): expected_type = float class String(Typed): expected_type = str ``` Here's how you use these classes (Note: the use of `@classmethod` allows us to avoid the extra step of creating instances which we don't really need): ```python >>> Integer.check(10) 10 >>> Integer.check('10') Traceback (most recent call last): File "", line 1, in check raise TypeError(f'Expected {cls.expected_type}') TypeError: Expected >>> String.check('10') '10' >>> ``` You could use the validators in a function. For example: ```python >>> def add(x, y): Integer.check(x) Integer.check(y) return x + y >>> add(2, 2) 4 >>> add('2', '3') Traceback (most recent call last): File "", line 1, in File "", line 2, in add File "validate.py", line 11, in check raise TypeError(f'Expected {cls.expected_type}') TypeError: Expected >>> ``` Now, make some more classes for different kinds of domain checking: ```python class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('Expected >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('Must be non-empty') return super().check(value) ``` Where is all of this going? Let's start composing classes together with multiple inheritance like toy blocks: ```python class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass ``` Essentially, you're taking existing validators and composing them together into new ones. Madness! However, let's use them to validate some things now: ```python >>> PositiveInteger.check(10) 10 >>> PositiveInteger.check('10') Traceback (most recent call last): File "", line 1, in raise TypeError(f'Expected {cls.expected_type}') TypeError: Expected >>> PositiveInteger.check(-10) Traceback (most recent call last): File "", line 1, in raise ValueError('Expected >= 0') ValueError: Must be >= 0 >>> NonEmptyString.check('hello') 'hello' >>> NonEmptyString.check('') Traceback (most recent call last): File "", line 1, in raise ValueError('Must be non-empty') ValueError: Must be non-empty >>> ``` At this point, your head is probably fully exploded. However, the problem of composing different bits of code together is one that arises in real-world programs. Cooperative multiple inheritance is one of the tools that can be used to organize it. ## (c) Using your validators Your validators can be used to add value checking to functions and classes. For example, perhaps the validators could be used in the properties of `Stock`: ```python class Stock: ... @property def shares(self): return self._shares @shares.setter def shares(self, value): self._shares = PositiveInteger.check(value) ... ``` Copy the `Stock` class from `stock.py` change it to use the validators in the property code for `shares` and `price`. \[ [Solution](soln4_2.md) | [Index](index.md) | [Exercise 4.1](ex4_1.md) | [Exercise 4.3](ex4_3.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex4_3.md ================================================ \[ [Index](index.md) | [Exercise 4.2](ex4_2.md) | [Exercise 4.4](ex4_4.md) \] # Exercise 4.3 *Objectives:* - Learn about descriptors *Files Created:* `descrip.py` *Files Modified:* `validate.py` ## (a) Descriptors in action Earlier, you created a class `Stock` that made use of slots, properties, and other features. All of these features are implemented using the descriptor protocol. See it in action by trying this simple experiment. First, create a stock object, and try looking up a few attributes: ```python >>> s = Stock('GOOG', 100, 490.10) >>> s.name 'GOOG' >>> s.shares 100 >>> ``` Now, notice that these attributes are in the class dictionary. ```python >>> Stock.__dict__.keys() ['sell', '__module__', '__weakref__', 'price', '_price', 'shares', '_shares', '__slots__', 'cost', '__repr__', '__doc__', '__init__'] >>> ``` Try these steps which illustrate how descriptors get and set values on an instance: ```python >>> q = Stock.__dict__['shares'] >>> q.__get__(s) 100 >>> q.__set__(s,75) >>> s.shares 75 >>> q.__set__(s, '75') Traceback (most recent call last): File "", line 1, in File "stock.py", line 23, in shares raise TypeError('Expected an integer') TypeError: Expected an integer >>> ``` The execution of `__get__()` and `__set__()` occurs automatically whenever you access instances. ## (b) Make your own descriptor Define the descriptor class from the notes: ```python # descrip.py class Descriptor: def __init__(self, name): self.name = name def __get__(self, instance, cls): print('%s:__get__' % self.name) def __set__(self, instance, value): print('%s:__set__ %s' % (self.name, value)) def __delete__(self, instance): print('%s:__delete__' % self.name) ``` Now, try defining a simple class that uses this descriptor: ```python >>> class Foo: a = Descriptor('a') b = Descriptor('b') c = Descriptor('c') >>> f = Foo() >>> f <__main__.Foo object at 0x38e130> >>> f.a a:__get__ >>> f.b b:__get__ >>> f.a = 23 a:__set__ 23 >>> del f.a a:__delete__ >>> ``` Ponder the fact that you have captured the dot-operator for a specific attribute. ## (c) From Validators to Descriptors In the previous exercise, you wrote a series of classes that could perform checking. For example: ```python >>> PositiveInteger.check(10) 10 >>> PositiveInteger.check('10') Traceback (most recent call last): File "", line 1, in raise TypeError('Expected %s' % cls.expected_type) TypeError: expected >>> PositiveInteger.check(-10) ``` You can extend this to descriptors by making a simple change to the `Validator` base class. Change it to the following: ```python # validate.py class Validator: def __init__(self, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) ``` Note: The lack of the `__get__()` method in the descriptor means that Python will use its default implementation of attribute lookup. This requires that the supplied name matches the name used in the instance dictionary. No other changes should be necessary. Now, try modifying the `Stock` class to use the validators as descriptors like this: ```python class Stock: name = String('name') shares = PositiveInteger('shares') price = PositiveFloat('price') def __init__(self,name,shares,price): self.name = name self.shares = shares self.price = price ``` You'll find that your class works the same way as before, involves much less code, and gives you all of the desired checking: ```python >>> s = Stock('GOOG', 100, 490.10) >>> s.name 'GOOG' >>> s.shares 100 >>> s.shares = 75 >>> s.shares = '75' ... TypeError ... >>> s.shares = -50 ... ValueError ... >>> ``` This is pretty cool. Descriptors have allowed you to greatly simplify the implementation of the `Stock` class. This is the real power of descriptors--you get low level control over the dot and can use it to do amazing things. ## (d) Fixing the Names One annoying thing about descriptors is the redundant name specification. For example: ```python class Stock: ... shares = PositiveInteger('shares') ... ``` We can fix that. Change the top-level `Validator` class to include a `__set_name__()` method like this: ```python # validate.py class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) ``` Now, try rewriting your `Stock` class so that it looks like this: ```python class Stock: name = String() shares = PositiveInteger() price = PositiveFloat() def __init__(self,name,shares,price): self.name = name self.shares = shares self.price = price ``` Ah, much nicer. Be aware that this ability to set the name is a Python 3.6 feature however. It won't work on older versions. \[ [Solution](soln4_3.md) | [Index](index.md) | [Exercise 4.2](ex4_2.md) | [Exercise 4.4](ex4_4.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex4_4.md ================================================ \[ [Index](index.md) | [Exercise 4.3](ex4_3.md) | [Exercise 5.1](ex5_1.md) \] # Exercise 4.4 *Objectives:* - Learn about customizing attribute access - Delegation vs. inheritance ## (a) Slots vs. setattr In previous exercises, `__slots__` was used to list the instance attributes on a class. The primary purpose of slots is to optimize the use of memory. A secondary effect is that it strictly limits the allowed attributes to those listed. A downside of slots is that it often interacts strangely with other parts of Python (for example, classes using slots can't be used with multiple inheritance). For that reason, you really shouldn't use slots except in special cases. If you really wanted to limit the set of allowed attributes, an alternate way to do it would be to define a `__setattr__()` method. Try this experiment: ```python >>> class Stock: def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __setattr__(self, name, value): if name not in { 'name', 'shares', 'price' }: raise AttributeError('No attribute %s' % name) super().__setattr__(name, value) >>> s = Stock('GOOG', 100, 490.1) >>> s.name 'GOOG' >>> s.shares = 75 >>> s.share = 50 Traceback (most recent call last): File "", line 1, in File "", line 8, in __setattr__ AttributeError: No attribute share >>> ``` In this example, there are no slots, but the `__setattr__()` method still restricts attributes to those in a predefined set. You'd probably need to think about how this approach might interact with inheritance (e.g., if subclasses wanted to add new attributes, they'd probably need to redefine `__setattr__()` to make it work). ## (b) Proxies A proxy class is a class that wraps around an existing class and provides a similar interface. Define the following class which makes a read-only layer around an existing object: ```python >>> class Readonly: def __init__(self, obj): self.__dict__['_obj'] = obj def __setattr__(self, name, value): raise AttributeError("Can't set attribute") def __getattr__(self, name): return getattr(self._obj, name) >>> ``` To use the class, you simply wrap it around an existing instance: ```python >>> from stock import Stock >>> s = Stock('GOOG', 100, 490.1) >>> p = Readonly(s) >>> p.name 'GOOG' >>> p.shares 100 >>> p.cost 49010.0 >>> p.shares = 50 Traceback (most recent call last): File "", line 1, in File "", line 8, in __setattr__ AttributeError: Can't set attribute >>> ``` ## (c) Delegation as an alternative to inheritance Delegation is sometimes used as an alternative to inheritance. The idea is almost the same as the proxy class you defined in part (b). Try defining the following class: ```python >>> class Spam: def a(self): print('Spam.a') def b(self): print('Spam.b') >>> ``` Now, make a class that wraps around it and redefines some of the methods: ```python >>> class MySpam: def __init__(self): self._spam = Spam() def a(self): print('MySpam.a') self._spam.a() def c(self): print('MySpam.c') def __getattr__(self, name): return getattr(self._spam, name) >>> s = MySpam() >>> s.a() MySpam.a Spam.a >>> s.b() Spam.b >>> s.c() MySpam.c >>> ``` Carefully notice that the resulting class looks very similar to inheritance. For example the `a()` method is doing something similar to the `super()` call. The method `b()` is picked up via the `__getattr__()` method which delegates to the internally held `Spam` instance. **Discussion** The `__getattr__()` method is commonly defined on classes that act as wrappers around other objects. However, you have to be aware that the process of wrapping another object in this manner often introduces other complexities. For example, the wrapper object might break type-checking if any other part of the application is using the `isinstance()` function. Delegating methods through `__getattr__()` also doesn't work with special methods such as `__getitem__()`, `__enter__()`, and so forth. If a class makes extensive use of such methods, you'll have to provide similar functions in your wrapper class. \[ [Solution](soln4_4.md) | [Index](index.md) | [Exercise 4.3](ex4_3.md) | [Exercise 5.1](ex5_1.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex5_1.md ================================================ \[ [Index](index.md) | [Exercise 4.4](ex4_4.md) | [Exercise 5.2](ex5_2.md) \] # Exercise 5.1 *Objectives:* - Explore a few definitional aspects of functions/methods - Making functions more flexible - Type hints In [Exercise 2.6](ex2_6.md) you wrote a `reader.py` module that had a function for reading a CSV into a list of dictionaries. For example: ```python >>> import reader >>> port = reader.read_csv_as_dicts('Data/portfolio.csv', [str,int,float]) >>> ``` We later expanded to that code to work with instances in [Exercise 3.3](ex3_3.md): ```python >>> import reader >>> from stock import Stock >>> port = reader.read_csv_as_instances('Data/portfolio.csv', Stock) >>> ``` Eventually the code was refactored into a collection of classes involving inheritance in [Exercise 3.7](ex3_7.md). However, the code has become rather complex and convoluted. ## (a) Back to Basics Start by reverting the changes related to class definitions. Rewrite the `reader.py` file so that it contains the two basic functions that you had before you messed it up with classes: ```python # reader.py import csv def read_csv_as_dicts(filename, types): ''' Read CSV data into a list of dictionaries with optional type conversion ''' records = [] with open(filename) as file: rows = csv.reader(file) headers = next(rows) for row in rows: record = { name: func(val) for name, func, val in zip(headers, types, row) } records.append(record) return records def read_csv_as_instances(filename, cls): ''' Read CSV data into a list of instances ''' records = [] with open(filename) as file: rows = csv.reader(file) headers = next(rows) for row in rows: record = cls.from_row(row) records.append(record) return records ``` Make sure the code still works as it did before: ```python >>> import reader >>> port = reader.read_csv_as_dicts('Data/portfolio.csv', [str, int, float]) >>> port [{'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}] >>> import stock >>> port = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock) >>> port [Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44), Stock('MSFT', 200, 51.23), Stock('GE', 95, 40.37), Stock('MSFT', 50, 65.1), Stock('IBM', 100, 70.44)] >>> ``` ## (b) Thinking about Flexibility Right now, the two functions in `reader.py` are hard-wired to work with filenames that are passed directly to `open()`. Refactor the code so that it works with any iterable object that produces lines. To do this, create two new functions `csv_as_dicts(lines, types)` and `csv_as_instances(lines, cls)` that convert any iterable sequence of lines. For example: ```python >>> file = open('Data/portfolio.csv') >>> port = reader.csv_as_dicts(file, [str, int, float]) >>> port [{'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}] >>> ``` The whole point of doing this is to make it possible to work with different kinds of input sources. For example: ```python >>> import gzip >>> import stock >>> file = gzip.open('Data/portfolio.csv.gz', 'rt') >>> port = reader.csv_as_instances(file, stock.Stock) >>> port [Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44), Stock('MSFT', 200, 51.23), Stock('GE', 95, 40.37), Stock('MSFT', 50, 65.1), Stock('IBM', 100, 70.44)] >>> ``` To maintain backwards compatibility with older code, write functions `read_csv_as_dicts()` and `read_csv_as_instances()` that take a filename as before. These functions should call `open()` on the supplied filename and use the new `csv_as_dicts()` or `csv_as_instances()` functions on the resulting file. ## (c) Design Challenge: CSV Headers The code assumes that the first line of CSV data always contains column headers. However, this isn't always the case. For example, the file `Data/portfolio_noheader.csv` contains data, but no column headers. How would you refactor the code to accommodate missing column headers, having them supplied manually by the caller instead? ## (d) API Challenge: Type hints Functions can have optional type-hints attached to arguments and return values. For example: ```python def add(x:int, y:int) -> int: return x + y ``` The `typing` module has additional classes for expressing more complex kinds of types including containers. For example: ```python from typing import List def sum_squares(nums: List[int]) -> int: total = 0 for n in nums: total += n*n return total ``` Your challenge: Modify the code in `reader.py` so that all functions have type hints. Try to make the type-hints as accurate as possible. To do this, you may need to consult the documentation for the [typing module](https://docs.python.org/3/library/typing.html). \[ [Solution](soln5_1.md) | [Index](index.md) | [Exercise 4.4](ex4_4.md) | [Exercise 5.2](ex5_2.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex5_2.md ================================================ \[ [Index](index.md) | [Exercise 5.1](ex5_1.md) | [Exercise 5.3](ex5_3.md) \] # Exercise 5.2 *Objectives:* - Returning values from functions In this exercise, we briefly look at problems related to returning values from functions. At first glance, it seems like this should be straightforward, but there are some subtle problems that arise. ## (a) Returning Multiple Values Suppose you were writing code to parse configuration files consisting of lines like this: name=value Write a function `parse_line(line)` that takes such a line and returns both the associated name and value. The common convention for returning multiple values is to return them in a tuple. For example: ```python >>> parse_line('email=guido@python.org') ('email', 'guido@python.org') >>> name, val = parse_line('email=guido@python.org') >>> name 'email' >>> val 'guido@python.org' >>> ``` ## (b) Returning Optional Values Sometimes a function might return an optional value--possibly as a mechanism for indicating success or failure. The most common convention is to use `None` as a representation for a missing value. Modify the `parse_line()` function above so that it either returns a tuple on success or `None` on bad data. For example: ```python >>> parse_line('email=guido@python.org') ('email', 'guido@python.org') >>> parse_line('spam') # Returns None >>> ``` Design discussion: Would it be better for the `parse_line()` function to raise an exception on malformed data? ## (c) Futures Sometimes Python code executes concurrently via threads or processes. To illustrate, try this example: ```python >>> import time >>> def worker(x, y): print('About to work') time.sleep(20) print('Done') return x + y >>> worker(2, 3) # Normal function call About to work Done 5 >>> ``` Now, launch `worker()` into a separate thread: ```python >>> import threading >>> t = threading.Thread(target=worker, args=(2, 3)) >>> t.start() About to work >>> Done ``` Carefully notice that the result of the calculation appears nowhere. Not only that, you don't even know when it's going to be completed. There is a certain coordination problem here. The convention for handling this case is to wrap the result of a function in a `Future`. A `Future` represents a future result. Here's how it works: ```python >>> from concurrent.futures import Future >>> # Wrapper around the function to use a future >>> def do_work(x, y, fut): fut.set_result(worker(x,y)) >>> fut = Future() >>> t = threading.Thread(target=do_work, args=(2, 3, fut)) >>> t.start() About to work >>> result = fut.result() Done >>> result 5 >>> ``` You'll see this kind of pattern a lot of if working with thread pools, processes, and other constructs. For example: ```python >>> from concurrent.futures import ThreadPoolExecutor >>> pool = ThreadPoolExecutor() >>> fut = pool.submit(worker, 2, 3) About to work >>> fut >>> fut.result() Done 5 >>> ``` \[ [Solution](soln5_2.md) | [Index](index.md) | [Exercise 5.1](ex5_1.md) | [Exercise 5.3](ex5_3.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex5_3.md ================================================ \[ [Index](index.md) | [Exercise 5.2](ex5_2.md) | [Exercise 5.4](ex5_4.md) \] # Exercise 5.3 *Objectives:* - Higher order functions *Files Modified:* `reader.py` ## (a) Using higher-order functions At the moment, the `reader.py` program consists of two core functions, `csv_as_dicts()` and `csv_as_instances()`. The code in these two functions is almost identical. For example: ```python def csv_as_dicts(lines, types, *, headers=None): ''' Convert lines of CSV data into a list of dictionaries ''' records = [] rows = csv.reader(lines) if headers is None: headers = next(rows) for row in rows: record = { name: func(val) for name, func, val in zip(headers, types, row) } records.append(record) return records def csv_as_instances(lines, cls, *, headers=None): ''' Convert lines of CSV data into a list of instances ''' records = [] rows = csv.reader(lines) if headers is None: headers = next(rows) for row in rows: record = cls.from_row(row) records.append(record) return records ``` Unify the core of these functions into a single function `convert_csv()` that accepts a user-defined conversion function as an argument. For example: ```python >>> def make_dict(headers, row): return dict(zip(headers, row)) >>> lines = open('Data/portfolio.csv') >>> convert_csv(lines, make_dict) [{'name': 'AA', 'shares': '100', 'price': '32.20'}, {'name': 'IBM', 'shares': '50', 'price': '91.10'}, {'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.10'}, {'name': 'IBM', 'shares': '100', 'price': '70.44'}] >>> ``` Rewrite the `csv_as_dicts()` and `csv_as_instances()` functions in terms of the new `convert_csv()` function. ## (b) Mapping One of the most common operations in functional programming is the `map()` operation that maps a function to the values in a sequence. Python has a built-in `map()` function that does this. For example: ```python >>> nums = [1,2,3,4] >>> squares = map(lambda x: x*x, nums) >>> for n in squares: print(n) 1 4 9 16 >>> ``` `map()` produces an iterator so if you want a list, you'll need to create it explicitly: ```python >>> squares = list(map(lambda x: x*x, nums)) >>> squares [1, 4, 9, 16] >>> ``` Try to use `map()` in your `convert_csv()` function. \[ [Solution](soln5_3.md) | [Index](index.md) | [Exercise 5.2](ex5_2.md) | [Exercise 5.4](ex5_4.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex5_4.md ================================================ \[ [Index](index.md) | [Exercise 5.3](ex5_3.md) | [Exercise 5.5](ex5_5.md) \] # Exercise 5.4 *Objectives:* - Learn more about closures In this section, we look briefly at a few of the more unusual aspects of closures. ## (a) Closures as a data structure One potential use of closures is as a tool for data encapsulation. Try this example: ```python def counter(value): def incr(): nonlocal value value += 1 return value def decr(): nonlocal value value -= 1 return value return incr, decr ``` This code defines two inner functions that manipulate a value. Try it out: ```python >>> up, down = counter(0) >>> up() 1 >>> up() 2 >>> up() 3 >>> down() 2 >>> down() 1 >>> ``` Notice how there is no class definition involved here. Moreover, there is no global variable either. Yet, the `up()` and `down()` functions are manipulating some "behind the scenes" value. It's fairly magical. ## (b) Closures as a code generator In [Exercise 4.3](ex4_3.md), you developed a collection of descriptor classes that allowed type-checking of object attributes. For example: ```python class Stock: name = String() shares = Integer() price = Float() ``` This kind of thing can also be implemented using closures. Define a file ``typedproperty.py`` and put the following code in it: ```python # typedproperty.py def typedproperty(name, expected_type): private_name = '_' + name @property def value(self): return getattr(self, private_name) @value.setter def value(self, val): if not isinstance(val, expected_type): raise TypeError(f'Expected {expected_type}') setattr(self, private_name, val) return value ``` This look pretty wild, but the function is effectively making code. You'd use it in a class definition like this: ```python from typedproperty import typedproperty class Stock: name = typedproperty('name', str) shares = typedproperty('shares', int) price = typedproperty('price', float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price ``` Verify that this class performs type-checking in the same way as the descriptor code. Add function `String()`, `Integer()`, and `Float()` to the `typedproperty.py` file so that you can write the following code: ```python from typedproperty import String, Integer, Float class Stock: name = String('name') shares = Integer('shares') price = Float('price') def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price ``` ## (c) Challenge: Eliminating names Modify the `typedproperty.py` code so that attribute names are no-longer required: ```python from typedproperty import String, Integer, Float class Stock: name = String() shares = Integer() price = Float() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price ``` Hint: To do this, recall the `__set_name__()` method of descriptor objects that gets called when descriptors are placed in a class definition. \[ [Solution](soln5_4.md) | [Index](index.md) | [Exercise 5.3](ex5_3.md) | [Exercise 5.5](ex5_5.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex5_5.md ================================================ \[ [Index](index.md) | [Exercise 5.4](ex5_4.md) | [Exercise 5.6](ex5_6.md) \] # Exercise 5.5 *Objectives:* - Learn more about exception handling and logging *Files Modified:* `reader.py` In the `reader.py` file, there is a central function `convert_csv()` that does most of the work. This function crashes if you run it on data with missing or bad data. For example: ```python >>> port = read_csv_as_dicts('Data/missing.csv', types=[str, int, float]) Traceback (most recent call last): File "", line 1, in File "reader.py", line 24, in read_csv_as_dicts return csv_as_dicts(file, types, headers=headers) File "reader.py", line 13, in csv_as_dicts lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) }) File "reader.py", line 9, in convert_csv return list(map(lambda row: converter(headers, row), rows)) File "reader.py", line 9, in return list(map(lambda row: converter(headers, row), rows)) File "reader.py", line 13, in lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) }) File "reader.py", line 13, in lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) }) ValueError: invalid literal for int() with base 10: '' >>> ``` ## (a) Catching Exceptions Instead of crashing on bad data, modify the code to issue a warning message instead. The final result should be a list of the rows that were successfully converted. For example: ```python >>> port = read_csv_as_dicts('Data/missing.csv', types=[str, int, float]) Row 4: Bad row: ['C', '', '53.08'] Row 7: Bad row: ['DIS', '50', 'N/A'] Row 8: Bad row: ['GE', '', '37.23'] Row 13: Bad row: ['INTC', '', '21.84'] Row 17: Bad row: ['MCD', '', '51.11'] Row 19: Bad row: ['MO', '', '70.09'] Row 22: Bad row: ['PFE', '', '26.40'] Row 26: Bad row: ['VZ', '', '42.92'] >>> len(port) 20 >>> ``` Note: Making this change may be a bit tricky because of your previous use of the `map()` built-in function. You may have to abandon that approach since there's no easy way to catch and handle exceptions in `map()`. ## (b) Logging Modify the code so that warning messages are issued using the `logging` module. In addition, give optional debugging information indicating the reason for failure. For example: ```python >>> import reader >>> import logging >>> logging.basicConfig(level=logging.DEBUG) >>> port = reader.read_csv_as_dicts('Data/missing.csv', types=[str, int, float]) WARNING:reader:Row 4: Bad row: ['C', '', '53.08'] DEBUG:reader:Row 4: Reason: invalid literal for int() with base 10: '' WARNING:reader:Row 7: Bad row: ['DIS', '50', 'N/A'] DEBUG:reader:Row 7: Reason: could not convert string to float: 'N/A' WARNING:reader:Row 8: Bad row: ['GE', '', '37.23'] DEBUG:reader:Row 8: Reason: invalid literal for int() with base 10: '' WARNING:reader:Row 13: Bad row: ['INTC', '', '21.84'] DEBUG:reader:Row 13: Reason: invalid literal for int() with base 10: '' WARNING:reader:Row 17: Bad row: ['MCD', '', '51.11'] DEBUG:reader:Row 17: Reason: invalid literal for int() with base 10: '' WARNING:reader:Row 19: Bad row: ['MO', '', '70.09'] DEBUG:reader:Row 19: Reason: invalid literal for int() with base 10: '' WARNING:reader:Row 22: Bad row: ['PFE', '', '26.40'] DEBUG:reader:Row 22: Reason: invalid literal for int() with base 10: '' WARNING:reader:Row 26: Bad row: ['VZ', '', '42.92'] DEBUG:reader:Row 26: Reason: invalid literal for int() with base 10: '' >>> ``` \[ [Solution](soln5_5.md) | [Index](index.md) | [Exercise 5.4](ex5_4.md) | [Exercise 5.6](ex5_6.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex5_6.md ================================================ \[ [Index](index.md) | [Exercise 5.5](ex5_5.md) | [Exercise 6.1](ex6_1.md) \] # Exercise 5.6 *Objectives:* - Learn how to use Python's unittest module *Files Created:* `teststock.py` In this exercise, you will explore the basic mechanics of using Python's `unittest` modules. ## (a) Preliminaries In previous exercises, you created a file `stock.py` that contained a `Stock` class. In a separate file, `teststock.py`, define the following testing code: ```python # teststock.py import unittest import stock class TestStock(unittest.TestCase): def test_create(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) if __name__ == '__main__': unittest.main() ``` Make sure you can run the file: ``` bash % python3 teststock.py . ------------------------------------------------------------------``` Ran 1 tests in 0.001s OK bash % ``` ## (b) Unit testing Using the code in `teststock.py` as a guide, extend the `TestStock` class with tests for the following: - Test that you can create a `Stock` using keyword arguments such as `Stock(name='GOOG',shares=100,price=490.1)`. - Test that the `cost` property returns a correct value - Test that the `sell()` method correctly updates the shares. - Test that the `from_row()` class method creates a new instance from good data. - Test that the `__repr__()` method creates a proper representation string. - Test the comparison operator method `__eq__()` ## (c) Unit tests with expected errors Suppose you wanted to write a unit test that checks for an exception. Here is how you can do it: ```python class TestStock(unittest.TestCase): ... def test_bad_shares(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.shares = '50' ... ``` Using this test as a guide, write unit tests for the following failure modes: - Test that setting `shares` to a string raises a `TypeError` - Test that setting `shares` to a negative number raises a `ValueError` - Test that setting `price` to a string raises a `TypeError` - Test that setting `price` to a negative number raises a `ValueError` - Test that setting a non-existent attribute `share` raises an `AttributeError` In total, you should have around a dozen unit tests when you're done. **Important Note** For later use in the course, you will want to have a fully working `stock.py` and `teststock.py` file. Save your work in progress if you have to, but you are strongly encouraged to copy the code from `Solutions/5_6` if things are still broken at this point. We're going to use the `teststock.py` file as a tool for improving the `Stock` code later. You'll want it on hand to make sure that the new code behaves the same way as the old code. \[ [Solution](soln5_6.md) | [Index](index.md) | [Exercise 5.5](ex5_5.md) | [Exercise 6.1](ex6_1.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex6_1.md ================================================ \[ [Index](index.md) | [Exercise 5.6](ex5_6.md) | [Exercise 6.2](ex6_2.md) \] # Exercise 6.1 *Objectives:* - Learn more about function argument passing conventions *Files Created:* `structure.py`, `stock.py` **IMPORTANT NOTE** This exercise is going to start a long road of rewriting the `stock.py` file in a more sane way. Before doing anything, copy your work in `stock.py` to a new file `orig_stock.py`. We're going to recreate the `Stock` class from scratch using some new techniques. Make sure you have your unit tests from [Exercise 5.4](ex5_4.md) handy. You'll want those. If you define a function, you probably already know that it can be called using a mix of positional or keyword arguments. For example: ```python >>> def foo(x, y, z): return x + y + z >>> foo(1, 2, 3) 6 >>> foo(1, z=3, y=2) 6 >>> ``` You may also know that you can pass sequences and dictionaries as function arguments using the * and ** syntax. For example: ```python >>> args = (1, 2, 3) >>> foo(*args) 6 >>> kwargs = {'y':2, 'z':3 } >>> foo(1,**kwargs) 6 >>> ``` In addition to that, you can write functions that accept any number of positional or keyword arguments using the * and ** syntax. For example: ```python >>> def foo(*args): print(args) >>> foo(1,2) (1, 2) >>> foo(1,2,3,4,5) (1, 2, 3, 4, 5) >>> foo() () >>> >>> def bar(**kwargs): print(kwargs) >>> bar(x=1,y=2) {'y': 2, 'x': 1} >>> bar(x=1,y=2,z=3) {'y': 2, 'x': 1, 'z': 3} >>> bar() {} >>> ``` Variable argument functions are sometimes useful as a technique for reducing or simplifying the amount of code you need to type. In this exercise, we'll explore that idea for simple data structures. ## (a) Simplified Data Structures In earlier exercises, you defined a class representing a stock like this: ```python class Stock: def __init__(self,name,shares,price): self.name = name self.shares = shares self.price = price ``` Focus on the `__init__()` method---isn't that a lot of code to type each time you want to populate a structure? What if you had to define dozens of such structures in your program? In a file `structure.py`, define a base class `Structure` that allows the user to define simple data structures as follows: ```python class Stock(Structure): _fields = ('name','shares','price') class Date(Structure): _fields = ('year', 'month', 'day') ``` The `Structure` class should define an `__init__()` method that takes any number of arguments and which looks for the presence of a `_fields` class variable. Have the method populate the instance from the attribute names in `_fields` and values passed to `__init__()`. Here is some sample code to test your implementation: ```python >>> s = Stock('GOOG',100,490.1) >>> s.name 'GOOG' >>> s.shares 100 >>> s.price 490.1 >>> s = Stock('AA',50) Traceback (most recent call last): ... TypeError: Expected 3 arguments >>> ``` ## (b) Making a Useful Representation Modify the `Structure` class so that it produces a nice representation when `repr()` is used. For example: ```python >>> s = Stock('GOOG', 100, 490.1) >>> s Stock('GOOG',100,490.1) >>> ``` ## (c) Restricting Attribute Names Give the `Structure` class a `__setattr__()` method that restricts the allowed set of attributes to those listed in the `_fields` variable. However, it should still allow any "private" attribute (e.g., name starting with `_` to be set). For example: ```python >>> s = Stock('GOOG',100,490.1) >>> s.shares = 50 >>> s.share = 50 Traceback (most recent call last): File "", line 1, in File "structure.py", line 13, in __setattr__ raise AttributeError('No attribute %s' % name) AttributeError: No attribute share >>> s._shares = 100 # Private attribute. OK >>> ``` ## (d) Starting Over Create a new file `stock.py` (or delete all of your previous code). Start over by defining `Stock` as follows: ```python # stock.py from structure import Structure class Stock(Structure): _fields = ('name', 'shares', 'price') @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ``` Once you've done this, run your `teststock.py` unit tests. You should get a lot of failures, but at least a handful of the tests should pass. \[ [Solution](soln6_1.md) | [Index](index.md) | [Exercise 5.6](ex5_6.md) | [Exercise 6.2](ex6_2.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex6_2.md ================================================ \[ [Index](index.md) | [Exercise 6.1](ex6_1.md) | [Exercise 6.3](ex6_3.md) \] # Exercise 6.2 *Objectives:* - Learn more about scoping rules - Learn some scoping tricks *Files modified:* `structure.py`, `stock.py` In the last exercise, you created a class `Structure` that made it easy to define data structures. For example: ```python class Stock(Structure): _fields = ('name','shares','price') ``` This works fine except that a lot of things are pretty weird about the `__init__()` function. For example, if you ask for help using `help(Stock)`, you don't get any kind of useful signature. Also, keyword argument passing doesn't work. For example: ```python >>> help(Stock) ... look at output ... >>> s = Stock(name='GOOG', shares=100, price=490.1) Traceback (most recent call last): File "", line 1, in TypeError: __init__() got an unexpected keyword argument 'price' >>> ``` In this exercise, we're going to look at a different approach to the problem. ## (a) Show me your locals First, try an experiment by defining the following class: ```python >>> class Stock: def __init__(self, name, shares, price): print(locals()) >>> ``` Now, try running this: ```python >>> s = Stock('GOOG', 100, 490.1) {'self': <__main__.Stock object at 0x100699b00>, 'price': 490.1, 'name': 'GOOG', 'shares': 100} >>> ``` Notice how the locals dictionary contains all of the arguments passed to `__init__()`. That's interesting. Now, define the following function and class definitions: ```python >>> def _init(locs): self = locs['self'] for name, val in locs.items(): if name == 'self': continue setattr(self, name, val) >>> class Stock: def __init__(self, name, shares, price): _init(locals()) ``` In this code, the `_init()` function is used to automatically initialize an object from a dictionary of passed local variables. You'll find that `help(Stock)` and keyword arguments work perfectly. ```python >>> s = Stock(name='GOOG', price=490.1, shares=50) >>> s.name 'GOOG' >>> s.shares 50 >>> s.price 490.1 >>> ``` ## (b) Frame Hacking One complaint about the last part is that the `__init__()` function now looks pretty weird with that call to `locals()` inserted into it. You can get around that though if you're willing to do a bit of stack frame hacking. Try this variant of the `_init()` function: ```python >>> import sys >>> def _init(): locs = sys._getframe(1).f_locals # Get callers local variables self = locs['self'] for name, val in locs.items(): if name == 'self': continue setattr(self, name, val) >>> ``` In this code, the local variables are extracted from the stack frame of the caller. Here is a modified class definition: ```python >>> class Stock: def __init__(self, name, shares, price): _init() >>> s = Stock('GOOG', 100, 490.1) >>> s.name 'GOOG' >>> s.shares 100 >>> ``` At this point, you're probably feeling rather disturbed. Yes, you just wrote a function that reached into the stack frame of another function and examined its local variables. ## (c) Putting it Together Taking the ideas in the first two parts, delete the `__init__()` method that was originally part of the `Structure` class. Next, add an `_init()` method like this: ```python # structure.py import sys class Structure: ... @staticmethod def _init(): locs = sys._getframe(1).f_locals self = locs['self'] for name, val in locs.items(): if name == 'self': continue setattr(self, name, val) ... ``` Note: The reason this is defined as a `@staticmethod` is that the `self` argument is obtained from the locals--there's no need to additionally have it passed as an argument to the method itself (admittedly this is a bit subtle). Now, modify your `Stock` class so that it looks like the following: ```python # stock.py from structure import Structure class Stock(Structure): _fields = ('name','shares','price') def __init__(self, name, shares, price): self._init() @property def cost(self): return self.shares * self.price def sell(self, shares): self.shares -= shares ``` Verify that the class works properly, supports keyword arguments, and has a proper help signature. ```python >>> s = Stock(name='GOOG', price=490.1, shares=50) >>> s.name 'GOOG' >>> s.shares 50 >>> s.price 490.1 >>> help(Stock) ... look at the output ... >>> ``` Run your unit tests in `teststock.py` again. You should see at least one more test pass. Yay! At this point, it's going to look like we just took a giant step backwards. Not only do the classes need the `__init__()` method, they also need the `_fields` variable for some of the other methods to work (`__repr__()` and `__setattr__()`). Plus, the use of `self._init()` looks pretty hacky. We'll work on this, but be patient. \[ [Solution](soln6_2.md) | [Index](index.md) | [Exercise 6.1](ex6_1.md) | [Exercise 6.3](ex6_3.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex6_3.md ================================================ \[ [Index](index.md) | [Exercise 6.2](ex6_2.md) | [Exercise 6.4](ex6_4.md) \] # Exercise 6.3 *Objectives:* - Learn how to inspect the internals of functions *Files Modified:* `structure.py` ## (a) Inspecting functions Define a simple function: ```python >>> def add(x,y): 'Adds two things' return x+y >>> ``` Do a `dir()` on the function to look at its attributes. ```python >>> dir(add) ... look at the result ... >>> ``` Get some basic information such as the function name, defining module name, and documentation string. ```python >>> add.__name__ 'add' >>> add.__module__ '__main__' >>> add.__doc__ 'Adds two things' >>> ``` The `__code__` attribute of a function has low-level information about the function implementation. See if you can look at this and determine the number of required arguments and names of local variables. ## (b) Using the inspect module Use the inspect module to get calling information about the function: ```python >>> import inspect >>> sig = inspect.signature(add) >>> sig >>> sig.parameters mappingproxy(OrderedDict([('x', ), ('y', )])) >>> tuple(sig.parameters) ('x', 'y') >>> ``` ## (c) Putting it Together In [Exercise 6.1](ex6_1.md), you created a class `Structure` that defined a generalized `__init__()`, `__setattr__()`, and `__repr__()` method. That class required a user to define a `_fields` class variable like this: ```python class Stock(Structure): _fields = ('name','shares','price') ``` The problem with this class is that the `__init__()` function didn't have a useful argument signature for the purposes of help and keyword argument passing. In [Exercise 6.2](ex6_2.md), you did a sneaky trick involving a special `self._init()` function. For example: ```python class Stock(Structure): _fields = ('name', 'shares', 'price') def __init__(self, name, shares, price): self._init() ... ``` This gave a useful signature, but now the class is just weird because the user has to provide both the `_fields` variable and the `__init__()` method. Your task is to eliminate the `_fields` variable using some function inspection techniques. First, notice that you can get the argument signature from `Stock` as follows: ```python >>> import inspect >>> sig = inspect.signature(Stock) >>> tuple(sig.parameters) ('name', 'shares', 'price') >>> ``` Perhaps you could set the `_fields` variable from the argument signature of `__init__()`. Add a class method `set_fields(cls)` to `Structure` that inspects the `__init__()` function, and sets the `_fields` variable appropriately. You should use your new function like this: ```python class Stock(Structure): def __init__(self, name, shares, price): self._init() ... Stock.set_fields() ``` The resulting class should work the same way as before: ```python >>> s = Stock(name='GOOG', shares=100, price=490.1) >>> s Stock('GOOG',100,490.1) >>> s.shares = 50 >>> s.share = 50 Traceback (most recent call last): File "", line 1, in File "structure.py", line 12, in __setattr__ raise AttributeError('No attribute %s' % name) AttributeError: No attribute share >>> ``` Verify the slightly modified `Stock` class with your unit tests again. There will still be failures, but nothing should change from the previous exercise. At this point, it's all still a bit "hacky", but you're making progress. You have a Stock structure class with a useful `__init__()` function, there is a useful representation string, and the `__setattr__()` method restricts the set of attribute names. The extra step of having to invoke `set_fields()` is a bit odd, but we'll get back to that. \[ [Solution](soln6_3.md) | [Index](index.md) | [Exercise 6.2](ex6_2.md) | [Exercise 6.4](ex6_4.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex6_4.md ================================================ \[ [Index](index.md) | [Exercise 6.3](ex6_3.md) | [Exercise 6.5](ex6_5.md) \] # Exercise 6.4 *Objectives:* - Learn to create code with `exec()` ## (a) Experiment with exec() Define a fragment of Python code in a string and try running it: ```python >>> code = ''' for i in range(n): print(i, end=' ') ''' >>> n = 10 >>> exec(code) 0 1 2 3 4 5 6 7 8 9 >>> ``` That's interesting, but executing random code fragments is not especially useful. A more interesting use of `exec()` is in making code such as functions, methods, or classes. Try this example in which we make an `__init__()` function for a class. ```python >>> class Stock: _fields = ('name', 'shares', 'price') >>> argstr = ','.join(Stock._fields) >>> code = f'def __init__(self, {argstr}):\n' >>> for name in Stock._fields: code += f' self.{name} = {name}\n' >>> print(code) def __init__(self, name,shares,price): self.name = name self.shares = shares self.price = price >>> locs = { } >>> exec(code, locs) >>> Stock.__init__ = locs['__init__'] >>> # Now try the class >>> s = Stock('GOOG', 100, 490.1) >>> s.name 'GOOG' >>> s.shares 100 >>> s.price 490.1 >>> ``` In this example, an `__init__()` function is made directly from the `_fields` variable. There are no weird hacks involving a special `_init()` method or stack frames. ## (b) Creating an `__init__()` function In [Exercise 6.3](ex6_3.md), you wrote code that inspected the signature of the `__init__()` method to set the attribute names in a `_fields` class variable. For example: ```python class Stock(Structure): def __init__(self, name, shares, price): self._init() Stock.set_fields() ``` Instead of inspecting the `__init__()` method, write a class method `create_init(cls)` that creates an `__init__()` method from the value of `_fields`. Use the `exec()` function to do this as shown above. Here's how a user will use it: ```python class Stock(Structure): _fields = ('name', 'shares', 'price') Stock.create_init() ``` The resulting class should work exactly the same way as before: ```python >>> s = Stock(name='GOOG', shares=100, price=490.1) >>> s Stock('GOOG',100,490.1) >>> s.shares = 50 >>> s.share = 50 Traceback (most recent call last): File "", line 1, in File "structure.py", line 12, in __setattr__ raise AttributeError('No attribute %s' % name) AttributeError: No attribute share >>> ``` Modify the `Stock` class in progress to use the `create_init()` function as shown. Verify with your unit tests as before. While you're at it, get rid of the `_init()` and `set_fields()` methods on the `Structure` class--that approach was kind of weird. ## (c) Named Tuples In [Exercise 2.1](ex2_1.md), you experimented with `namedtuple` objects in the `collections` module. Just to refresh your memory, here is how they worked: ```python >>> from collections import namedtuple >>> Stock = namedtuple('Stock', ['name', 'shares', 'price']) >>> s = Stock('GOOG', 100, 490.1) >>> s.name 'GOOG' >>> s.shares 100 >>> s[1] 100 >>> ``` Under the covers, the `namedtuple()` function is creating code as a string and executing it using `exec()`. Look at the code and marvel: ```python >>> import inspect >>> print(inspect.getsource(namedtuple)) ... look at the output ... >>> ``` \[ [Solution](soln6_4.md) | [Index](index.md) | [Exercise 6.3](ex6_3.md) | [Exercise 6.5](ex6_5.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex6_5.md ================================================ \[ [Index](index.md) | [Exercise 6.4](ex6_4.md) | [Exercise 7.1](ex7_1.md) \] # Exercise 6.5 *Objectives:* - Learn how to define a proper callable object Files Modified : `validate.py` Back in [Exercise 4.3](ex4_3.md), you created a series of `Validator` classes for performing different kinds of type and value checks. For example: ```python >>> from validate import Integer >>> Integer.check(1) >>> Integer.check('hello') Traceback (most recent call last): File "", line 1, in File "validate.py", line 21, in check raise TypeError(f'Expected {cls.expected_type}') TypeError: Expected >>> ``` You could use the validators in functions like this: ```python >>> def add(x, y): Integer.check(x) Integer.check(y) return x + y >>> ``` In this exercise, we're going to take it just one step further. ## (a) Creating a Callable Object In the file `validate.py`, start by creating a class like this: ```python # validate.py ... class ValidatedFunction: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print('Calling', self.func) result = self.func(*args, **kwargs) return result ``` Test the class by applying it to a function: ```python >>> def add(x, y): return x + y >>> add = ValidatedFunction(add) >>> add(2, 3) Calling 5 >>> ``` ## (b) Enforcement Modify the `ValidatedFunction` class so that it enforces value checks attached via function annotations. For example: ```python >>> def add(x: Integer, y:Integer): return x + y >>> add = ValidatedFunction(add) >>> add(2,3) 5 >>> add('two','three') Traceback (most recent call last): File "", line 1, in File "validate.py", line 67, in __call__ self.func.__annotations__[name].check(val) File "validate.py", line 21, in check raise TypeError(f'Expected {cls.expected_type}') TypeError: expected >>>> ``` Hint: To do this, play around with signature binding. Use the `bind()` method of `Signature` objects to bind function arguments to argument names. Then cross reference this information with the `__annotations__` attribute to get the different validator classes. Keep in mind, you're making an object that looks like a function, but it's really not. There is magic going on behind the scenes. ## (c) Use as a Method (Challenge) A custom callable often presents problems if used as a custom method. For example, try this: ```python class Stock: def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price @property def cost(self): return self.shares * self.price def sell(self, nshares:Integer): self.shares -= nshares sell = ValidatedFunction(sell) # Fails ``` You'll find that the wrapped `sell()` fails miserably: ```python >>> s = Stock('GOOG', 100, 490.1) >>> s.sell(10) Traceback (most recent call last): File "", line 1, in File "validate.py", line 64, in __call__ bound = self.signature.bind(*args, **kwargs) File "/usr/local/lib/python3.6/inspect.py", line 2933, in bind return args[0]._bind(args[1:], kwargs) File "/usr/local/lib/python3.6/inspect.py", line 2848, in _bind raise TypeError(msg) from None TypeError: missing a required argument: 'nshares' >>> ``` Bonus: Figure out why it fails--but don't spend too much time fooling around with it. \[ [Solution](soln6_5.md) | [Index](index.md) | [Exercise 6.4](ex6_4.md) | [Exercise 7.1](ex7_1.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex7_1.md ================================================ \[ [Index](index.md) | [Exercise 6.5](ex6_5.md) | [Exercise 7.2](ex7_2.md) \] # Exercise 7.1 *Objectives:* - Learn how to define simple decorator functions. *Files Created:* `logcall.py` *Files Modified:* `validate.py` ## (a) Your First Decorator To start with decorators, write a _very_ simple decorator function that simply prints out a message each time a function is called. Create a file `logcall.py` and define the following function: ```python # logcall.py def logged(func): print('Adding logging to', func.__name__) def wrapper(*args, **kwargs): print('Calling', func.__name__) return func(*args, **kwargs) return wrapper ``` Now, make a separate file `sample.py` and apply it to a few function definitions: ```python # sample.py from logcall import logged @logged def add(x,y): return x+y @logged def sub(x,y): return x-y ``` Test your code as follows: ```python >>> import sample Adding logging to add Adding logging to sub >>> sample.add(3,4) Calling add 7 >>> sample.sub(2,3) Calling sub -1 >>> ``` ## (b) A Real Decorator In [Exercise 6.5](ex6_5.md), you created a callable class `ValidatedFunction` that enforced type annotations. Rewrite this class as a decorator function called `validated`. It should allow you to write code like this: ```python from validate import Integer, validated @validated def add(x: Integer, y:Integer) -> Integer: return x + y @validated def pow(x: Integer, y:Integer) -> Integer: return x ** y ``` Here's how the decorated functions should work: ```python >>> add(2, 3) 5 >>> add('2', '3') Traceback (most recent call last): File "", line 1, in File "validate.py", line 75, in wrapper raise TypeError('Bad Arguments\n' + '\n'.join(errors)) TypeError: Bad Arguments x: Expected y: Expected >>> pow(2, 3) 8 >>> pow(2, -1) Traceback (most recent call last): File "", line 1, in File "validate.py", line 83, in wrapper raise TypeError(f'Bad return: {e}') from None TypeError: Bad return: Expected >>> ``` Your decorator should try to patch up the exceptions so that they show more useful information as shown. Also, the `@validated` decorator should work in classes (you don't need to do anything special). ```python class Stock: def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price @property def cost(self): return self.shares * self.price @validated def sell(self, nshares:PositiveInteger): self.shares -= nshares ``` Note: This part doesn't involve a lot of code, but there are a lot of low-level fiddly bits. The solution will look almost the same as for Exercise 6.5. Don't be shy about looking at solution code though. \[ [Solution](soln7_1.md) | [Index](index.md) | [Exercise 6.5](ex6_5.md) | [Exercise 7.2](ex7_2.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex7_2.md ================================================ \[ [Index](index.md) | [Exercise 7.1](ex7_1.md) | [Exercise 7.3](ex7_3.md) \] # Exercise 7.2 *Objectives:* - Decorator chaining - Defining decorators that accept arguments. *Files Modified:* `logcall.py`, `validate.py` ## (a) Copying Metadata When a function gets wrapped by a decorator, you often lose information about the name of the function, documentation strings, and other details. Verify this: ```python >>> @logged def add(x,y): 'Adds two things' return x+y >>> add >>> help(add) ... look at the output ... >>> ``` Fix the definition of the `logged` decorator so that it copies function metadata properly. To do this, use the `@wraps(func)` decorator as shown in the notes. After you're done, make sure the decorator preserves the function name and doc string. ```python >>> @logged def add(x,y): 'Adds two things' return x+y >>> add >>> add.__doc__ 'Adds two things' >>> ``` Fix the `@validated` decorator you wrote earlier so that it also preserves metadata using `@wraps(func)`. ## (b) Your first decorator with arguments The `@logged` decorator you defined earlier always just prints a simple message with the function name. Suppose that you wanted the user to be able to specify a custom message of some sort. Define a new decorator `@logformat(fmt)` that accepts a format string as an argument and uses `fmt.format(func=func)` to format a supplied function into a log message: ```python # sample.py ... from logcall import logformat @logformat('{func.__code__.co_filename}:{func.__name__}') def mul(x,y): return x*y ``` To do this, you need to define a decorator that takes an argument. This is what it should look like when you test it: ```python >>> import sample Adding logging to add Adding logging to sub Adding logging to mul >>> sample.add(2,3) Calling add 5 >>> sample.mul(2,3) sample.py:mul 6 >>> ``` To further simplify the code, show how you can define the original `@logged` decorator using the the `@logformat` decorator. ## (c) Multiple decorators and methods Things can get a bit dicey when decorators are applied to methods in a class. Try applying your `@logged` decorator to the methods in the following class. ```python class Spam: @logged def instance_method(self): pass @logged @classmethod def class_method(cls): pass @logged @staticmethod def static_method(): pass @logged @property def property_method(self): pass ``` Does it even work at all? (hint: no). Is there any way to fix the code so that it works? For example, can you make it so the following example works? ```python >>> s = Spam() >>> s.instance_method() instance_method >>> Spam.class_method() class_method >>> Spam.static_method() static_method >>> s.property_method property_method >>> ``` ## (d) Validation (Redux) In the last exercise, you wrote a `@validated` decorator that enforced type annotations. For example: ```python @validated def add(x: Integer, y:Integer) -> Integer: return x + y ``` Make a new decorator `@enforce()` that enforces types specified via keyword arguments to the decorator instead. For example: ```python @enforce(x=Integer, y=Integer, return_=Integer) def add(x, y): return x + y ``` The resulting behavior of the decorated function should be identical. Note: Make the `return_` keyword specify the return type. `return` is a Python reserved word so you have to pick a slightly different name. **Discussion** Writing robust decorators is often a lot harder than it looks. Recommended reading: [How you implemented your Python decorator is wrong](https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/01-how-you-implemented-your-python-decorator-is-wrong.md) \[ [Solution](soln7_2.md) | [Index](index.md) | [Exercise 7.1](ex7_1.md) | [Exercise 7.3](ex7_3.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex7_3.md ================================================ \[ [Index](index.md) | [Exercise 7.2](ex7_2.md) | [Exercise 7.4](ex7_4.md) \] # Exercise 7.3 *Objectives:* - Learn about class decorators - Descriptors revisited *Files Modified:* `validate.py`, `structure.py` This exercise is going to pull together a bunch of topics we've developed over the last few days. Hang on to your hat. ## (a) Descriptors Revisited In [Exercise 4.3](ex4_3.md) you defined some descriptors that allowed a user to define classes with type-checked attributes like this: ```python from validate import String, PositiveInteger, PositiveFloat class Stock: name = String() shares = PositiveInteger() price = PositiveFloat() ... ``` Modify your `Stock` class so that it includes the above descriptors and now looks like this (see [Exercise 6.4](ex6_4.md)): ```python # stock.py from structure import Structure from validate import String, PositiveInteger, PositiveFloat class Stock(Structure): _fields = ('name', 'shares', 'price') name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares Stock.create_init() ``` Run the unit tests in `teststock.py`. You should see a significant number of tests passing with the addition of type checking. Excellent. ## (b) Using Class Decorators to Fill in Details An annoying aspect of the above code is there are extra details such as `_fields` variable and the final step of `Stock.create_init()`. A lot of this could be packaged into a class decorator instead. In the file `structure.py`, make a class decorator `@validate_attributes` that examines the class body for instances of Validators and fills in the `_fields` variable. For example: ```python # structure.py from validate import Validator def validate_attributes(cls): validators = [] for name, val in vars(cls).items(): if isinstance(val, Validator): validators.append(val) cls._fields = [val.name for val in validators] return cls ``` This code relies on the fact that class dictionaries are ordered starting in Python 3.6. Thus, it will encounter the different `Validator` descriptors in the order that they're listed. Using this order, you can then fill in the `_fields` variable. This allows you to write code like this: ```python # stock.py from structure import Structure, validate_attributes from validate import String, PositiveInteger, PositiveFloat @validate_attributes class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares Stock.create_init() ``` Once you've got this working, modify the `@validate_attributes` decorator to additionally perform the final step of calling `Stock.create_init()`. This will reduce the class to the following: ```python # stock.py from structure import Structure, validate_attributes from validate import String, PositiveInteger, PositiveFloat @validate_attributes class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ``` ## (c) Applying Decorators via Inheritance Having to specify the class decorator itself is kind of annoying. Modify the `Structure` class with the following `__init_subclass__()` method: ```python # structure.py class Structure: ... @classmethod def __init_subclass__(cls): validate_attributes(cls) ``` Once you've made this change, you should be able to drop the decorator entirely and solely rely on inheritance. It's inheritance plus some hidden magic! ```python # stock.py from structure import Structure from validate import String, PositiveInteger, PositiveFloat class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ``` Now, the code is really starting to go places. In fact, it almost looks normal. Let's keep pushing it. ## (d) Row Conversion One missing feature from the `Structure` class is a `from_row()` method that allows it to work with earlier CSV reading code. Let's fix that. Give the `Structure` class a `_types` class variable and the following class method: ```python # structure.py class Structure: _types = () ... @classmethod def from_row(cls, row): rowdata = [ func(val) for func, val in zip(cls._types, row) ] return cls(*rowdata) ... ``` Modify the `@validate_attributes` decorator so that it examines the various validators for an `expected_type` attribute and uses it to fill in the `_types` variable above. Once you've done this, you should be able to do things like this: ```python >>> s = Stock.from_row(['GOOG', '100', '490.1']) >>> s Stock('GOOG', 100, 490.1) >>> import reader >>> port = reader.read_csv_as_instances('Data/portfolio.csv', Stock) >>> ``` ## (e) Method Argument Checking Remember that `@validated` decorator you wrote in the last part? Let's modify the `@validate_attributes` decorator so that any method in the class with annotations gets wrapped by `@validated` automatically. This allows you to put enforced annotations on methods such as the `sell()` method: ```python # stock.py from structure import Structure from validate import String, PositiveInteger, PositiveFloat class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares: PositiveInteger): self.shares -= nshares ``` You'll find that `sell()` now enforces the argument. ```python >>> s = Stock('GOOG', 100, 490.1) >>> s.sell(25) >>> s.sell(-25) Traceback (most recent call last): ... TypeError: Bad Arguments nshares: must be >= 0 >>> ``` Yes, this starting to get very interesting now. The combination of a class decorator and inheritance is a powerful force. \[ [Solution](soln7_3.md) | [Index](index.md) | [Exercise 7.2](ex7_2.md) | [Exercise 7.4](ex7_4.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex7_4.md ================================================ \[ [Index](index.md) | [Exercise 7.3](ex7_3.md) | [Exercise 7.5](ex7_5.md) \] # Exercise 7.4 *Objectives:* - Learn about the low-level steps involved in creating a class *Files Modified:* `validate.py`, `structure.py` In this exercise, we look at the mechanics of how classes are actually created. ## (a) Class creation Recall, from earlier exercises, we defined a simple class `Stock` that looked like this: ```python class Stock: def __init__(self,name,shares,price): self.name = name self.shares = shares self.price = price def cost(self): return self.shares*self.price def sell(self,nshares): self.shares -= nshares ``` What we're going to do here is create the class manually. Start out by just defining the methods as normal Python functions. ```python >>> def __init__(self,name,shares,price): self.name = name self.shares = shares self.price = price >>> def cost(self): return self.shares*self.price >>> def sell(self,nshares): self.shares -= nshares >>> ``` Next, make a methods dictionary: ```python >>> methods = { '__init__' : __init__, 'cost' : cost, 'sell' : sell } >>> ``` Finally, create the `Stock` class object: ```python >>> Stock = type('Stock',(object,),methods) >>> s = Stock('GOOG',100,490.10) >>> s.name 'GOOG' >>> s.cost() 49010.0 >>> s.sell(25) >>> s.shares 75 >>> ``` Congratulations, you just created a class. A class is really nothing more than a name, a tuple of base classes, and a dictionary holding all of the class contents. `type()` is a constructor that creates a class for you if you supply these three parts. ## (b) Typed structures In the `structure.py` file, define the following function: ```python # structure.py ... def typed_structure(clsname, **validators): cls = type(clsname, (Structure,), validators) return cls ``` This function is somewhat similar to the `namedtuple()` function in that it creates a class. Try it out: ```python >>> from validate import String, PositiveInteger, PositiveFloat >>> from structure import typed_structure >>> Stock = typed_structure('Stock', name=String(), shares=PositiveInteger(), price=PositiveFloat()) >>> s = Stock('GOOG', 100, 490.1) >>> s.name 'GOOG' >>> s Stock('GOOG', 100, 490.1) >>> ``` You might find the seams of your head starting to pull apart about now. ## (c) Making a lot of classes There are other situations where direct usage of the `type()` constructor might be advantageous. Consider this bit of code: ```python # validate.py ... class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') super().check(value) class Integer(Typed): expected_type = int class Float(Typed): expected_type = float class String(Typed): expected_type = str ... ``` Wow is the last part of that annoying and repetitive. Change it to use a table of desired type classes like this: ```python # validate.py ... _typed_classes = [ ('Integer', int), ('Float', float), ('String', str) ] globals().update((name, type(name, (Typed,), {'expected_type':ty})) for name, ty in _typed_classes) ``` Now, if you want to have more type classes, you just add them to the table: ```python _typed_classes = [ ('Integer', int), ('Float', float), ('Complex', complex), ('Decimal', decimal.Decimal), ('List', list), ('Bool', bool), ('String', str) ] ``` Admit it, that's kind of cool and saves a lot of typing (at the keyboard). \[ [Solution](soln7_4.md) | [Index](index.md) | [Exercise 7.3](ex7_3.md) | [Exercise 7.5](ex7_5.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex7_5.md ================================================ \[ [Index](index.md) | [Exercise 7.4](ex7_4.md) | [Exercise 7.6](ex7_6.md) \] # Exercise 7.5 *Objectives:* - Create your first metaclass *Files Created:* `mymeta.py` ## (a) Create your first metaclass Create a file called `mymeta.py` and put the following code in it (from the slides): ```python # mymeta.py class mytype(type): @staticmethod def __new__(meta, name, bases, __dict__): print("Creating class :", name) print("Base classes :", bases) print("Attributes :", list(__dict__)) return super().__new__(meta, name, bases, __dict__) class myobject(metaclass=mytype): pass ``` Once you've done this, define a class that inherits from `myobject` instead of object. For example: ```python class Stock(myobject): def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ``` Try running your code and creating instances of `Stock`. See what happens. You should see the print statements from your `mytype` running once when the `Stock` class is defined. What happens if you inherit from `Stock`? ```python class MyStock(Stock): pass ``` You should still see your metaclass at work. Metaclasses are "sticky" in that they get applied across an entire inheritance hierarchy. **Discussion** Why would you want to do something like this? The main power of a metaclass is that it gives a programmer the ability to capture details about classes just prior to their creation. For example, in the `__new__()` method, you are given all of the basic details including the name of the class, base classes, and methods data. If you inspect this data, you can perform various types of diagnostic checks. If you're more daring, you can modify the data and change what gets placed in the class definition when it is created. Needless to say, there are many opportunities for horrible diabolical evil. \[ [Solution](soln7_5.md) | [Index](index.md) | [Exercise 7.4](ex7_4.md) | [Exercise 7.6](ex7_6.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex7_6.md ================================================ \[ [Index](index.md) | [Exercise 7.5](ex7_5.md) | [Exercise 8.1](ex8_1.md) \] # Exercise 7.6 *Objectives:* - Metaclasses in action - Explode your brain *Files Modified:* `structure.py`, `validate.py` ## (a) The Final Frontier In [Exercise 7.3](ex7_3.md), we made it possible to define type-checked structures as follows: ```python from validate import String, PositiveInteger, PositiveFloat from structure import Structure class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares: PositiveInteger): self.shares -= nshares ``` There are a lot of things going on under the covers. However, one annoyance concerns all of those type-name imports at the top (e.g., `String`, `PositiveInteger`, etc.). That's just the kind of thing that might lead to a `from validate import *` statement. One interesting thing about a metaclass is that it can be used to control the process by which a class gets defined. This includes managing the environment of a class definition itself. Let's tackle those imports. The first step in managing all of the validator names is to collect them. Go to the file `validate.py` and modify the `Validator` base class with this extra bit of code involving `__init_subclass__()` again: ```python # validate.py class Validator: ... # Collect all derived classes into a dict validators = { } @classmethod def __init_subclass__(cls): cls.validators[cls.__name__] = cls ``` That's not much, but it's creating a little namespace of all of the `Validator` subclasses. Take a look at it: ```python >>> from validate import Validator >>> Validator.validators {'Float': , 'Integer': , 'NonEmpty': , 'NonEmptyString': , 'Positive': , 'PositiveFloat': , 'PositiveInteger': , 'String': , 'Typed': } >>> ``` Now that you've done that, let's inject this namespace into namespace of classes defined from `Structure`. Define the following metaclass: ```python # structure.py ... from validate import Validator from collections import ChainMap class StructureMeta(type): @classmethod def __prepare__(meta, clsname, bases): return ChainMap({}, Validator.validators) @staticmethod def __new__(meta, name, bases, methods): methods = methods.maps[0] return super().__new__(meta, name, bases, methods) class Structure(metaclass=StructureMeta): ... ``` In this code, the `__prepare__()` method is making a special `ChainMap` mapping that consists of an empty dictionary and a dictionary of all of the defined validators. The empty dictionary that's listed first is going to collect all of the definitions made inside the class body. The `Validator.validators` dictionary is going to make all of the type definitions available to for use as descriptors or argument type annotations. The `__new__()` method discards extra the validator dictionary and passes the remaining definitions onto the type constructor. It's ingenious, but it lets you drop the annoying imports: ```python # stock.py from structure import Structure class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares: PositiveInteger): self.shares -= nshares ``` ## (b) Stare in Amazement Try running your `teststock.py` unit tests across this new file. Most of them should be passing now. For kicks, try your `Stock` class with some of the earlier code for tableformatting and reading data. It should all work. ```python >>> from stock import Stock >>> from reader import read_csv_as_instances >>> portfolio = read_csv_as_instances('Data/portfolio.csv', Stock) >>> portfolio [Stock('AA',100,32.2), Stock('IBM',50,91.1), Stock('CAT',150,83.44), Stock('MSFT',200,51.23), Stock('GE',95,40.37), Stock('MSFT',50,65.1), Stock('IBM',100,70.44)] >>> from tableformat import create_formatter, print_table >>> formatter = create_formatter('text') >>> print_table(portfolio, ['name','shares','price'], formatter) name shares price ---------- ---------- ---------- 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 >>> ``` Again, marvel at the final `stock.py` file and observe how clean the code looks. Just try not think about everything that is happening under the hood with the `Structure` base class. \[ [Solution](soln7_6.md) | [Index](index.md) | [Exercise 7.5](ex7_5.md) | [Exercise 8.1](ex8_1.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex8_1.md ================================================ \[ [Index](index.md) | [Exercise 7.6](ex7_6.md) | [Exercise 8.2](ex8_2.md) \] # Exercise 8.1 *Objectives:* - Learn how to customize iteration using generators *Files Modified:* `structure.py` *Files Created:* `follow.py` ## (a) A Simple Generator If you ever find yourself wanting to customize iteration, you should always think generator functions. They're easy to write---simply make a function that carries out the desired iteration logic and uses `yield` to emit values. For example, try this generator that allows you to iterate over a range of numbers with fractional steps (something not supported by the `range()` builtin): ```python >>> def frange(start,stop,step): while start < stop: yield start start += step >>> for x in frange(0, 2, 0.25): print(x, end=' ') 0 0.25 0.5 0.75 1.0 1.25 1.5 1.75 >>> ``` Iterating on a generator is a one-time operation. For example, here's what happen if you try to iterate twice: ```python >>> f = frange(0, 2, 0.25) >>> for x in f: print(x, end=' ') 0 0.25 0.5 0.75 1.0 1.25 1.5 1.75 >>> for x in f: print(x, end=' ') >>> ``` If you want to iterate over the same sequence, you need to recreate the generator by calling `frange()` again. Alternative, you could package everything into a class: ```python >>> class FRange: def __init__(self, start, stop, step): self.start = start self.stop = stop self.step = step def __iter__(self): n = self.start while n < self.stop: yield n n += self.step >>> f = FRange(0, 2, 0.25) >>> for x in f: print(x, end=' ') 0 0.25 0.5 0.75 1.0 1.25 1.5 1.75 >>> for x in f: print(x, end=' ') 0 0.25 0.5 0.75 1.0 1.25 1.5 1.75 >>> ``` ## (b) Adding Iteration to Objects If you've created a custom class, you can make it support iteration by defining an `__iter__()` special method. `__iter__()` returns an iterator as a result. As shown in the previous example, an easy way to do it is to define `__iter__()` as a generator. In earlier exercises, you defined a `Structure` base class. Add an `__iter__()` method to this class that produces the attribute values in order. For example: ```python class Structure(metaclass=StructureMeta): ... def __iter__(self): for name in self._fields: yield getattr(self, name) ... ``` Once you've done this, you should be able to iterate over the instance attributes like this: ```python >>> from stock import Stock >>> s = Stock('GOOG', 100, 490.1) >>> for val in s: print(val) GOOG 100 490.1 >>> ``` ## (c) The Surprising Power of Iteration Python uses iteration in ways you might not expect. Once you've added `__iter__()` to the `Structure` class, you'll find that it is easy to do all sorts of new operations. For example, conversions to sequences and unpacking: ```python >>> s = Stock('GOOG', 100, 490.1) >>> list(s) ['GOOG', 100, 490.1] >>> tuple(s) ('GOOG', 100, 490.1) >>> name, shares, price = s >>> name 'GOOG' >>> shares 100 >>> price 490.1 >>> ``` While we're at it, we can now add a comparison operator to our `Structure` class: ```python # structure.py class Structure(metaclass=StructureMeta): ... def __eq__(self, other): return isinstance(other, type(self)) and tuple(self) == tuple(other) ... ``` You should now be able to compare objects: ```python >>> a = Stock('GOOG', 100, 490.1) >>> b = Stock('GOOG', 100, 490.1) >>> a == b True >>> ``` Try running your `teststock.py` unit tests again. Everything should be passing now. Excellent. ## (d) Monitoring a streaming data source Generators can also be a useful way to simply produce a stream of data. In this part, we'll explore this idea by writing a generator to watch a log file. To start, follow the next instructions carefully. The program `Data/stocksim.py` is a program that simulates stock market data. As output, the program constantly writes real-time data to a file `stocklog.csv`. In a command window (not IDLE) go into the `Data/` directory and run this program: ``` % python3 stocksim.py ``` If you are on Windows, just locate the `stocksim.py` program and double-click on it to run it. Now, forget about this program (just let it run). Again, just let this program run in the background---it will run for several hours (you shouldn't need to worry about it). Once the above program is running, let's write a little program to open the file, seek to the end, and watch for new output. Create a file `follow.py` and put this code in it: ```python # follow.py import os import time f = open('Data/stocklog.csv') f.seek(0, os.SEEK_END) # Move file pointer 0 bytes from end of file while True: line = f.readline() if line == '': time.sleep(0.1) # Sleep briefly and retry continue fields = line.split(',') name = fields[0].strip('"') price = float(fields[1]) change = float(fields[4]) if change < 0: print('%10s %10.2f %10.2f' % (name, price, change)) ``` If you run the program, you'll see a real-time stock ticker. Under the covers, this code is kind of like the Unix `tail -f` command that's used to watch a log file. **Note:** The use of the `readline()` method in this example is somewhat unusual in that it is not the usual way of reading lines from a file (normally you would just use a `for`-loop). However, in this case, we are using it to repeatedly probe the end of the file to see if more data has been added (`readline()` will either return new data or an empty string). If you look at the code carefully, the first part of the code is producing lines of data whereas the statements at the end of the `while` loop are consuming the data. A major feature of generator functions is that you can move all of the data production code into a reusable function. Modify the code so that the file-reading is performed by a generator function `follow(filename)`. Make it so the following code works: ```python >>> for line in follow('Data/stocklog.csv'): print(line, end='') ... Should see lines of output produced here ... ``` Modify the stock ticker code so that it looks like this: ```python for line in follow('Data/stocklog.csv'): fields = line.split(',') name = fields[0].strip('"') price = float(fields[1]) change = float(fields[4]) if change < 0: print('%10s %10.2f %10.2f' % (name, price, change)) ``` **Discussion** Something very powerful just happened here. You moved an interesting iteration pattern (reading lines at the end of a file) into its own little function. The `follow()` function is now this completely general purpose utility that you can use in any program. For example, you could use it to watch server logs, debugging logs, and other similar data sources. That's kind of cool. \[ [Solution](soln8_1.md) | [Index](index.md) | [Exercise 7.6](ex7_6.md) | [Exercise 8.2](ex8_2.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex8_2.md ================================================ \[ [Index](index.md) | [Exercise 8.1](ex8_1.md) | [Exercise 8.3](ex8_3.md) \] # Exercise 8.2 *Objectives:* - Using generators to set up processing pipelines *Files Created:* `ticker.py` **Note** For this exercise the `stocksim.py` program should still be running in the background. You're going to use the `follow()` function you wrote in the previous exercise. ## (a) Setting up a processing pipeline A major power of generators is that they allow you to create programs that set up processing pipelines--much like pipes on Unix systems. Experiment with this concept by performing these steps: ```python >>> from follow import follow >>> import csv >>> lines = follow('Data/stocklog.csv') >>> rows = csv.reader(lines) >>> for row in rows: print(row) ['BA', '98.35', '6/11/2007', '09:41.07', '0.16', '98.25', '98.35', '98.31', '158148'] ['AA', '39.63', '6/11/2007', '09:41.07', '-0.03', '39.67', '39.63', '39.31', '270224'] ['XOM', '82.45', '6/11/2007', '09:41.07', '-0.23', '82.68', '82.64', '82.41', '748062'] ['PG', '62.95', '6/11/2007', '09:41.08', '-0.12', '62.80', '62.97', '62.61', '454327'] ... ``` Well, that's interesting. What you're seeing here is that the output of the `follow()` function has been piped into the `csv.reader()` function and we're now getting a sequence of split rows. ## (b) Making more pipeline components In a file `ticker.py`, define the following class (using your structure code from before) and set up a pipeline: ```python # ticker.py from structure import Structure class Ticker(Structure): name = String() price = Float() date = String() time = String() change = Float() open = Float() high = Float() low = Float() volume = Integer() if __name__ == '__main__': from follow import follow import csv lines = follow('Data/stocklog.csv') rows = csv.reader(lines) records = (Ticker.from_row(row) for row in rows) for record in records: print(record) ``` When you run this, you should see some output like this: Ticker('IBM',103.53,'6/11/2007','09:53.59',0.46,102.87,103.53,102.77,541633) Ticker('MSFT',30.21,'6/11/2007','09:54.01',0.16,30.05,30.21,29.95,7562516) Ticker('AA',40.01,'6/11/2007','09:54.01',0.35,39.67,40.15,39.31,576619) Ticker('T',40.1,'6/11/2007','09:54.08',-0.16,40.2,40.19,39.87,1312959) ## (c) Keep going Oh, you can do better than that. Let's plug this into your table generation code. Change the program to the following: ```python # ticker.py ... if __name__ == '__main__': from follow import follow import csv from tableformat import create_formatter, print_table formatter = create_formatter('text') lines = follow('Data/stocklog.csv') rows = csv.reader(lines) records = (Ticker.from_row(row) for row in rows) negative = (rec for rec in records if rec.change < 0) print_table(negative, ['name','price','change'], formatter) ``` This should produce some output that looks like this: name price change ---------- ---------- ---------- C 53.12 -0.21 UTX 70.04 -0.19 AXP 62.86 -0.18 MMM 85.72 -0.22 MCD 51.38 -0.03 WMT 49.85 -0.23 KO 51.6 -0.07 AIG 71.39 -0.14 PG 63.05 -0.02 HD 37.76 -0.19 Now, THAT is crazy! And pretty awesome. **Discussion** Some lessons learned: You can create various generator functions and chain them together to perform processing involving data-flow pipelines. A good mental model for generator functions might be Lego blocks. You can make a collection of small iterator patterns and start stacking them together in various ways. It can be an extremely powerful way to program. \[ [Solution](soln8_2.md) | [Index](index.md) | [Exercise 8.1](ex8_1.md) | [Exercise 8.3](ex8_3.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex8_3.md ================================================ \[ [Index](index.md) | [Exercise 8.2](ex8_2.md) | [Exercise 8.4](ex8_4.md) \] # Exercise 8.3 *Objectives:* - Using coroutines to set up processing pipelines *Files Created:* `cofollow.py`, `coticker.py` **Note** For this exercise the `stocksim.py` program should still be running in the background. In [Exercise 8.2](ex8_2.md) you wrote some code that used generators to set up a processing pipeline. A key aspect of that program was the idea of data flowing between generator functions. A very similar kind of dataflow can be set up using coroutines. The only difference is that with a coroutine, you send data into different processing elements as opposed to pulling data out with a for-loop. ## (a) A coroutine example Getting started with coroutines can be a little tricky. Here is an example program that performs the same task as [Exercise 8.2](ex8_2.md), but with coroutines. Take this program and copy it into a file called `cofollow.py`. ```python # cofollow.py import os import time # Data source def follow(filename,target): with open(filename,'r') as f: f.seek(0,os.SEEK_END) while True: line = f.readline() if line != '': target.send(line) else: time.sleep(0.1) # Decorator for coroutine functions from functools import wraps def consumer(func): @wraps(func) def start(*args,**kwargs): f = func(*args,**kwargs) f.send(None) return f return start # Sample coroutine @consumer def printer(): while True: item = yield # Receive an item sent to me print(item) # Example use if __name__ == '__main__': follow('Data/stocklog.csv',printer()) ``` Run this program and make sure produces output.. Make sure you understand how the different pieces are hooked together. ## (b) Build some pipeline components In a file `coticker.py`, build a series of pipeline components that carry out the same tasks as the `ticker.py` program in [Exercise 8.2](ex8_2.md). Here is the implementation of the various pieces. ```python # coticker.py from structure import Structure class Ticker(Structure): name = String() price =Float() date = String() time = String() change = Float() open = Float() high = Float() low = Float() volume = Integer() from cofollow import consumer, follow from tableformat import create_formatter import csv # This one is tricky. See solution for notes about it @consumer def to_csv(target): def producer(): while True: yield line reader = csv.reader(producer()) while True: line = yield target.send(next(reader)) @consumer def create_ticker(target): while True: row = yield target.send(Ticker.from_row(row)) @consumer def negchange(target): while True: record = yield if record.change < 0: target.send(record) @consumer def ticker(fmt, fields): formatter = create_formatter(fmt) formatter.headings(fields) while True: rec = yield row = [getattr(rec, name) for name in fields] formatter.row(row) ``` Your challenge: Write the main program that hooks all of these components together to generate the same stock ticker as in the previous exercise. \[ [Solution](soln8_3.md) | [Index](index.md) | [Exercise 8.2](ex8_2.md) | [Exercise 8.4](ex8_4.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex8_4.md ================================================ \[ [Index](index.md) | [Exercise 8.3](ex8_3.md) | [Exercise 8.5](ex8_5.md) \] # Exercise 8.4 *Objectives:* - Managing what happens at the `yield` statements *Files Modified:* `follow.py`, `cofollow.py` ## (a) Closing a Generator A common question concerning generators is their lifetime and garbage collection. For example, the `follow()` generator runs forever in an infinite `while` loop. What happens if the iteration loop that's driving it stops? Also, is there anyway to prematurely terminate the generator? Modify the `follow()` function so that all of the code is enclosed in a `try-except` block like this: ```python def follow(filename): try: with open(filename,'r') as f: f.seek(0,os.SEEK_END) while True: line = f.readline() if line == '': time.sleep(0.1) # Sleep briefly to avoid busy wait continue yield line except GeneratorExit: print('Following Done') ``` Now, try a few experiments: ```python >>> from follow import follow >>> # Experiment: Garbage collection of a running generator >>> f = follow('Data/stocklog.csv') >>> next(f) '"MO",70.29,"6/11/2007","09:30.09",-0.01,70.25,70.30,70.29,365314\n' >>> del f Following Done >>> # Experiment: Closing a generator >>> f = follow('Data/stocklog.csv') >>> for line in f: print(line,end='') if 'IBM' in line: f.close() "VZ",42.91,"6/11/2007","09:34.28",-0.16,42.95,42.91,42.78,210151 "HPQ",45.76,"6/11/2007","09:34.29",0.06,45.80,45.76,45.59,257169 "GM",31.45,"6/11/2007","09:34.31",0.45,31.00,31.50,31.45,582429 ... "IBM",102.86,"6/11/2007","09:34.44",-0.21,102.87,102.86,102.77,147550 Following Done >>> for line in f: print(line, end='') # No output: generator is done >>> ``` In these experiments you can see that a `GeneratorExit` exception is raised when a generator is garbage-collected or explicitly closed via its `close()` method. One additional area of exploration is whether or not you can resume iteration on a generator if you break out of a for-loop. For example, try this: ```python >>> f = follow('Data/stocklog.csv') >>> for line in f: print(line,end='') if 'IBM' in line: break "CAT",78.36,"6/11/2007","09:37.19",-0.16,78.32,78.36,77.99,237714 "VZ",42.99,"6/11/2007","09:37.20",-0.08,42.95,42.99,42.78,268459 ... "IBM",102.91,"6/11/2007","09:37.31",-0.16,102.87,102.91,102.77,190859 >>> # Resume iteration >>> for line in f: print(line,end='') if 'IBM' in line: break "AA",39.58,"6/11/2007","09:39.28",-0.08,39.67,39.58,39.31,243159 "HPQ",45.94,"6/11/2007","09:39.29",0.24,45.80,45.94,45.59,408919 ... "IBM",102.95,"6/11/2007","09:39.44",-0.12,102.87,102.95,102.77,225350 >>> del f Following Done >>> ``` In general, you can break out of running iteration and resume it later if you need to. You just need to make sure the generator object isn't forcefully closed or garbage collected somehow. ## (b) Raising Exceptions In the file `cofollow.py`, you created a coroutine `printer()`. Modify the code to catch and report exceptions like this: ```python # cofollow.py ... @consumer def printer(): while True: try: item = yield print(item) except Exception as e: print('ERROR: %r' % e) ``` Now, try an experiment: ```python >>> from cofollow import printer >>> p = printer() >>> p.send('hello') hello >>> p.send(42) 42 >>> p.throw(ValueError('It failed')) ERROR: ValueError('It failed',) >>> try: int('n/a') except ValueError as e: p.throw(e) ERROR: ValueError("invalid literal for int() with base 10: 'n/a'",) >>> ``` Notice how the running generator is not terminated by the exception. This is merely allowing the `yield` statement to signal an error instead of receiving a value. \[ [Solution](soln8_4.md) | [Index](index.md) | [Exercise 8.3](ex8_3.md) | [Exercise 8.5](ex8_5.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex8_5.md ================================================ \[ [Index](index.md) | [Exercise 8.4](ex8_4.md) | [Exercise 8.6](ex8_6.md) \] # Exercise 8.5 *Objectives:* - Learn about managed generators *Files Created:* `multitask.py`, `server.py` A generator or coroutine function can never execute without being driven by some other code. For example, a generator used for iteration doesn't do anything unless iteration is actually carried out using a for-loop. Similarly, a collection of coroutines won't run unless their `send()` method is invoked somehow. In advanced applications of generators, it is possible to drive generators in various unusual ways. In this exercise, we look at a few examples. ## (a) Generators as tasks If a file `multitask.py`, define the following code: ```python # multitask.py from collections import deque tasks = deque() def run(): while tasks: task = tasks.popleft() try: task.send(None) tasks.append(task) except StopIteration: print('Task done') ``` This code implements a tiny task scheduler that runs generator functions. Try it by running it on the following functions. ```python # multitask.py ... def countdown(n): while n > 0: print('T-minus', n) yield n -= 1 def countup(n): x = 0 while x < n: print('Up we go', x) yield x += 1 if __name__ == '__main__': tasks.append(countdown(10)) tasks.append(countdown(5)) tasks.append(countup(20)) run() ``` When you run this, you should see output from all of the generators interleaved together. For example: ```python T-minus 10 T-minus 5 Up we go 0 T-minus 9 T-minus 4 Up we go 1 T-minus 8 T-minus 3 Up we go 2 T-minus 7 T-minus 2 Up we go 3 T-minus 6 T-minus 1 Up we go 4 T-minus 5 Task done Up we go 5 T-minus 4 Up we go 6 T-minus 3 Up we go 7 T-minus 2 Up we go 8 T-minus 1 Up we go 9 Task done Up we go 10 Up we go 11 Up we go 12 Up we go 13 Up we go 14 Up we go 15 Up we go 16 Up we go 17 Up we go 18 Up we go 19 Task done ``` That's interesting, but not especially compelling. Move on to the next example. ## (b) Generators as Tasks Serving Network Connections Create a file `server.py` and put the following code into it: ```python # server.py from socket import * from select import select from collections import deque tasks = deque() recv_wait = {} # sock -> task send_wait = {} # sock -> task def run(): while any([tasks, recv_wait, send_wait]): while not tasks: can_recv, can_send, _ = select(recv_wait, send_wait, []) for s in can_recv: tasks.append(recv_wait.pop(s)) for s in can_send: tasks.append(send_wait.pop(s)) task = tasks.popleft() try: reason, resource = task.send(None) if reason == 'recv': recv_wait[resource] = task elif reason == 'send': send_wait[resource] = task else: raise RuntimeError('Unknown reason %r' % reason) except StopIteration: print('Task done') ``` This code is a slightly more complicated version of the task scheduler in part (a). It will require a bit of study, but the idea is that not only will each task yield, it will indicate a reason for doing so (receiving or sending). Depending on the reason, the task will move over to a waiting area. The scheduler then runs any available tasks or waits for I/O events to occur when nothing is left to do. It's all a bit tricky perhaps, but add the following code which implements a simple echo server: ```python # server.py ... def tcp_server(address, handler): sock = socket(AF_INET, SOCK_STREAM) sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.bind(address) sock.listen(5) while True: yield 'recv', sock client, addr = sock.accept() tasks.append(handler(client, addr)) def echo_handler(client, address): print('Connection from', address) while True: yield 'recv', client data = client.recv(1000) if not data: break yield 'send', client client.send(b'GOT:' + data) print('Connection closed') if __name__ == '__main__': tasks.append(tcp_server(('',25000), echo_handler)) run() ``` Run this server in its own terminal window. In another terminal, connect to it using a command such as `telnet` or `nc`. For example: ``` bash % nc localhost 25000 Hello Got: Hello World Got: World ``` If you don't have access to `nc` or `telnet` you can also use Python itself: ``` bash % python3 -m telnetlib localhost 25000 Hello Got: Hello World Got: World ``` If it's working, you should see output being echoed back to you. Not only that, if you connect multiple clients, they'll all operate concurrently. This tricky use of generators is not something that you would likely have to code directly. However, they are used in certain advanced packages such as `asyncio` that was added to the standard library in Python 3.4. \[ [Solution](soln8_5.md) | [Index](index.md) | [Exercise 8.4](ex8_4.md) | [Exercise 8.6](ex8_6.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex8_6.md ================================================ \[ [Index](index.md) | [Exercise 8.5](ex8_5.md) | [Exercise 9.1](ex9_1.md) \] # Exercise 8.6 *Objectives:* - Learn about delegating generators *Files Modified:* `cofollow.py`, `server.py` One potential issue in code that relies on generators is the problem of hiding details from the user and writing libraries. A lot of low-level mechanics are generally required to drive everything and it's often rather awkward to directly expose it to users. Starting in Python 3.3, a new `yield from` statement can be used to delegate generators to another function. It is a useful way to clean-up code that relies on generators. ## (a) Example: Receiving messages In [Exercise 8.3](ex8_3.md), we looked at the definitions of coroutines. Coroutines were functions that you sent data to. For example: ```python >>> from cofollow import consumer >>> @consumer def printer(): while True: item = yield print('Got:', item) >>> p = printer() >>> p.send('Hello') Got: Hello >>> p.send('World') Got: World >>> ``` At the time, it might have been interesting to use `yield` to receive a value. However, if you really look at the code, it looks pretty weird--a bare `yield` like that? What's going on there? In the `cofollow.py` file, define the following function: ```python def receive(expected_type): msg = yield assert isinstance(msg, expected_type), 'Expected type %s' % (expected_type) return msg ``` This function receives a message, but then verifies that it is of an expected type. Try it: ```python >>> from cofollow import consumer, receive >>> @consumer def print_ints(): while True: val = yield from receive(int) print('Got:', val) >>> p = print_ints() >>> p.send(42) Got: 42 >>> p.send(13) Got: 13 >>> p.send('13') Traceback (most recent call last): File "", line 1, in ... AssertionError: Expected type >>> ``` From a readability point of view, the `yield from receive(int)` statement is a bit more descriptive--it indicates that the function will yield until it receives a message of a given type. Now, modify all of the coroutines in `coticker.py` to use the new `receive()` function and make sure the code from [Exercise 8.3](ex8_3.md) still works. ## (b) Wrapping a Socket In the previous exercise, you wrote a simple network echo server using generators. The code for the server looked like this: ```python def tcp_server(address, handler): sock = socket(AF_INET, SOCK_STREAM) sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.bind(address) sock.listen(5) while True: yield 'recv', sock client, addr = sock.accept() tasks.append(handler(client, addr)) def echo_handler(client, address): print('Connection from', address) while True: yield 'recv', client data = client.recv(1000) if not data: break yield 'send', client client.send(b'GOT:', data) print('Connection closed') ``` Create a class `GenSocket` that cleans up the `yield` statements and allows the server to be rewritten more simply as follows: ```python def tcp_server(address, handler): sock = GenSocket(socket(AF_INET, SOCK_STREAM)) sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.bind(address) sock.listen(5) while True: client, addr = yield from sock.accept() tasks.append(handler(client, addr)) def echo_handler(client, address): print('Connection from', address) while True: data = yield from client.recv(1000) if not data: break yield from client.send(b'GOT:', data) print('Connection closed') ``` ## (c) Async/Await Take the `GenSocket` class you just wrote and wrap all of the methods that use `yield` with the `@coroutine` decorator from the `types` module. ```python from types import coroutine ... class GenSocket: def __init__(self, sock): self.sock = sock @coroutine def accept(self): yield 'recv', self.sock client, addr = self.sock.accept() return GenSocket(client), addr @coroutine def recv(self, maxsize): yield 'recv', self.sock return self.sock.recv(maxsize) @coroutine def send(self, data): yield 'send', self.sock return self.sock.send(data) def __getattr__(self, name): return getattr(self.sock, name) ``` Now, rewrite your server code to use `async` functions and `await` statements like this: ```python async def tcp_server(address, handler): sock = GenSocket(socket(AF_INET, SOCK_STREAM)) sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.bind(address) sock.listen(5) while True: client, addr = await sock.accept() tasks.append(handler(client, addr)) async def echo_handler(client, address): print('Connection from', address) while True: data = await client.recv(1000) if not data: break await client.send(b'GOT:', data) print('Connection closed') ``` \[ [Solution](soln8_6.md) | [Index](index.md) | [Exercise 8.5](ex8_5.md) | [Exercise 9.1](ex9_1.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex9_1.md ================================================ \[ [Index](index.md) | [Exercise 8.6](ex8_6.md) | [Exercise 9.2](ex9_2.md) \] # Exercise 9.1 *Objectives:* - A review of module basics This exercise is about some of the more tricky details of library modules. Start this exercise by creating a very simple library module: ```python # simplemod.py x = 42 # A global variable # A simple function def foo(): print('x is', x) # A simple class class Spam: def yow(self): print('Yow!') # A scripting statement print('Loaded simplemod') ``` ## (a) Module Loading and System Path Try importing the module you just created: ```python >>> import simplemod Loaded simplemod >>> simplemod.foo() x is 42 >>> ``` If this failed with an `ImportError`, your path setting is flaky. Look at the value of `sys.path` and fix it. ```python >>> import sys >>> sys.path ... look at the result ... >>> ``` ## (b) Repeated Module Loading Make sure you understand that modules are only loaded once. Try a repeated import and notice how you do not see the output from the `print` function: ```python >>> import simplemod >>> ``` Try changing the value of `x` and see that a repeated import has no effect. ```python >>> simplemod.x 42 >>> simplemod.x = 13 >>> simplemod.x 13 >>> import simplemod >>> simplemod.x 13 >>> ``` Use `importlib.reload()` if you want to force a module to reload. ```python >>> import importlib >>> importlib.reload(simplemod) Loaded simplemod >>> simplemod.x 42 >>> ``` `sys.modules` is a dictionary of all loaded modules. Take a look at it, delete your module, and try a repeated import. ```python >>> sys.modules ... look at output ... >>> sys.modules['simplemod'] >>> del sys.modules['simplemod'] >>> import simplemod Loaded simplemod >>> ``` ## (c) from module import Restart Python and import a selected symbol from a module. ```python >>> ############### [ RESTART ] ############### >>> from simplemod import foo Loaded simplemod >>> foo() x is 42 >>> ``` Notice how this loaded the entire module (observe the output of the print function and how the `x` variable is used). When you use `from`, the module object itself is not visible. For example: ```python >>> simplemod.foo() Traceback (most recent call last): File "", line 1, in NameError: name 'simplemod' is not defined >>> ``` Make sure you understand that when you export things from a module, they are simply name references. For example, try this and explain: ```python >>> from simplemod import x,foo >>> x 42 >>> foo() x is 42 >>> x = 13 >>> foo() x is 42 # !! Please explain >>> x 13 >>> ``` ## (d) Broken reload() Create an instance: ```python >>> import simplemod >>> s = simplemod.Spam() >>> s.yow() Yow! >>> ``` Now, go to the `simplemod.py` file and change the implementation of `Spam.yow()` to the following: ```python # simplemod.py ... class Spam: def yow(self): print('More Yow!') ``` Now, watch what happens on a reload. Do not restart Python for this part. ```python >>> importlib.reload(simplemod) Loaded simplemod >>> s.yow() 'Yow!' >>> t = simplemod.Spam() >>> t.yow() 'More Yow!' >>> ``` Notice how you have two instances of `Spam`, but they're using different implementations of the `yow()` method. Yes, actually both versions of code are loaded at the same time. You'll find other oddities as well. For example: ```python >>> s >>> isinstance(s, simplemod.Spam) False >>> isinstance(t, simplemod.Spam) True >>> ``` Bottom line: It's probably best not to rely on reloading for anything important. It might be fine if you're just trying to debug some things (as long as you're aware of its limitations and dangers). \[ [Solution](soln9_1.md) | [Index](index.md) | [Exercise 8.6](ex8_6.md) | [Exercise 9.2](ex9_2.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex9_2.md ================================================ \[ [Index](index.md) | [Exercise 9.1](ex9_1.md) | [Exercise 9.3](ex9_3.md) \] # Exercise 9.2 *Objectives:* - Learn how to create a Python package **Note** This exercise mostly just involves copying files on the file system. There shouldn't be a lot of coding. ## (a) Making a Package In previous exercises, you created the following files that were related to type-checked structures, reading data, and making tables: - `structure.py` - `validate.py` - `reader.py` - `tableformat.py` Your task is to take all of these files and move them into a package called `structly`. To do that, follow these steps: - Make a directory called `structly` - Make an empty file `__init__.py` and put it in the `structly` directory - Move the files `structure.py`, `validate.py`, `reader.py`, and `tableformat.py` into the `structly` directory. - Fix any import statements between modules (specifically, the `structure` module depends on `validate`). Once you've done that, modify the `stock.py` program so that it looks exactly like this and that it works: ```python # stock.py from structly.structure import Structure class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares: PositiveInteger): self.shares -= nshares if __name__ == '__main__': from structly.reader import read_csv_as_instances from structly.tableformat import create_formatter, print_table portfolio = read_csv_as_instances('Data/portfolio.csv', Stock) formatter = create_formatter('text') print_table(portfolio, ['name','shares','price'], formatter) ``` \[ [Solution](soln9_2.md) | [Index](index.md) | [Exercise 9.1](ex9_1.md) | [Exercise 9.3](ex9_3.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex9_3.md ================================================ \[ [Index](index.md) | [Exercise 9.2](ex9_2.md) | [Exercise 9.4](ex9_4.md) \] # Exercise 9.3 *Objectives:* - Learn about controlling symbols and combining submodules - Learn about module splitting One potentially annoying aspect of packages is that they complicate import statements. For example, in the `stock.py` program, you now have import statements such as the following: ```python from structly.structure import Structure from structly.reader import read_csv_as_instances from structly.tableformat import create_formatter, print_table ``` If the package is meant to be used as a unified whole, it might be more sane (and easier) to consolidate everything into a single top level package. Let's do that: ## (a) Controlling Exported Symbols Modify all of the submodules in the `structly` package so that they explicitly define an `__all__` variable which exports selected symbols. Specifically: - `structure.py` should export `Structure` - `reader.py` should export all of the various `read_csv_as_*()` functions - `tableformat.py` exports `create_formatter()` and `print_table()` Now, in the `__init__.py` file, unify all of the submodules like this: ```python # structly/__init__.py from .structure import * from .reader import * from .tableformat import * ``` Once you have done this, you should be able to import everything from a single logical module: ```python # stock.py from structly import Structure class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares: PositiveInteger): self.shares -= nshares if __name__ == '__main__': from structly import read_csv_as_instances, create_formatter, print_table portfolio = read_csv_as_instances('Data/portfolio.csv', Stock) formatter = create_formatter('text') print_table(portfolio, ['name','shares','price'], formatter) ``` ## (b) Exporting Everything In the `structly/__init__.py`, define an `__all__` variable that contains all exported symbols. Once you've done this, you should be able to simplify the `stock.py` file further: ```python # stock.py from structly import * class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares: PositiveInteger): self.shares -= nshares if __name__ == '__main__': portfolio = read_csv_as_instances('Data/portfolio.csv', Stock) formatter = create_formatter('text') print_table(portfolio, ['name','shares','price'], formatter) ``` As an aside, use of the `from module import *` statement is generally frowned upon the Python community--especially if you're not sure what you're doing. That said, there are situations where it often makes sense. For example, if a package defines a large number of commonly used symbols or constants it might be useful to use it. ## (c) Module Splitting The file `structly/tableformat.py` contains code for creating tables in different formats. Specifically: - A `TableFormatter` base class. - A `TextTableFormatter` class. - A `CSVTableFormatter` class. - A `HTMLTableFormatter` class. Instead of having all of these classes in a single `.py` file, maybe it would make sense to move each concrete formatter to its own file. To do this, we're going to split the `tableformat.py` file into parts. Follow these instructions carefully: First, remove the `structly/__pycache__` directory. ``` % cd structly % rm -rf __pycache__ ``` Next, create the directory `structly/tableformat`. This directory must have exactly the same name as the module it is replacing (`tableformat.py`). ``` bash % mkdir tableformat bash % ``` Move the original `tableformat.py` file into the new `tableformat` directory and rename it to `formatter.py`. ``` bash % mv tableformat.py tableformat/formatter.py bash % ``` In the `tableformat` directory, split the `tableformat.py` code into the following files and directories: - `formatter.py` - Contains the `TableFormatter` base class, mixins, and various functions. - `formats/text.py` - Contains the `TextTableFormatter` class. - `formats/csv.py` - Contains the `CSVTableFormatter` class. - `formats/html.py` - Contains the `HTMLTableFormatter` class. Add an `__init__.py` file to the `tableformat/` and `tableformat/formats` directories. Have the `tableformat/__init__.py` export the same symbols that the original `tableformat.py` file exported. After you have made all of these changes, you should have a package structure that looks like this: ``` structly/ __init__.py validate.py reader.py structure.py tableformat/ __init__.py formatter.py formats/ __init__.py text.py csv.py html.py ``` To users, everything should work exactly as it did before. For example, your prior `stock.py` file should work: ```python # stock.py from structly import * class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares if __name__ == '__main__': portfolio = read_csv_as_instances('Data/portfolio.csv', Stock) formatter = create_formatter('text') print_table(portfolio, ['name','shares','price'], formatter) ``` \[ [Solution](soln9_3.md) | [Index](index.md) | [Exercise 9.2](ex9_2.md) | [Exercise 9.4](ex9_4.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/ex9_4.md ================================================ \[ [Index](index.md) | [Exercise 9.3](ex9_3.md) | []() \] # Exercise 9.4 *Objectives:* - Explore circular imports - Dynamic module imports In the last exercise, you split the `tableformat.py` file up into submodules. The last part of the resulting `tableformat/formatter.py` file has turned into a mess of imports. ```python # tableformat.py ... class TableFormatter(ABC): @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass from .formats.text import TextTableFormatter from .formats.csv import CSVTableFormatter from .formats.html import HTMLTableFormatter ... def create_formatter(name, column_formats=None, upper_headers=False): if name == 'text': formatter_cls = TextTableFormatter elif name == 'csv': formatter_cls = CSVTableFormatter elif name == 'html': formatter_cls = HTMLTableFormatter else: raise RuntimeError('Unknown format %s' % name) if column_formats: class formatter_cls(ColumnFormatMixin, formatter_cls): formats = column_formats if upper_headers: class formatter_cls(UpperHeadersMixin, formatter_cls): pass return formatter_cls() ``` The imports in the middle of the file are required because the `create_formatter()` function needs them to find the appropriate classes. Really, the whole thing is a mess. ## (a) Circular Imports Try moving the following import statements to the top of the `formatter.py` file: ```python # formatter.py from .formats.text import TextTableFormatter from .formats.csv import CSVTableFormatter from .formats.html import HTMLTableFormatter class TableFormatter(ABC): @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass ... ``` Observe that nothing works anymore. Try running the `stock.py` program and notice the error about `TableFormatter` not being defined. The order of import statements matters and you can't just move the imports anywhere you want. Move the import statements back where they were. Sigh. ## (b) Subclass Registration Try the following experiment and observe: ```python >>> from structly.tableformat.formats.text import TextTableFormatter >>> TextTableFormatter.__module__ 'structly.tableformat.formats.text' >>> TextTableFormatter.__module__.split('.')[-1] 'text' >>> ``` Modify the `TableFormatter` base class by adding a dictionary and an `__init_subclass__()` method: ```python class TableFormatter(ABC): _formats = { } @classmethod def __init_subclass__(cls): name = cls.__module__.split('.')[-1] TableFormatter._formats[name] = cls @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass ``` This makes the parent class track all of its subclasses. Check it out: ```python >>> from structly.tableformat.formatter import TableFormatter >>> TableFormatter._formats {'text': , 'csv': , 'html': } >>> ``` Modify the `create_formatter()` function to look up the class in this dictionary instead: ```python def create_formatter(name, column_formats=None, upper_headers=False): formatter_cls = TableFormatter._formats.get(name) if not formatter_cls: raise RuntimeError('Unknown format %s' % name) if column_formats: class formatter_cls(ColumnFormatMixin, formatter_cls): formats = column_formats if upper_headers: class formatter_cls(UpperHeadersMixin, formatter_cls): pass return formatter_cls() ``` Run the `stock.py` program. Make sure it still works after you've made these changes. Just a note that all of the import statements are still there. You've mainly just cleaned up the code a bit and eliminated the hard-wired class names. ## (c) Dynamic Imports You're now ready for the final frontier. Delete the following import statements altogether: ```python # formatter.py ... from .formats.text import TextTableFormatter # DELETE from .formats.csv import CSVTableFormatter # DELETE from .formats.html import HTMLTableFormatter # DELETE ... ``` Run your `stock.py` code again--it should fail with an error. It knows nothing about the text formatter. Fix it by adding this tiny fragment of code to `create_formatter()`: ```python def create_formatter(name, column_formats=None, upper_headers=False): if name not in TableFormatter._formats: __import__(f'{__package__}.formats.{name}') ... ``` This code attempts a dynamic import of a formatter module if nothing is known about the name. The import alone (if it works) will register the class with the `_formats` dictionary and everything will just work. Magic! Try running the `stock.py` code and make sure it works afterwards. \[ [Solution](soln9_4.md) | [Index](index.md) | [Exercise 9.3](ex9_3.md) \] ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Exercises/index.md ================================================ # Advanced Python Mastery Exercises Copyright (C) 2007-2023 David M. Beazley https://www.dabeaz.com This page contains links to all of the course exercises. For the best experience, the exercises should be worked in order as later exercises often build upon earlier exercises. The [`Solutions/`](../Solutions) directory contains fully worked out solutions to every exercise. You can use this if you get stuck or you need to reset your code to a working state. Before beginning, you should fork/clone the GitHub repo so that you have a local copy on your machine. Exercises assume that all of your work will take place in the top-level `python-mastery` directory. Associated course presentation slides can be found at [PythonMastery.pdf](../PythonMastery.pdf). ## 1. Python Review - [Exercise 1.1](ex1_1.md) - [Exercise 1.2](ex1_2.md) - [Exercise 1.3](ex1_3.md) - [Exercise 1.4](ex1_4.md) - [Exercise 1.5](ex1_5.md) - [Exercise 1.6](ex1_6.md) ## 2. Data Handling - [Exercise 2.1](ex2_1.md) - [Exercise 2.2](ex2_2.md) - [Exercise 2.3](ex2_3.md) - [Exercise 2.4](ex2_4.md) - [Exercise 2.5](ex2_5.md) - [Exercise 2.6](ex2_6.md) ## 3. Classes and Objects - [Exercise 3.1](ex3_1.md) - [Exercise 3.2](ex3_2.md) - [Exercise 3.3](ex3_3.md) - [Exercise 3.4](ex3_4.md) - [Exercise 3.5](ex3_5.md) - [Exercise 3.6](ex3_6.md) - [Exercise 3.7](ex3_7.md) - [Exercise 3.8](ex3_8.md) ## 4. Inside Python Objects - [Exercise 4.1](ex4_1.md) - [Exercise 4.2](ex4_2.md) - [Exercise 4.3](ex4_3.md) - [Exercise 4.4](ex4_4.md) ## 5. Functions, Errors, and Testing - [Exercise 5.1](ex5_1.md) - [Exercise 5.2](ex5_2.md) - [Exercise 5.3](ex5_3.md) - [Exercise 5.4](ex5_4.md) - [Exercise 5.5](ex5_5.md) - [Exercise 5.6](ex5_6.md) ## 6. Working with Code - [Exercise 6.1](ex6_1.md) - [Exercise 6.2](ex6_2.md) - [Exercise 6.3](ex6_3.md) - [Exercise 6.4](ex6_4.md) - [Exercise 6.5](ex6_5.md) ## 7. Metaprogramming - [Exercise 7.1](ex7_1.md) - [Exercise 7.2](ex7_2.md) - [Exercise 7.3](ex7_3.md) - [Exercise 7.4](ex7_4.md) - [Exercise 7.5](ex7_5.md) - [Exercise 7.6](ex7_6.md) ## 8. Iterators, Generators, and Coroutines - [Exercise 8.1](ex8_1.md) - [Exercise 8.2](ex8_2.md) - [Exercise 8.3](ex8_3.md) - [Exercise 8.4](ex8_4.md) - [Exercise 8.5](ex8_5.md) - [Exercise 8.6](ex8_6.md) ## 9. Modules and Packages - [Exercise 9.1](ex9_1.md) - [Exercise 9.2](ex9_2.md) - [Exercise 9.3](ex9_3.md) - [Exercise 9.4](ex9_4.md) ================================================ FILE: Exercises/soln1_1.md ================================================ # Exercise 1.1 - Solution Check [here](../Solutions/1_1/art.py) ================================================ FILE: Exercises/soln1_2.md ================================================ # Exercise 1.2 - Solution Solution code is shown in the exercise. [Back](ex1_2.md) ================================================ FILE: Exercises/soln1_3.md ================================================ # Exercise 1.3 - Solution ```python # pcost.py total_cost = 0.0 with open('Data/portfolio.dat', 'r') as f: for line in f: fields = line.split() nshares = int(fields[1]) price = float(fields[2]) total_cost = total_cost + nshares * price print(total_cost) ``` [Back](ex1_3.md) ================================================ FILE: Exercises/soln1_4.md ================================================ # Exercise 1.4 - Solution ## (a) Defining a function ```python # pcost.py def portfolio_cost(filename): total_cost = 0.0 with open(filename) as f: for line in f: fields = line.split() nshares = int(fields[1]) price = float(fields[2]) total_cost = total_cost + nshares * price return total_cost print(portfolio_cost('Data/portfolio.dat')) ``` ## (b) Adding some error handling ```python # pcost.py def portfolio_cost(filename): total_cost = 0.0 with open(filename) as f: for line in f: fields = line.split() try: nshares = int(fields[1]) price = float(fields[2]) total_cost = total_cost + nshares*price # This catches errors in int() and float() conversions above except ValueError as e: print("Couldn't parse:", repr(line)) print("Reason:", e) return total_cost print(portfolio_cost('Data/portfolio3.dat')) ``` [Back](ex1_4.md) ================================================ FILE: Exercises/soln1_5.md ================================================ # Exercise 1.5 - Solution The solution is shown in the exercise description. [Back](ex1_5.md) ================================================ FILE: Exercises/soln1_6.md ================================================ # Exercise 1.6 - Solution ## (b) Main Module ```python # pcost.py def portfolio_cost(filename): total_cost = 0.0 with open(filename) as f: for line in f: fields = line.split() try: nshares = int(fields[1]) price = float(fields[2]) total_cost = total_cost + nshares * price # This catches errors in int() and float() conversions above except ValueError as e: print("Couldn't parse:", line) print("Reason:", e) return total_cost if __name__ == '__main__': print(portfolio_cost('Data/portfolio.dat')) ``` [Back](ex1_6.md) ================================================ FILE: Exercises/soln2_1.md ================================================ # Exercise 2.1 - Solution ```python # readrides.py import csv def read_rides_as_tuples(filename): ''' Read the bus ride data as a list of tuples ''' records = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = (route, date, daytype, rides) records.append(record) return records def read_rides_as_dicts(filename): ''' Read the bus ride data as a list of dicts ''' records = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = { 'route': route, 'date': date, 'daytype': daytype, 'rides' : rides } records.append(record) return records class Row: # Uncomment to see effect of slots # __slots__ = ('route', 'date', 'daytype', 'rides') def __init__(self, route, date, daytype, rides): self.route = route self.date = date self.daytype = daytype self.rides = rides # Uncomment to use a namedtuple instead #from collections import namedtuple #Row = namedtuple('Row',('route','date','daytype','rides')) def read_rides_as_instances(filename): ''' Read the bus ride data as a list of instances ''' records = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = Row(route, date, daytype, rides) records.append(record) return records if __name__ == '__main__': import tracemalloc tracemalloc.start() read_rides = read_rides_as_tuples # Change to as_dicts, as_instances, etc. rides = read_rides("Data/ctabus.csv") print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory()) ``` [Back](ex2_1.md) ================================================ FILE: Exercises/soln2_2.md ================================================ # Exercise 2.2 - Solution There is no official solution for this exercise--you need rely on your current Python knowledge. However, there are a few tips that can help. - For problems where you need to determine uniqueness, use a set. Sets aren't allowed to have duplicates. - If you need to tabulate data, consider using a dictionary that maps keys to a numeric count. For example, to count rides on each bus route, you could make a dictionary that maps the route name to the total ride count for that route. A `Counter` object from `collections` is perfect for this task. - If you need to keep data in order or sort data, store it in a list. - You can make Python data structures out of almost anything. For example, dictionaries of sets, nested dictionaries, etc. You might need to do this to answer questions 3 and 4. Even though no code is provided, here are some answers to the questions so that you can check your work: 1. How many bus routes exist in Chicago? (Answer: 181) 2. How many people rode the number 22 bus on February 2, 2011? (Answer: 5055) 3. What is the total number of rides taken on each bus route? (Answer: for the top three routes, [('79', 133796763), ('9', 117923787), ('49', 95915008)]) 4. What five bus routes had the greatest ten-year increase in ridership from 2001 to 2011? (Answer: [('15', 2732209), ('147', 2107910), ('66', 1612958), ('12', 1612067), ('14', 1351308)]) [Back](ex2_2.md) ================================================ FILE: Exercises/soln2_3.md ================================================ # Exercise 2.3 - Solution Work through the exercise. Code is given in the description. [Back](ex2_3.md) ================================================ FILE: Exercises/soln2_4.md ================================================ # Exercise 2.4 - Solution ```python # mutint.py from functools import total_ordering @total_ordering class MutInt: __slots__ = ['value'] def __init__(self, value): self.value = value def __str__(self): return str(self.value) def __repr__(self): return f'MutInt({self.value!r})' def __format__(self, fmt): return format(self.value, fmt) # Implement the "+" operator. Forward operands (MutInt + other) def __add__(self, other): if isinstance(other, MutInt): return MutInt(self.value + other.value) elif isinstance(other, int): return MutInt(self.value + other) else: return NotImplemented # Support for reversed operands (other + MutInt) __radd__ = __add__ # Support for in-place update (MutInt += other) def __iadd__(self, other): if isinstance(other, MutInt): self.value += other.value return self elif isinstance(other, int): self.value += other return self else: return NotImplemented # Support for equality testing def __eq__(self, other): if isinstance(other, MutInt): return self.value == other.value elif isinstance(other, int): return self.value == other else: return NotImplemented # One relation is needed for @total_ordering decorator. It fills in others def __lt__(self, other): if isinstance(other, MutInt): return self.value < other.value elif isinstance(other, int): return self.value < other else: return NotImplemented # Conversions to int() and float() def __int__(self): return int(self.value) def __float__(self): return float(self.value) # Support for indexing s[MutInt] __index__ = __int__ ``` [Back](ex2_4.md) ================================================ FILE: Exercises/soln2_5.md ================================================ # Exercise 2.5 - Solution Here is a version of `readrides.py` with changes for parts (c) and (d). ```python # readrides.py import csv def read_rides_as_tuples(filename): ''' Read the bus ride data as a list of tuples ''' records = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = (route, date, daytype, rides) records.append(record) return records def read_rides_as_dicts(filename): ''' Read the bus ride data as a list of dicts ''' records = RideData() # <---- CHANGED with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = { 'route': route, 'date': date, 'daytype': daytype, 'rides' : rides } records.append(record) return records class Row: __slots__ = ('route', 'date', 'daytype', 'rides') def __init__(self, route, date, daytype, rides): self.route = route self.date = date self.daytype = daytype self.rides = rides def read_rides_as_instances(filename): ''' Read the bus ride data as a list of instances ''' records = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = Row(route, date, daytype, rides) records.append(record) return records # Read as columns def read_rides_as_columns(filename): ''' Read the bus ride data into 4 lists, representing columns ''' routes = [] dates = [] daytypes = [] numrides = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: routes.append(row[0]) dates.append(row[1]) daytypes.append(row[2]) numrides.append(int(row[3])) return dict(routes=routes, dates=dates, daytypes=daytypes, numrides=numrides) # The great "fake" import collections class RideData(collections.abc.Sequence): def __init__(self): # Each value is a list with all of the values (a column) self.routes = [] self.dates = [] self.daytypes = [] self.numrides = [] def __len__(self): # All lists assumed to have the same length return len(self.routes) def append(self, d): self.routes.append(d['route']) self.dates.append(d['date']) self.daytypes.append(d['daytype']) self.numrides.append(d['rides']) def __getitem__(self, index): return { 'route': self.routes[index], 'date': self.dates[index], 'daytype': self.daytypes[index], 'rides': self.numrides[index] } if __name__ == '__main__': import tracemalloc tracemalloc.start() read_rides = read_rides_as_dicts # Change to as_dicts, as_instances, etc. rides = read_rides("Data/ctabus.csv") print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory()) ``` [Back](ex2_5.md) ================================================ FILE: Exercises/soln2_6.md ================================================ # Exercise 2.6 - Solution ```python # reader.py import csv from collections import defaultdict def read_csv_as_dicts(filename, types): ''' Read a CSV file with column type conversion ''' records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = { name: func(val) for name, func, val in zip(headers, types, row) } records.append(record) return records ``` [Back](ex2_6.md) ================================================ FILE: Exercises/soln3_1.md ================================================ # Exercise 3.1 - Solution ```python # stock.py class Stock: def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares def read_portfolio(filename): ''' Read a CSV file of stock data into a list of Stocks ''' import csv portfolio = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = Stock(row[0], int(row[1]), float(row[2])) portfolio.append(record) return portfolio def print_portfolio(portfolio): ''' Make a nicely formatted table showing stock data ''' print('%10s %10s %10s' % ('name', 'shares', 'price')) print(('-'*10 + ' ')*3) for s in portfolio: print('%10s %10d %10.2f' % (s.name, s.shares, s.price)) if __name__ == '__main__': portfolio = read_portfolio('Data/portfolio.csv') print_portfolio(portfolio) ``` [Back](ex3_1.md) ================================================ FILE: Exercises/soln3_2.md ================================================ # Exercise 3.2 - Solution (c) Table Formatter ```python # tableformat.py # Print a table def print_table(records, fields): print(' '.join('%10s' % fieldname for fieldname in fields)) print(('-'*10 + ' ')*len(fields)) for record in records: print(' '.join('%10s' % getattr(record, fieldname) for fieldname in fields)) ``` [Back](ex3_2.md) ================================================ FILE: Exercises/soln3_3.md ================================================ # Exercise 3.3 - Solution ```python # stock.py class Stock: types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls.types, row)] return cls(*values) def read_portfolio(filename): ''' Read a CSV file of stock data into a list of Stocks ''' import csv portfolio = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = Stock.from_row(row) portfolio.append(record) return portfolio if __name__ == '__main__': import tableformat import reader portfolio = read_portfolio('Data/portfolio.csv') # Generalized version # portfolio = reader.read_csv_as_instances('Data/portfolio.csv', Stock) tableformat.print_table(portfolio, ['name', 'shares', 'price']) ``` The `reader.py` file should now look like this: ```python # reader.py import csv from collections import defaultdict def read_csv_as_dicts(filename, types): ''' Read a CSV file into a list of dicts with column type conversion ''' records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = { name: func(val) for name, func, val in zip(headers, types, row) } records.append(record) return records def read_csv_as_instances(filename, cls): ''' Read a CSV file into a list of instances ''' records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: records.append(cls.from_row(row)) return records ``` [Back](ex3_3.md) ================================================ FILE: Exercises/soln3_4.md ================================================ # Exercise 3.4 - Solution ## (a) Private attributes ```python # stock.py class Stock: _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ``` ## (b) Computed Attributes ```python # stock.py class Stock: _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ``` ## (c) Enforcing Type-Checking Rules ```python # stock.py class Stock: _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, int): raise TypeError('Expected an integer') if value < 0: raise ValueError('shares must be >= 0') self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, float): raise TypeError('Expected a float') if value < 0: raise ValueError('price must be >= 0') self._price = value @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ``` ## (d) Adding `__slots__` ```python # stock.py class Stock: __slots__ = ('name','_shares','_price') _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, int): raise TypeError('Expected integer') if value < 0: raise ValueError('shares must be >= 0') self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, float): raise TypeError('Expected float') if value < 0: raise ValueError('price must be >= 0') self._price = value @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ``` ## (e) Reconciling types ```python # stock.py class Stock: __slots__ = ('name','_shares','_price') _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, self._types[1]): raise TypeError(f'Expected {self._types[1].__name__}') if value < 0: raise ValueError('shares must be >= 0') self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, self._types[2]): raise TypeError(f'Expected {self._types[2].__name__}') if value < 0: raise ValueError('price must be >= 0') self._price = value @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ``` [Back](ex3_4.md) ================================================ FILE: Exercises/soln3_5.md ================================================ # Exercise 3.5 - Solution ```python # tableformat.py def print_table(records, fields, formatter): formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) class TableFormatter: def headings(self, headers): raise NotImplementedError() def row(self, rowdata): raise NotImplementedError() class TextTableFormatter(TableFormatter): def headings(self, headers): print(' '.join('%10s' % h for h in headers)) print(('-'*10 + ' ')*len(headers)) def row(self, rowdata): print(' '.join('%10s' % d for d in rowdata)) class CSVTableFormatter(TableFormatter): def headings(self, headers): print(','.join(headers)) def row(self, rowdata): print(','.join(str(d) for d in rowdata)) class HTMLTableFormatter(TableFormatter): def headings(self, headers): print('', end=' ') for h in headers: print('%s' % h, end=' ') print('') def row(self, rowdata): print('', end=' ') for d in rowdata: print('%s' % d, end=' ') print('') def create_formatter(name): if name == 'text': formatter_cls = TextTableFormatter elif name == 'csv': formatter_cls = CSVTableFormatter elif name == 'html': formatter_cls = HTMLTableFormatter else: raise RuntimeError('Unknown format %s' % name) return formatter_cls() ``` [Back](ex3_5.md) ================================================ FILE: Exercises/soln3_6.md ================================================ # Exercise 3.6 - Solution ## (a) Better output for printing objects ```python # stock.py class Stock: ... def __repr__(self): # Note: The !r format code produces the repr() string return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})' ... ``` ## (b) Making Objects Comparable ```python class Stock: ... def __eq__(self, other): return isinstance(other, Stock) and ((self.name, self.shares, self.price) == (other.name, other.shares, other.price)) ... ``` ## (c) Context Managers Code is given in the exercise. [Back](ex3_6.md) ================================================ FILE: Exercises/soln3_7.md ================================================ # Exercise 3.7 - Solution ## (a) Interfaces ```python # tableformat.py def print_table(records, fields, formatter): if not isinstance(formatter, TableFormatter): raise TypeError('Expected a TableFormatter') formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) ... ``` ## (b) Abstract Base Classes ```python # tableformat.py from abc import ABC, abstractmethod class TableFormatter(ABC): @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass ``` ## (c) Algorithm Template Classes ```python # reader.py import csv from abc import ABC, abstractmethod class CSVParser(ABC): def parse(self, filename): records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = self.make_record(headers, row) records.append(record) return records @abstractmethod def make_record(self, headers, row): pass class DictCSVParser(CSVParser): def __init__(self, types): self.types = types def make_record(self, headers, row): return { name: func(val) for name, func, val in zip(headers, self.types, row) } class InstanceCSVParser(CSVParser): def __init__(self, cls): self.cls = cls def make_record(self, headers, row): return self.cls.from_row(row) def read_csv_as_dicts(filename, types): parser = DictCSVParser(types) return parser.parse(filename) def read_csv_as_instances(filename, cls): parser = InstanceCSVParser(cls) return parser.parse(filename) ``` [Back](ex3_7.md) ================================================ FILE: Exercises/soln3_8.md ================================================ # Exercise 3.8 - Solution ```python # tableformat.py from abc import ABC, abstractmethod def print_table(records, fields, formatter): if not isinstance(formatter, TableFormatter): raise RuntimeError('Expected a TableFormatter') formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) class TableFormatter(ABC): @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass class TextTableFormatter(TableFormatter): def headings(self, headers): print(' '.join('%10s' % h for h in headers)) print(('-'*10 + ' ')*len(headers)) def row(self, rowdata): print(' '.join('%10s' % d for d in rowdata)) class CSVTableFormatter(TableFormatter): def headings(self, headers): print(','.join(headers)) def row(self, rowdata): print(','.join(str(d) for d in rowdata)) class HTMLTableFormatter(TableFormatter): def headings(self, headers): print('', end=' ') for h in headers: print('%s' % h, end=' ') print('') def row(self, rowdata): print('', end=' ') for d in rowdata: print('%s' % d, end=' ') print('') class ColumnFormatMixin: formats = [] def row(self, rowdata): rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)] super().row(rowdata) class UpperHeadersMixin: def headings(self, headers): super().headings([h.upper() for h in headers]) def create_formatter(name, column_formats=None, upper_headers=False): if name == 'text': formatter_cls = TextTableFormatter elif name == 'csv': formatter_cls = CSVTableFormatter elif name == 'html': formatter_cls = HTMLTableFormatter else: raise RuntimeError('Unknown format %s' % name) if column_formats: class formatter_cls(ColumnFormatMixin, formatter_cls): formats = column_formats if upper_headers: class formatter_cls(UpperHeadersMixin, formatter_cls): pass return formatter_cls() ``` [Back](ex3_8.md) ================================================ FILE: Exercises/soln4_1.md ================================================ # Exercise 4.1 - Solution No solution. Just follow along. [Back](ex4_1.md) ================================================ FILE: Exercises/soln4_2.md ================================================ # Exercise 4.2 - Solution Here is the validator code in its entirety: ```python class Validator: @classmethod def check(cls, value): return value class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'Expected {cls.expected_type}') return super().check(value) class Integer(Typed): expected_type = int class Float(Typed): expected_type = float class String(Typed): expected_type = str class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('Must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('Must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass ``` ## (c) Using the validators ```python # validate.py ... if __name__ == '__main__': class Stock: __slots__ = ('name', '_shares', '_price') def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def shares(self): return self._shares @shares.setter def shares(self, value): self._shares = PositiveInteger.check(value) @property def price(self): return self._price @price.setter def price(self, value): self._price = PositiveFloat.check(value) @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ``` [Back](ex4_2.md) ================================================ FILE: Exercises/soln4_3.md ================================================ # Exercise 4.3 - Solution ```python class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') return super().check(value) class Integer(Typed): expected_type = int class Float(Typed): expected_type = float class String(Typed): expected_type = str class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass # Examples if __name__ == '__main__': def add(x, y): Integer.check(x) Integer.check(y) return x + y class Stock: name = NonEmptyString() shares = PositiveInteger() price = PositiveFloat() def __init__(self,name,shares,price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ``` [Back](ex4_3.md) ================================================ FILE: Exercises/soln4_4.md ================================================ # Exercise 4.4 - Solution Code is given in the exercise. [Back](ex4_4.md) ================================================ FILE: Exercises/soln5_1.md ================================================ # Exercise 5.1 - Solution This is a partial solution that does not include type-hints. ```python # reader.py import csv def csv_as_dicts(lines, types, *, headers=None): ''' Convert lines of CSV data into a list of dictionaries ''' records = [] rows = csv.reader(lines) if headers is None: headers = next(rows) for row in rows: record = { name: func(val) for name, func, val in zip(headers, types, row) } records.append(record) return records def csv_as_instances(lines, cls, *, headers=None): ''' Convert lines of CSV data into a list of instances ''' records = [] rows = csv.reader(lines) if headers is None: headers = next(rows) for row in rows: record = cls.from_row(row) records.append(record) return records def read_csv_as_dicts(filename, types, *, headers=None): ''' Read CSV data into a list of dictionaries with optional type conversion ''' with open(filename) as file: return csv_as_dicts(file, types, headers=headers) def read_csv_as_instances(filename, cls, *, headers=None): ''' Read CSV data into a list of instances ''' with open(filename) as file: return csv_as_instances(file, cls, headers=headers) ``` [Back](ex5_1.md) ================================================ FILE: Exercises/soln5_2.md ================================================ # Exercise 5.2 - Solution None. Just work through the exercise. [Back](ex5_2.md) ================================================ FILE: Exercises/soln5_3.md ================================================ # Exercise 5.3 - Solution ## (a) Higher order functions ```python # reader.py import csv def convert_csv(lines, converter, *, headers=None): records = [] rows = csv.reader(lines) if headers is None: headers = next(rows) for row in rows: record = converter(headers, row) records.append(record) return records def csv_as_dicts(lines, types, *, headers=None): return convert_csv(lines, lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) }) def csv_as_instances(lines, cls, *, headers=None): return convert_csv(lines, lambda headers, row: cls.from_row(row)) def read_csv_as_dicts(filename, types, *, headers=None): ''' Read CSV data into a list of dictionaries with optional type conversion ''' with open(filename) as file: return csv_as_dicts(file, types, headers=headers) def read_csv_as_instances(filename, cls, *, headers=None): ''' Read CSV data into a list of instances ''' with open(filename) as file: return csv_as_instances(file, cls, headers=headers) ``` ## (b) Using map() ```python # reader.py import csv def convert_csv(lines, converter, *, headers=None): rows = csv.reader(lines) if headers is None: headers = next(rows) return list(map(lambda row: converter(headers, row), rows)) ``` [Back](ex5_3.md) ================================================ FILE: Exercises/soln5_4.md ================================================ # Exercise 5.4 - Solution ```python # typedproperty.py def typedproperty(name, expected_type): private_name = '_' + name @property def value(self): return getattr(self, private_name) @value.setter def value(self, val): if not isinstance(val, expected_type): raise TypeError(f'Expected {expected_type}') setattr(self, private_name, val) return value String = lambda name: typedproperty(name, str) Integer = lambda name: typedproperty(name, int) Float = lambda name: typedproperty(name, float) # Example if __name__ == '__main__': class Stock: name = String('name') shares = Integer('shares') price = Float('price') def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price ``` [Back](ex5_4.md) ================================================ FILE: Exercises/soln5_5.md ================================================ # Exercise 5.5 - Solution ```python # reader.py import csv import logging log = logging.getLogger(__name__) def convert_csv(lines, converter, *, headers=None): rows = csv.reader(lines) if headers is None: headers = next(rows) records = [] for rowno, row in enumerate(rows, start=1): try: records.append(converter(headers, row)) except ValueError as e: log.warning('Row %s: Bad row: %s', rowno, row) log.debug('Row %s: Reason: %s', rowno, row) return records def csv_as_dicts(lines, types, *, headers=None): return convert_csv(lines, lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) }) def csv_as_instances(lines, cls, *, headers=None): return convert_csv(lines, lambda headers, row: cls.from_row(row)) def read_csv_as_dicts(filename, types, *, headers=None): ''' Read CSV data into a list of dictionaries with optional type conversion ''' with open(filename) as file: return csv_as_dicts(file, types, headers=headers) def read_csv_as_instances(filename, cls, *, headers=None): ''' Read CSV data into a list of instances ''' with open(filename) as file: return csv_as_instances(file, cls, headers=headers) ``` [Back](ex5_5.md) ================================================ FILE: Exercises/soln5_6.md ================================================ # Exercise 5.6 - Solution ## (b) Unit testing ```python # teststock.py import stock import unittest class TestStock(unittest.TestCase): def test_create(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_create_keyword(self): s = stock.Stock(name='GOOG', shares=100, price=490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_cost(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.cost, 49010.0) def test_sell(self): s = stock.Stock('GOOG', 100, 490.1) s.sell(25) self.assertEqual(s.shares, 75) def test_from_row(self): s = stock.Stock.from_row(['GOOG','100','490.1']) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_repr(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)") def test_eq(self): a = stock.Stock('GOOG', 100, 490.1) b = stock.Stock('GOOG', 100, 490.1) self.assertTrue(a==b) if __name__ == '__main__': unittest.main() ``` ## (c) Testing for Errors ```python # teststock.py import stock import unittest class TestStock(unittest.TestCase): def test_create(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_create_keyword(self): s = stock.Stock(name='GOOG', shares=100, price=490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_cost(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.cost, 49010.0) def test_sell(self): s = stock.Stock('GOOG', 100, 490.1) s.sell(25) self.assertEqual(s.shares, 75) def test_from_row(self): s = stock.Stock.from_row(['GOOG','100','490.1']) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_repr(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)") def test_eq(self): a = stock.Stock('GOOG', 100, 490.1) b = stock.Stock('GOOG', 100, 490.1) self.assertTrue(a==b) # Tests for failure conditions def test_shares_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.shares = '50' def test_shares_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.shares = -50 def test_price_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.price = '45.23' def test_price_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.price = -45.23 def test_bad_attribute(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(AttributeError): s.share = 100 if __name__ == '__main__': unittest.main() ``` [Back](ex5_6.md) ================================================ FILE: Exercises/soln6_1.md ================================================ # Exercise 6.1 - Solution ## (a) Simplified Structures ```python # structure.py class Structure: _fields = () def __init__(self, *args): if len(args) != len(self._fields): raise TypeError('Expected %d arguments' % len(self._fields)) for name, arg in zip(self._fields,args): setattr(self, name, arg) ``` ## (b) Making a Useful Representation [source, python] ``` class Structure: ... def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) ``` ## (c) Restricting Attribute Names [source, python] ``` class Structure: ... def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) ``` [Back](ex6_1.md) ================================================ FILE: Exercises/soln6_2.md ================================================ # Exercise 6.2 - Solution ```python # structure.py import sys class Structure: _fields = () @staticmethod def _init(): locs = sys._getframe(1).f_locals self = locs['self'] for name, val in locs.items(): if name == 'self': continue setattr(self, name, val) def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) ``` [Back](ex6_2.md) ================================================ FILE: Exercises/soln6_3.md ================================================ # Exercise 6.3 - Solution ```python # structure.py import sys import inspect class Structure: _fields = () @staticmethod def _init(): locs = sys._getframe(1).f_locals self = locs['self'] for name, val in locs.items(): if name == 'self': continue setattr(self, name, val) def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) @classmethod def set_fields(cls): sig = inspect.signature(cls) cls._fields = tuple(sig.parameters) ``` Here is the `Stock` class in progress: ```python # stock.py from structure import Structure class Stock(Structure): def __init__(self, name, shares, price): self._init() @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares Stock.set_fields() ``` [Back](ex6_3.md) ================================================ FILE: Exercises/soln6_4.md ================================================ # Exercise 6.4 - Solution ```python # structure.py class Structure: _fields = () def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) @classmethod def create_init(cls): args = ','.join(cls._fields) code = 'def __init__(self, {0}):\n'.format(args) statements = [ ' self.{0} = {0}'.format(name) for name in cls._fields] code += '\n'.join(statements) locs = { } exec(code, locs) cls.__init__ = locs['__init__'] ``` Here is the `Stock` class in progress after this change: ```python # stock.py from structure import Structure class Stock(Structure): _fields = ('name', 'shares', 'price') @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares Stock.create_init() ``` [Back](ex6_4.md) ================================================ FILE: Exercises/soln6_5.md ================================================ # Exercise 6.5 - Solution ```python # validate.py ... from inspect import signature class ValidatedFunction: def __init__(self, func): self.func = func self.signature = signature(func) self.annotations = dict(func.__annotations__) self.retcheck = self.annotations.pop('return', None) def __call__(self, *args, **kwargs): bound = self.signature.bind(*args, **kwargs) for name, val in self.annotations.items(): val.check(bound.arguments[name]) result = self.func(*args, **kwargs) if self.retcheck: self.retcheck.check(result) return result # Examples if __name__ == '__main__': def add(x:Integer, y:Integer) -> Integer: return x + y add = ValidatedFunction(add) ``` [Back](ex6_5.md) ================================================ FILE: Exercises/soln7_1.md ================================================ # Exercise 7.1 - Solution ```python # validate.py ... from inspect import signature def validated(func): sig = signature(func) # Gather the function annotations annotations = dict(func.__annotations__) # Get the return annotation (if any) retcheck = annotations.pop('return', None) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) # Enforce return check (if any) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper ``` [Back](ex7_1.md) ================================================ FILE: Exercises/soln7_2.md ================================================ # Exercise 7.2 - Solution ## (a) Copying Metadata ```python # logcall.py from functools import wraps def logged(func): print('Adding logging to', func.__name__) @wraps(func) def wrapper(*args,**kwargs): print('Calling', func.__name__) return func(*args,**kwargs) return wrapper ``` ## (b) Decorators with arguments ```python # logcall.py from functools import wraps ... def logformat(fmt): def logged(func): print('Adding logging to', func.__name__) @wraps(func) def wrapper(*args, **kwargs): print(fmt.format(func=func)) return func(*args, **kwargs) return wrapper return logged ``` The earlier `@logged` decorator can be rewritten as follows: ```python logged = logformat('Calling {func.__name__}') ``` ## (c) Decorators and methods You can get the code to work if you interchange the order of the decorators. For example: ```python from logcall import logged class Spam: @logged def instance_method(self): pass @classmethod @logged def class_method(cls): pass @staticmethod @logged def static_method(): pass @property @logged def property_method(self): pass ``` Ponder why it doesn't work in the original order. Is there any way to make the `@logged` decorator work regardless of the order in which its applied? ## (d) Validation (Redux) ```python # validate.py ... from inspect import signature from functools import wraps def validated(func): sig = signature(func) # Gather the function annotations annotations = dict(func.__annotations__) # Get the return annotation (if any) retcheck = annotations.pop('return', None) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) # Enforce return check (if any) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper def enforce(**annotations): retcheck = annotations.pop('return_', None) def decorate(func): sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper return decorate ``` [Back](ex7_2.md) ================================================ FILE: Exercises/soln7_3.md ================================================ # Exercise 7.3 - Solution Here's a full version of the `structure.py` file. ```python # structure.py from validate import Validator, validated class Structure: _fields = () _types = () def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) @classmethod def from_row(cls, row): rowdata = [ func(val) for func, val in zip(cls._types, row) ] return cls(*rowdata) @classmethod def create_init(cls): ''' Create an __init__ method from _fields ''' args = ','.join(cls._fields) code = f'def __init__(self, {args}):\n' for name in cls._fields: code += f' self.{name} = {name}\n' locs = { } exec(code, locs) cls.__init__ = locs['__init__'] @classmethod def __init_subclass__(cls): # Apply the validated decorator to subclasses validate_attributes(cls) def validate_attributes(cls): ''' Class decorator that scans a class definition for Validators and builds a _fields variable that captures their definition order. ''' validators = [] for name, val in vars(cls).items(): if isinstance(val, Validator): validators.append(val) # Apply validated decorator to any callable with annotations elif callable(val) and val.__annotations__: setattr(cls, name, validated(val)) # Collect all of the field names cls._fields = tuple([v.name for v in validators]) # Collect type conversions. The lambda x:x is an identity # function that's used in case no expected_type is found. cls._types = tuple([ getattr(v, 'expected_type', lambda x: x) for v in validators ]) # Create the __init__ method if cls._fields: cls.create_init() return cls ``` [Back](ex7_3.md) ================================================ FILE: Exercises/soln7_4.md ================================================ # Exercise 7.4 - Solution All of the solution code is given in the exercise description. [Back](ex7_4.md) ================================================ FILE: Exercises/soln7_5.md ================================================ # Exercise 7.5 - Solution Nothing here. Just follow the exercise. [Back](ex7_5.md) ================================================ FILE: Exercises/soln7_6.md ================================================ # Exercise 7.6 - Solution ```python # structure.py from validate import Validator, validated from collections import ChainMap class StructureMeta(type): @classmethod def __prepare__(meta, clsname, bases): return ChainMap({}, Validator.validators) @staticmethod def __new__(meta, name, bases, methods): methods = methods.maps[0] return super().__new__(meta, name, bases, methods) class Structure(metaclass=StructureMeta): _fields = () _types = () def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) @classmethod def from_row(cls, row): rowdata = [ func(val) for func, val in zip(cls._types, row) ] return cls(*rowdata) @classmethod def create_init(cls): ''' Create an __init__ method from _fields ''' args = ','.join(cls._fields) code = f'def __init__(self, {args}):\n' for name in cls._fields: code += f' self.{name} = {name}\n' locs = { } exec(code, locs) cls.__init__ = locs['__init__'] @classmethod def __init_subclass__(cls): # Apply the validated decorator to subclasses validate_attributes(cls) def validate_attributes(cls): ''' Class decorator that scans a class definition for Validators and builds a _fields variable that captures their definition order. ''' validators = [] for name, val in vars(cls).items(): if isinstance(val, Validator): validators.append(val) # Apply validated decorator to any callable with annotations elif callable(val) and val.__annotations__: setattr(cls, name, validated(val)) # Collect all of the field names cls._fields = tuple([v.name for v in validators]) # Collect type conversions. The lambda x:x is an identity # function that's used in case no expected_type is found. cls._types = tuple([ getattr(v, 'expected_type', lambda x: x) for v in validators ]) # Create the __init__ method if cls._fields: cls.create_init() return cls def typed_structure(clsname, **validators): cls = type(clsname, (Structure,), validators) return cls ``` [Back](ex7_6.md) ================================================ FILE: Exercises/soln8_1.md ================================================ # Exercise 8.1 - Solution ## (b) Adding Iteration to Objects ```python # structure.py ... class Structure(metaclass=StructureMeta): ... def __iter__(self): for name in self._fields: yield getattr(self, name) ... ``` ## (c) The Surprising Power of Iteration ```python # structure.py ... class Structure(metaclass=StructureMeta): ... def __eq__(self, other): return isinstance(other, type(self)) and tuple(self) == tuple(other) ... ``` ## (d) Monitoring a streaming data source ```python # follow.py import os import time def follow(filename): ''' Generator that produces a sequence of lines being written at the end of a file. ''' with open(filename,'r') as f: f.seek(0,os.SEEK_END) while True: line = f.readline() if line == '': time.sleep(0.1) # Sleep briefly to avoid busy wait continue yield line # Example use if __name__ == '__main__': for line in follow('Data/stocklog.csv'): row = line.split(',') name = row[0].strip('"') price = float(row[1]) change = float(row[4]) if change < 0: print('%10s %10.2f %10.2f' % (name, price, change)) ``` **Discussion** The `time.sleep()` function is being used here to avoid busy-waiting on the CPU (e.g., sitting in an infinite loop with 100% CPU use while waiting for new lines to arrive) [Back](ex8_1.md) ================================================ FILE: Exercises/soln8_2.md ================================================ # Exercise 8.2 - Solution ```python # ticker.py from structure import Structure class Ticker(Structure): name = String() price = Float() date = String() time = String() change = Float() open = Float() high = Float() low = Float() volume = Integer() if __name__ == '__main__': from follow import follow import csv from tableformat import create_formatter, print_table formatter = create_formatter('text') lines = follow('Data/stocklog.csv') rows = csv.reader(lines) records = (Ticker.from_row(row) for row in rows) negative = (rec for rec in records if rec.change < 0) print_table(negative, ['name','price','change'], formatter) ``` [Back](ex8_2.md) ================================================ FILE: Exercises/soln8_3.md ================================================ # Exercise 8.3 - Solution ```python # coticker.py from structure import Structure from validate import String, Integer, Float class Ticker(Structure): name = String() price = Float() date = String() time = String() change = Float() open = Float() high = Float() low = Float() volume = Integer() from cofollow import consumer, follow from tableformat import create_formatter import csv # Coroutine that splits rows using the CSV module. This is rather # mind-bending due to the fact that the csv module only understands # iteration with the for-loop. To make it work, we wrap it around # a generator that simply produces the last item received. @consumer def to_csv(target): def producer(): while True: yield line reader = csv.reader(producer()) while True: line = yield target.send(next(reader)) @consumer def create_ticker(target): while True: row = yield target.send(Ticker.from_row(row)) @consumer def negchange(target): while True: record = yield if record.change < 0: target.send(record) @consumer def ticker(fmt, fields): formatter = create_formatter(fmt) formatter.headings(fields) while True: rec = yield row = [getattr(rec, name) for name in fields] formatter.row(row) if __name__ == '__main__': follow('Data/stocklog.csv', to_csv( create_ticker( negchange( ticker('text', ['name','price','change']))))) ``` [Back](ex8_3.md) ================================================ FILE: Exercises/soln8_4.md ================================================ # Exercise 8.4 - Solution All of the code is given in the exercise description. [Back](ex8_4.md) ================================================ FILE: Exercises/soln8_5.md ================================================ # Exercise 8.5 - Solution ## (b) Generators as Tasks Serving Network Connections Here's the complete code. ```python # server.py from socket import * from select import select from collections import deque tasks = deque() recv_wait = {} # sock -> task send_wait = {} # sock -> task def run(): while any([tasks, recv_wait, send_wait]): while not tasks: can_recv, can_send, _ = select(recv_wait, send_wait, []) for s in can_recv: tasks.append(recv_wait.pop(s)) for s in can_send: tasks.append(send_wait.pop(s)) task = tasks.popleft() try: reason, resource = task.send(None) if reason == 'recv': recv_wait[resource] = task elif reason == 'send': send_wait[resource] = task else: raise RuntimeError('Unknown reason %r' % reason) except StopIteration: print('Task done') def tcp_server(address, handler): sock = socket(AF_INET, SOCK_STREAM) sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.bind(address) sock.listen(5) while True: yield 'recv', sock client, addr = sock.accept() tasks.append(handler(client, addr)) def echo_handler(client, address): print('Connection from', address) while True: yield 'recv', client data = client.recv(1000) if not data: break yield 'send', client client.send(b'GOT:' + data) print('Connection closed') if __name__ == '__main__': tasks.append(tcp_server(('',25000), echo_handler)) run() ``` [Back](ex8_5.md) ================================================ FILE: Exercises/soln8_6.md ================================================ # Exercise 8.6 - Solution ## (a) Receiving Messages ```python # coticker.py from structure import Structure from validate import String, Integer, Float class Ticker(Structure): name = String() price = Float() date = String() time = String() change = Float() open = Float() high = Float() low = Float() volume = Integer() from cofollow import consumer, follow, receive from tableformat import create_formatter import csv @consumer def to_csv(target): def producer(): while True: yield line reader = csv.reader(producer()) while True: line = yield from receive(str) target.send(next(reader)) @consumer def create_ticker(target): while True: row = yield from receive(list) target.send(Ticker.from_row(row)) @consumer def negchange(target): while True: record = yield from receive(Ticker) if record.change < 0: target.send(record) @consumer def ticker(fmt, fields): formatter = create_formatter('text') formatter.headings(fields) while True: rec = yield from receive(Ticker) row = [getattr(rec, name) for name in fields] formatter.row(row) if __name__ == '__main__': follow('Data/stocklog.csv', to_csv( create_ticker( negchange( ticker('text', ['name','price','change']))))) ``` ## (b) Wrapping a Socket ```python # server.py ... class GenSocket: def __init__(self, sock): self.sock = sock def accept(self): yield 'recv', self.sock client, addr = self.sock.accept() return GenSocket(client), addr def recv(self, maxsize): yield 'recv', self.sock return self.sock.recv(maxsize) def send(self, data): yield 'send', self.sock return self.sock.send(data) def __getattr__(self, name): return getattr(self.sock, name) def tcp_server(address, handler): sock = GenSocket(socket(AF_INET, SOCK_STREAM)) sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.bind(address) sock.listen(5) while True: client, addr = yield from sock.accept() tasks.append(handler(client, addr)) def echo_handler(client, address): print('Connection from', address) while True: data = yield from client.recv(1000) if not data: break yield from client.send(b'GOT:' + data) print('Connection closed') if __name__ == '__main__': tasks.append(tcp_server(('',25000), echo_handler)) run() ``` ## (c) Async/Await ```python # server.py from socket import * from select import select from collections import deque from types import coroutine tasks = deque() recv_wait = {} # sock -> task send_wait = {} # sock -> task def run(): while any([tasks, recv_wait, send_wait]): while not tasks: can_recv, can_send, _ = select(recv_wait, send_wait, []) for s in can_recv: tasks.append(recv_wait.pop(s)) for s in can_send: tasks.append(send_wait.pop(s)) task = tasks.popleft() try: reason, resource = task.send(None) if reason == 'recv': recv_wait[resource] = task elif reason == 'send': send_wait[resource] = task else: raise RuntimeError('Unknown reason %r' % reason) except StopIteration: print('Task done') class GenSocket: def __init__(self, sock): self.sock = sock @coroutine def accept(self): yield 'recv', self.sock client, addr = self.sock.accept() return GenSocket(client), addr @coroutine def recv(self, maxsize): yield 'recv', self.sock return self.sock.recv(maxsize) @coroutine def send(self, data): yield 'send', self.sock return self.sock.send(data) def __getattr__(self, name): return getattr(self.sock, name) async def tcp_server(address, handler): sock = GenSocket(socket(AF_INET, SOCK_STREAM)) sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.bind(address) sock.listen(5) while True: client, addr = await sock.accept() tasks.append(handler(client, addr)) async def echo_handler(client, address): print('Connection from', address) while True: data = await client.recv(1000) if not data: break await client.send(b'GOT:' + data) print('Connection closed') if __name__ == '__main__': tasks.append(tcp_server(('',25000), echo_handler)) run() ``` [Back](ex8_6.md) ================================================ FILE: Exercises/soln9_1.md ================================================ # Exercise 9.1 - Solution None. Just follow the exercise steps. [Back](ex9_1.md) ================================================ FILE: Exercises/soln9_2.md ================================================ # Exercise 9.2 - Solution ## (a) Making a package You don't need to modify much source code. Just make a directory with this structure: ``` structly/ __init__.py validate.py reader.py structure.py tableformat.py ``` The `__init__.py` file can be empty. You need to make one small change to the `structure.py` file to make the import statement work. ```python # structure.py ... from .validate import Validator ``` [Back](ex9_2.md) ================================================ FILE: Exercises/soln9_3.md ================================================ # Exercise 9.3 - Solution ## (a) Controlling Exported Symbols ```python # structure.py __all__ = ['Structure'] ... ``` ```python # reader.py __all__ = [ 'read_csv_as_dicts', 'read_csv_as_instances' ] ... ``` ```python # tableformat.py __all__ = ['create_formatter', 'print_table'] ... ``` ## (b) Exporting Everything ```python # structly/__init__.py from .structure import * from .reader import * from .tableformat import * __all__ = [ *structure.__all__, *reader.__all__, *tableformat.__all__ ] ``` ## (c) Module Splitting There are a few parts to this. First, the individual files in `tableformat/formats` are going to look like this: ```python # tableformat/formats/text.py from ..formatter import TableFormatter class TextTableFormatter(TableFormatter): ... # tableformat/formats/csv.py from ..formatter import TableFormatter class CSVTableFormatter(TableFormatter): ... # tableformat/formats/html.py from ..formatter import TableFormatter class HTMLTableFormatter(TableFormatter): ... ``` The `formatter.py` file will look like this: ```python # tableformat/formatter.py from abc import ABC, abstractmethod def print_table(records, fields, formatter): if not isinstance(formatter, TableFormatter): raise RuntimeError('Expected a TableFormatter') formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) class TableFormatter(ABC): @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass from .formats.text import TextTableFormatter from .formats.csv import CSVTableFormatter from .formats.html import HTMLTableFormatter class ColumnFormatMixin: formats = [] def row(self, rowdata): rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)] super().row(rowdata) class UpperHeadersMixin: def headings(self, headers): super().headings([h.upper() for h in headers]) def create_formatter(name, column_formats=None, upper_headers=False): if name == 'text': formatter_cls = TextTableFormatter elif name == 'csv': formatter_cls = CSVTableFormatter elif name == 'html': formatter_cls = HTMLTableFormatter else: raise RuntimeError('Unknown format %s' % name) if column_formats: class formatter_cls(ColumnFormatMixin, formatter_cls): formats = column_formats if upper_headers: class formatter_cls(UpperHeadersMixin, formatter_cls): pass return formatter_cls() ``` Finally, you need to have a `tableformat/__init__.py` file like this: ```python # tableformat/__init__.py from .formatter import print_table, create_formatter __all__ = [ 'print_table', 'create_formatter' ] ``` [Back](ex9_3.md) ================================================ FILE: Exercises/soln9_4.md ================================================ # Exercise 9.4 - Solution ```python # tableformat.py from abc import ABC, abstractmethod def print_table(records, fields, formatter): if not isinstance(formatter, TableFormatter): raise RuntimeError('Expected a TableFormatter') formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) class TableFormatter(ABC): _formats = { } @classmethod def __init_subclass__(cls): name = cls.__module__.split('.')[-1] TableFormatter._formats[name] = cls @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass class ColumnFormatMixin: formats = [] def row(self, rowdata): rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)] super().row(rowdata) class UpperHeadersMixin: def headings(self, headers): super().headings([h.upper() for h in headers]) def create_formatter(name, column_formats=None, upper_headers=False): if name not in TableFormatter._formats: __import__(f'{__package__}.formats.{name}') formatter_cls = TableFormatter._formats.get(name) if not formatter_cls: raise RuntimeError('Unknown format %s' % name) if column_formats: class formatter_cls(ColumnFormatMixin, formatter_cls): formats = column_formats if upper_headers: class formatter_cls(UpperHeadersMixin, formatter_cls): pass return formatter_cls() ``` [Back](ex9_4.md) ================================================ 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: README.md ================================================ # Advanced Python Mastery A course by David Beazley (https://www.dabeaz.com) Copyright (C) 2007-2024 ## Synopsis An exercise-driven course on Advanced Python Programming that was battle-tested several hundred times on the corporate-training circuit for more than a decade. Written by David Beazley, author of the [Python Cookbook, 3rd Edition](https://www.dabeaz.com/cookbook.html) (O'Reilly) and [Python Distilled](https://www.dabeaz.com/python-distilled/index.html) (Addison-Wesley). Released under a Creative Commons license. Free of ads, tracking, pop-ups, newsletters, and AI. Everything in this course should work with the latest version of Python, but be aware that the course primarily targets the feature set of Python 3.6. As such, certain modern features don't get coverage. Honestly, this shouldn't affect you much unless you're trying to write code that's freakishly clever. ## Target Audience This course is for Python programmers who want to move beyond short scripts to writing more sophisticated programs. To do that, it helps to better understand the programming techniques used in popular libraries and frameworks. Thus, this course is mainly for programmers who want to build a more complete mental model of the Python language itself and how it works. Ultimately, the goal is to be able to apply this knowledge to your own projects. ## Prerequisites You already know some Python. This is not a course for beginners. For more introductory material, you might consider the [Practical Python Programming](https://dabeaz-course.github.io/practical-python) course. ## How to Take the Course To take the course, you should first fork/clone the GitHub repo to your own machine. It is assumed that you are working locally in a proper Python development environment. That means a proper installation of Python, an editor/IDE, and whatever other tools that you would normally install to work on Python. Due to the use of multiple files and module imports, the use of Notebooks is not recommended. The [`PythonMastery.pdf`](PythonMastery.pdf) file contains detailed presentation slides. Course exercises and suggested timings are clearly indicated. You'll want to keep this by your side (I recommend downloading and viewing it with a local PDF viewer). Start here! The [Exercises/](Exercises/index.md) directory has all of the course exercises. The [Solutions/](Solutions/) directory has fully worked out solution code. The [Data/](Data/) directory has some datafiles used during the course. The course was originally taught over 4-5 days in an in-person classroom setting with a mix of lecture and hands-on exercises. Successful completion of the course will likely require 30-50 hours of work. Exercises tend to build upon each other. Solutions are always provided in case you get stuck. ## Supplemental Material The Advanced Python Mastery course often suggested more in-depth tutorials on selected topics. These were presented at the PyCon conference and might be of interest: * [Generator Tricks for Systems Programmers](https://www.dabeaz.com/generators/) * [A Curious Course on Coroutines and Concurrency](http://dabeaz.com/coroutines/index.html) * [Python3 Metaprogramming](https://dabeaz.com/py3meta/index.html) * [Generators: The Final Frontier](https://dabeaz.com/finalgenerator/index.html) * [Modules and Packages: Live and Let Die](https://dabeaz.com/modulepackage/index.html) ## Questions and Answers **Q: Are any videos available?** **A:** No. You will be able to more quickly read the presentation slides which contain technical information. However, the [Python Programming Language: LiveLessons](https://www.safaribooksonline.com/library/view/python-programming-language/9780134217314/) video available on O'Reilly's Safari site is closely related to the material in this course. **Q: Can I use these materials in my own course?** **A:** Yes. I just kindly ask that you give proper attribution. **Q: Do you accept bug reports or pull requests?** **A:** If you've found a bug, please report it! However, I'm not looking to expand or reorganize the course content with new topics or exercises. **Q: Are the presentation slides available in any format other than PDF?** **A:** No. **Q: Is there any forum/chat where the course can be discussed?** **A:** You can use [GitHub discussions](https://github.com/dabeaz-course/python-mastery/discussions) to discuss the course. **Q: Why wasn't topic/tool/library X covered?** **A:** The course was designed to be completed in an intense 4-day in-person format. It simply isn't possible to cover absolutely everything. As such, the course is focused primarily on the core Python language, not third party libraries or tooling. **Q: Why aren't features like typing, async, or pattern matching covered?** **A:** Mainly, it's an issue of calendar timing and scope. Course material was primarily developed pre-pandemic and represents Python as it was at that time. Some topics (e.g., typing or async) are sufficiently complex that they would be better covered on their own in a separate course. **Q: Do you have plans to modernize the course?** **A:** It is my intention that everything in the course apply to the latest version of Python. Unless Python makes backwards-incompatible changes to the core language, that should hold. Although the course doesn't cover every new features, I won't rule out future changes. A lot depends on my available time and interest however. So, I make no promises. **Q: Why did you release the course?** **A:** This course was extensively taught pre-pandemic. Post-pandemic, my teaching has shifted towards projects and CS fundamentals. However, why let a good course just languish on my computer? **Q: How can I help?** **A:** If you like the course, the best way to support it is to tell other people about it. ---- `>>>` Advanced Python Mastery `...` A course by [dabeaz](https://www.dabeaz.com) `...` Copyright 2007-2023 ![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/) ================================================ FILE: Solutions/1_1/art.py ================================================ # art.py import sys import random chars = '\|/' def draw(rows, columns): for r in range(rows): print(''.join(random.choice(chars) for _ in range(columns))) if __name__ == '__main__': if len(sys.argv) != 3: raise SystemExit("Usage: art.py rows columns") draw(int(sys.argv[1]), int(sys.argv[2])) ================================================ FILE: Solutions/1_3/pcost.py ================================================ # pcost.py total_cost = 0.0 with open('../../Data/portfolio.dat', 'r') as f: for line in f: fields = line.split() nshares = int(fields[1]) price = float(fields[2]) total_cost = total_cost + nshares * price print(total_cost) ================================================ FILE: Solutions/1_4/pcost.py ================================================ # pcost.py def portfolio_cost(filename): total_cost = 0.0 with open(filename) as f: for line in f: fields = line.split() try: nshares = int(fields[1]) price = float(fields[2]) total_cost = total_cost + nshares*price # This catches errors in int() and float() conversions above except ValueError as e: print("Couldn't parse:", repr(line)) print("Reason:", e) return total_cost print(portfolio_cost('../../Data/portfolio3.dat')) ================================================ FILE: Solutions/1_5/stock.py ================================================ # stock.py class Stock: def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def cost(self): return self.shares * self.price ================================================ FILE: Solutions/1_6/pcost.py ================================================ # pcost.py def portfolio_cost(filename): total_cost = 0.0 with open(filename) as f: for line in f: fields = line.split() try: nshares = int(fields[1]) price = float(fields[2]) total_cost = total_cost + nshares * price # This catches errors in int() and float() conversions above except ValueError as e: print("Couldn't parse:", repr(line)) print("Reason:", e) return total_cost if __name__ == '__main__': print(portfolio_cost('../../Data/portfolio.dat')) ================================================ FILE: Solutions/2_1/readrides.py ================================================ # readrides.py import csv def read_rides_as_tuples(filename): ''' Read the bus ride data as a list of tuples ''' records = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = (route, date, daytype, rides) records.append(record) return records def read_rides_as_dicts(filename): ''' Read the bus ride data as a list of dicts ''' records = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = { 'route': route, 'date': date, 'daytype': daytype, 'rides' : rides } records.append(record) return records class Row: # Uncomment to see effect of slots # __slots__ = ('route', 'date', 'daytype', 'rides') def __init__(self, route, date, daytype, rides): self.route = route self.date = date self.daytype = daytype self.rides = rides # Uncomment to use a namedtuple instead #from collections import namedtuple #Row = namedtuple('Row',('route','date','daytype','rides')) def read_rides_as_instances(filename): ''' Read the bus ride data as a list of instances ''' records = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = Row(route, date, daytype, rides) records.append(record) return records if __name__ == '__main__': import tracemalloc tracemalloc.start() read_rides = read_rides_as_tuples # Change to as_dicts, as_instances, etc. rides = read_rides("../../Data/ctabus.csv") print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory()) ================================================ FILE: Solutions/2_2/cta.py ================================================ # cta.py from collections import defaultdict, Counter import tracemalloc import readrides tracemalloc.start() rows = readrides.read_rides_as_dicts('../../Data/ctabus.csv') # -------------------------------------------------- # Question 1: How many bus routes are in Chicago? # Solution: Use a set to get unique values. routes = set() for row in rows: routes.add(row['route']) print(len(routes), 'routes') # -------------------------------------------------- # Question 2: How many people rode route 22 on February 2, 2011? # Solution: Make dictionary with composite keys by_route_date = { } for row in rows: by_route_date[row['route'], row['date']] = row['rides'] print('Rides on Route 22, February 2, 2011:', by_route_date['22','02/02/2011']) # -------------------------------------------------- # Question 3: Total number of rides per route # Solution: Use a counter to tabulate things rides_per_route = Counter() for row in rows: rides_per_route[row['route']] += row['rides'] # Make a table showing the routes and a count ranked by popularity for route, count in rides_per_route.most_common(): print('%5s %10d' % (route, count)) # -------------------------------------------------- # Question 4: Routes with greatest increase in ridership 2001 - 2011 # Solution: Counters embedded inside a defaultdict rides_by_year = defaultdict(Counter) for row in rows: year = row['date'].split('/')[2] rides_by_year[year][row['route']] += row['rides'] diffs = rides_by_year['2011'] - rides_by_year['2001'] for route, diff in diffs.most_common(5): print(route, diff) # ---- Memory use print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory()) ================================================ FILE: Solutions/2_2/readport.py ================================================ # readport.py import csv # A function that reads a file into a list of dicts def read_portfolio(filename): portfolio = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = { 'name' : row[0], 'shares' : int(row[1]), 'price' : float(row[2]) } portfolio.append(record) return portfolio ================================================ FILE: Solutions/2_2/readrides.py ================================================ # readrides.py import csv def read_rides_as_tuples(filename): ''' Read the bus ride data as a list of tuples ''' records = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = (route, date, daytype, rides) records.append(record) return records def read_rides_as_dicts(filename): ''' Read the bus ride data as a list of dicts ''' records = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = { 'route': route, 'date': date, 'daytype': daytype, 'rides' : rides } records.append(record) return records class Row: # Uncomment to see effect of slots # __slots__ = ('route', 'date', 'daytype', 'rides') def __init__(self, route, date, daytype, rides): self.route = route self.date = date self.daytype = daytype self.rides = rides # Uncomment to use a namedtuple instead #from collections import namedtuple #Row = namedtuple('Row',('route','date','daytype','rides')) def read_rides_as_instances(filename): ''' Read the bus ride data as a list of instances ''' records = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = Row(route, date, daytype, rides) records.append(record) return records if __name__ == '__main__': import tracemalloc tracemalloc.start() read_rides = read_rides_as_tuples # Change to as_dicts, as_instances, etc. rides = read_rides("../../Data/ctabus.csv") print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory()) ================================================ FILE: Solutions/2_4/mutint.py ================================================ # mutint.py # # Mutable integers from functools import total_ordering @total_ordering class MutInt: __slots__ = ['value'] def __init__(self, value): self.value = value def __str__(self): return str(self.value) def __repr__(self): return f'MutInt({self.value!r})' def __format__(self, fmt): return format(self.value, fmt) # Implement the "+" operator. Forward operands (MutInt + other) def __add__(self, other): if isinstance(other, MutInt): return MutInt(self.value + other.value) elif isinstance(other, int): return MutInt(self.value + other) else: return NotImplemented # Support for reversed operands (other + MutInt) __radd__ = __add__ # Support for in-place update (MutInt += other) def __iadd__(self, other): if isinstance(other, MutInt): self.value += other.value return self elif isinstance(other, int): self.value += other return self else: return NotImplemented # Support for equality testing def __eq__(self, other): if isinstance(other, MutInt): return self.value == other.value elif isinstance(other, int): return self.value == other else: return NotImplemented # One relation is needed for @total_ordering decorator. It fills in others def __lt__(self, other): if isinstance(other, MutInt): return self.value < other.value elif isinstance(other, int): return self.value < other else: return NotImplemented # Conversions to int() and float() def __int__(self): return int(self.value) def __float__(self): return float(self.value) # Support for indexing s[MutInt] __index__ = __int__ ================================================ FILE: Solutions/2_5/cta.py ================================================ # cta.py from collections import defaultdict, Counter import tracemalloc import readrides tracemalloc.start() rows = readrides.read_rides_as_dicts('../../Data/ctabus.csv') # -------------------------------------------------- # Question 1: How many bus routes are in Chicago? # Solution: Use a set to get unique values. routes = set() for row in rows: routes.add(row['route']) print(len(routes), 'routes') # -------------------------------------------------- # Question 2: How many people rode route 22 on February 2, 2011? # Solution: Make dictionary with composite keys by_route_date = { } for row in rows: by_route_date[row['route'], row['date']] = row['rides'] print('Rides on Route 22, February 2, 2011:', by_route_date['22','02/02/2011']) # -------------------------------------------------- # Question 3: Total number of rides per route # Solution: Use a counter to tabulate things rides_per_route = Counter() for row in rows: rides_per_route[row['route']] += row['rides'] # Make a table showing the routes and a count ranked by popularity for route, count in rides_per_route.most_common(): print('%5s %10d' % (route, count)) # -------------------------------------------------- # Question 4: Routes with greatest increase in ridership 2001 - 2011 # Solution: Counters embedded inside a defaultdict rides_by_year = defaultdict(Counter) for row in rows: year = row['date'].split('/')[2] rides_by_year[year][row['route']] += row['rides'] diffs = rides_by_year['2011'] - rides_by_year['2001'] for route, diff in diffs.most_common(5): print(route, diff) # ---- Memory use print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory()) ================================================ FILE: Solutions/2_5/readrides.py ================================================ # readrides.py import csv import collections.abc def read_rides_as_tuples(filename): ''' Read the bus ride data as a list of tuples ''' records = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = (route, date, daytype, rides) records.append(record) return records def read_rides_as_dicts(filename): ''' Read the bus ride data as a list of dicts ''' records = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = { 'route': route, 'date': date, 'daytype': daytype, 'rides' : rides } records.append(record) return records class Row: __slots__ = ('route', 'date', 'daytype', 'rides') def __init__(self, route, date, daytype, rides): self.route = route self.date = date self.daytype = daytype self.rides = rides def read_rides_as_instances(filename): ''' Read the bus ride data as a list of instances ''' records = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = Row(route, date, daytype, rides) records.append(record) return records # Read as columns def read_rides_as_columns(filename): ''' Read the bus ride data into 4 lists, representing columns ''' routes = [] dates = [] daytypes = [] numrides = [] with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: routes.append(row[0]) dates.append(row[1]) daytypes.append(row[2]) numrides.append(int(row[3])) return dict(routes=routes, dates=dates, daytypes=daytypes, numrides=numrides) # The great "fake" import collections class RideData(collections.abc.Sequence): def __init__(self): # Each value is a list with all of the values (a column) self.routes = [] self.dates = [] self.daytypes = [] self.numrides = [] def __len__(self): # All lists assumed to have the same length return len(self.routes) def append(self, d): self.routes.append(d['route']) self.dates.append(d['date']) self.daytypes.append(d['daytype']) self.numrides.append(d['rides']) def __getitem__(self, index): return { 'route': self.routes[index], 'date': self.dates[index], 'daytype': self.daytypes[index], 'rides': self.numrides[index] } # Modified version using RideData def read_rides_as_dicts(filename): ''' Read the bus ride data as a list of dicts ''' records = RideData() with open(filename) as f: rows = csv.reader(f) headings = next(rows) # Skip headers for row in rows: route = row[0] date = row[1] daytype = row[2] rides = int(row[3]) record = { 'route': route, 'date': date, 'daytype': daytype, 'rides' : rides } records.append(record) return records if __name__ == '__main__': import tracemalloc tracemalloc.start() read_rides = read_rides_as_dicts # Change to as_dicts, as_instances, etc. rides = read_rides("../../Data/ctabus.csv") print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory()) ================================================ FILE: Solutions/2_6/colreader.py ================================================ # colreader.py import collections import collections.abc import csv class DataCollection(collections.abc.Sequence): def __init__(self, columns): self.column_names = list(columns) self.column_data = list(columns.values()) def __len__(self): return len(self.column_data[0]) def __getitem__(self, index): return dict(zip(self.column_names, (col[index] for col in self.column_data))) def read_csv_as_columns(filename, types): columns = collections.defaultdict(list) with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: for name, func, val in zip(headers, types, row): columns[name].append(func(val)) return DataCollection(columns) if __name__ == '__main__': import tracemalloc from sys import intern tracemalloc.start() data = read_csv_as_columns('../../Data/ctabus.csv', [intern, intern, intern, int]) print(tracemalloc.get_traced_memory()) ================================================ FILE: Solutions/2_6/cta.py ================================================ # cta.py from collections import defaultdict, Counter import tracemalloc import csv import sys tracemalloc.start() if True: # Part (b) import reader rows = reader.read_csv_as_dicts('../../Data/ctabus.csv', [sys.intern, sys.intern, sys.intern, int]) else: # Part (d) - Challenge import colreader rows = colreader.read_csv_as_columns('../../Data/ctabus.csv', [sys.intern, sys.intern, sys.intern, int]) # -------------------------------------------------- # Question 1: How many bus routes are in Chicago? # Solution: Use a set to get unique values. routes = set() for row in rows: routes.add(row['route']) print(len(routes), 'routes') # -------------------------------------------------- # Question 2: Total number of rides per route # Solution: Use a counter to tabulate things rides_per_route = Counter() for row in rows: rides_per_route[row['route']] += row['rides'] # Make a table showing the routes and a count ranked by popularity for route, count in rides_per_route.most_common(): print('%5s %10d' % (route, count)) # -------------------------------------------------- # Question 3: Routes with greatest increase in ridership 2001 - 2011 # Solution: Counters embedded inside a defaultdict rides_by_year = defaultdict(Counter) for row in rows: year = row['date'].split('/')[2] rides_by_year[year][row['route']] += row['rides'] diffs = rides_by_year['2011'] - rides_by_year['2001'] for route, diff in diffs.most_common(5): print(route, diff) # ---- Memory use print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory()) ================================================ FILE: Solutions/2_6/reader.py ================================================ # reader.py import csv def read_csv_as_dicts(filename, types): ''' Read a CSV file into a list of dicts with column type conversion ''' records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = { name: func(val) for name, func, val in zip(headers, types, row) } records.append(record) return records ================================================ FILE: Solutions/3_1/stock.py ================================================ # stock.py class Stock: def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares def read_portfolio(filename): ''' Read a CSV file of stock data into a list of Stocks ''' import csv portfolio = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = Stock(row[0], int(row[1]), float(row[2])) portfolio.append(record) return portfolio def print_portfolio(portfolio): ''' Make a nicely formatted table showing stock data ''' print('%10s %10s %10s' % ('name', 'shares', 'price')) print(('-'*10 + ' ')*3) for s in portfolio: print('%10s %10d %10.2f' % (s.name, s.shares, s.price)) if __name__ == '__main__': portfolio = read_portfolio('../../Data/portfolio.csv') print_portfolio(portfolio) ================================================ FILE: Solutions/3_2/stock.py ================================================ # stock.py class Stock: def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares def read_portfolio(filename): ''' Read a CSV file of stock data into a list of Stocks ''' import csv portfolio = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = Stock(row[0], int(row[1]), float(row[2])) portfolio.append(record) return portfolio if __name__ == '__main__': import tableformat portfolio = read_portfolio('../../Data/portfolio.csv') tableformat.print_table(portfolio, ['name','shares','price']) ================================================ FILE: Solutions/3_2/tableformat.py ================================================ # tableformat.py # Print a table def print_table(records, fields): print(' '.join('%10s' % fieldname for fieldname in fields)) print(('-'*10 + ' ')*len(fields)) for record in records: print(' '.join('%10s' % getattr(record, fieldname) for fieldname in fields)) ================================================ FILE: Solutions/3_3/reader.py ================================================ # reader.py import csv def read_csv_as_dicts(filename, types): ''' Read a CSV file into a list of dicts with column type conversion ''' records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = { name: func(val) for name, func, val in zip(headers, types, row) } records.append(record) return records def read_csv_as_instances(filename, cls): ''' Read a CSV file into a list of instances ''' records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: records.append(cls.from_row(row)) return records ================================================ FILE: Solutions/3_3/stock.py ================================================ # stock.py class Stock: types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls.types, row)] return cls(*values) def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares def read_portfolio(filename): ''' Read a CSV file of stock data into a list of Stocks ''' import csv portfolio = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = Stock.from_row(row) portfolio.append(record) return portfolio if __name__ == '__main__': import tableformat import reader # portfolio = read_portfolio('../../Data/portfolio.csv') portfolio = reader.read_csv_as_instances('../../Data/portfolio.csv', Stock) tableformat.print_table(portfolio, ['name', 'shares', 'price']) ================================================ FILE: Solutions/3_3/tableformat.py ================================================ # tableformat.py # Print a table def print_table(records, fields): # Print the table headers in a 10-character wide field for fieldname in fields: print('%10s' % fieldname, end=' ') print() # Print the separator bars print(('-'*10 + ' ')*len(fields)) # Output the table contents for r in records: for fieldname in fields: print('%10s' % getattr(r, fieldname), end=' ') print() ================================================ FILE: Solutions/3_4/stock.py ================================================ # stock.py class Stock: __slots__ = ('name','_shares','_price') _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, self._types[1]): raise TypeError(f'Expected {self._types[1].__name__}') if value < 0: raise ValueError('shares must be >= 0') self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, self._types[2]): raise TypeError(f'Expected {self._types[2].__name__}') if value < 0: raise ValueError('price must be >= 0') self._price = value @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ================================================ FILE: Solutions/3_5/reader.py ================================================ # reader.py import csv def read_csv_as_dicts(filename, types): ''' Read a CSV file into a list of dicts with column type conversion ''' records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = { name: func(val) for name, func, val in zip(headers, types, row) } records.append(record) return records def read_csv_as_instances(filename, cls): ''' Read a CSV file into a list of instances ''' records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: records.append(cls.from_row(row)) return records ================================================ FILE: Solutions/3_5/stock.py ================================================ # stock.py class Stock: __slots__ = ('name','_shares','_price') _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, self._types[1]): raise TypeError(f'Expected {self._types[1].__name__}') if value < 0: raise ValueError('shares must be >= 0') self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, self._types[2]): raise TypeError(f'Expected {self._types[2].__name__}') if value < 0: raise ValueError('price must be >= 0') self._price = value @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares # Sample if __name__ == '__main__': import reader from tableformat import create_formatter, print_table portfolio = reader.read_csv_as_instances('../../Data/portfolio.csv', Stock) formatter = create_formatter('text') print_table(portfolio,['name','shares','price'], formatter) ================================================ FILE: Solutions/3_5/tableformat.py ================================================ # tableformat.py def print_table(records, fields, formatter): formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) class TableFormatter: def headings(self, headers): raise NotImplementedError() def row(self, rowdata): raise NotImplementedError() class TextTableFormatter(TableFormatter): def headings(self, headers): print(' '.join('%10s' % h for h in headers)) print(('-'*10 + ' ')*len(headers)) def row(self, rowdata): print(' '.join('%10s' % d for d in rowdata)) class CSVTableFormatter(TableFormatter): def headings(self, headers): print(','.join(headers)) def row(self, rowdata): print(','.join(str(d) for d in rowdata)) class HTMLTableFormatter(TableFormatter): def headings(self, headers): print('', end=' ') for h in headers: print('%s' % h, end=' ') print('') def row(self, rowdata): print('', end=' ') for d in rowdata: print('%s' % d, end=' ') print('') def create_formatter(name): if name == 'text': formatter_cls = TextTableFormatter elif name == 'csv': formatter_cls = CSVTableFormatter elif name == 'html': formatter_cls = HTMLTableFormatter else: raise RuntimeError('Unknown format %s' % name) return formatter_cls() ================================================ FILE: Solutions/3_6/reader.py ================================================ # reader.py import csv def read_csv_as_dicts(filename, types): ''' Read a CSV file into a list of dicts with column type conversion ''' records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = { name: func(val) for name, func, val in zip(headers, types, row) } records.append(record) return records def read_csv_as_instances(filename, cls): ''' Read a CSV file into a list of instances ''' records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: records.append(cls.from_row(row)) return records ================================================ FILE: Solutions/3_6/stock.py ================================================ # stock.py class Stock: __slots__ = ('name','_shares','_price') _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): # Note: The !r format code produces the repr() string return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})' def __eq__(self, other): return isinstance(other, Stock) and ((self.name, self.shares, self.price) == (other.name, other.shares, other.price)) @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, self._types[1]): raise TypeError(f'Expected {self._types[1].__name__}') if value < 0: raise ValueError('shares must be >= 0') self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, self._types[2]): raise TypeError(f'Expected {self._types[2].__name__}') if value < 0: raise ValueError('price must be >= 0') self._price = value @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares # Sample if __name__ == '__main__': import reader from tableformat import create_formatter, print_table portfolio = reader.read_csv_as_instances('../../Data/portfolio.csv', Stock) formatter = create_formatter('text') print_table(portfolio,['name','shares','price'], formatter) ================================================ FILE: Solutions/3_6/tableformat.py ================================================ # tableformat.py def print_table(records, fields, formatter): formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) class TableFormatter: def headings(self, headers): raise NotImplementedError() def row(self, rowdata): raise NotImplementedError() class TextTableFormatter(TableFormatter): def headings(self, headers): print(' '.join('%10s' % h for h in headers)) print(('-'*10 + ' ')*len(headers)) def row(self, rowdata): print(' '.join('%10s' % d for d in rowdata)) class CSVTableFormatter(TableFormatter): def headings(self, headers): print(','.join(headers)) def row(self, rowdata): print(','.join(str(d) for d in rowdata)) class HTMLTableFormatter(TableFormatter): def headings(self, headers): print('', end=' ') for h in headers: print('%s' % h, end=' ') print('') def row(self, rowdata): print('', end=' ') for d in rowdata: print('%s' % d, end=' ') print('') def create_formatter(name): if name == 'text': formatter_cls = TextTableFormatter elif name == 'csv': formatter_cls = CSVTableFormatter elif name == 'html': formatter_cls = HTMLTableFormatter else: raise RuntimeError('Unknown format %s' % name) return formatter_cls() ================================================ FILE: Solutions/3_7/reader.py ================================================ # reader.py import csv from abc import ABC, abstractmethod class CSVParser(ABC): def parse(self, filename): records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = self.make_record(headers, row) records.append(record) return records @abstractmethod def make_record(self, headers, row): pass class DictCSVParser(CSVParser): def __init__(self, types): self.types = types def make_record(self, headers, row): return { name: func(val) for name, func, val in zip(headers, self.types, row) } class InstanceCSVParser(CSVParser): def __init__(self, cls): self.cls = cls def make_record(self, headers, row): return self.cls.from_row(row) def read_csv_as_dicts(filename, types): parser = DictCSVParser(types) return parser.parse(filename) def read_csv_as_instances(filename, cls): parser = InstanceCSVParser(cls) return parser.parse(filename) ================================================ FILE: Solutions/3_7/stock.py ================================================ # stock.py class Stock: __slots__ = ('name','_shares','_price') _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): # Note: The !r format code produces the repr() string return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})' def __eq__(self, other): return isinstance(other, Stock) and ((self.name, self.shares, self.price) == (other.name, other.shares, other.price)) @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, self._types[1]): raise TypeError(f'Expected {self._types[1].__name__}') if value < 0: raise ValueError('shares must be >= 0') self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, self._types[2]): raise TypeError(f'Expected {self._types[2].__name__}') if value < 0: raise ValueError('price must be >= 0') self._price = value @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares # Sample if __name__ == '__main__': import reader from tableformat import create_formatter, print_table portfolio = reader.read_csv_as_instances('../../Data/portfolio.csv', Stock) formatter = create_formatter('text') print_table(portfolio,['name','shares','price'], formatter) ================================================ FILE: Solutions/3_7/tableformat.py ================================================ # tableformat.py from abc import ABC, abstractmethod def print_table(records, fields, formatter): if not isinstance(formatter, TableFormatter): raise TypeError('Expected a TableFormatter') formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) class TableFormatter(ABC): @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass class TextTableFormatter(TableFormatter): def headings(self, headers): print(' '.join('%10s' % h for h in headers)) print(('-'*10 + ' ')*len(headers)) def row(self, rowdata): print(' '.join('%10s' % d for d in rowdata)) class CSVTableFormatter(TableFormatter): def headings(self, headers): print(','.join(headers)) def row(self, rowdata): print(','.join(str(d) for d in rowdata)) class HTMLTableFormatter(TableFormatter): def headings(self, headers): print('', end=' ') for h in headers: print('%s' % h, end=' ') print('') def row(self, rowdata): print('', end=' ') for d in rowdata: print('%s' % d, end=' ') print('') def create_formatter(name): if name == 'text': formatter = TextTableFormatter elif name == 'csv': formatter = CSVTableFormatter elif name == 'html': formatter = HTMLTableFormatter else: raise RuntimeError('Unknown format %s' % name) return formatter() ================================================ FILE: Solutions/3_8/reader.py ================================================ # reader.py import csv from abc import ABC, abstractmethod class CSVParser(ABC): def parse(self, filename): records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for row in rows: record = self.make_record(headers, row) records.append(record) return records @abstractmethod def make_record(self, headers, row): pass class DictCSVParser(CSVParser): def __init__(self, types): self.types = types def make_record(self, headers, row): return { name: func(val) for name, func, val in zip(headers, self.types, row) } class InstanceCSVParser(CSVParser): def __init__(self, cls): self.cls = cls def make_record(self, headers, row): return self.cls.from_row(row) def read_csv_as_dicts(filename, types): parser = DictCSVParser(types) return parser.parse(filename) def read_csv_as_instances(filename, cls): parser = InstanceCSVParser(cls) return parser.parse(filename) ================================================ FILE: Solutions/3_8/stock.py ================================================ # stock.py class Stock: __slots__ = ('name','_shares','_price') _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): # Note: The !r format code produces the repr() string return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})' def __eq__(self, other): return isinstance(other, Stock) and ((self.name, self.shares, self.price) == (other.name, other.shares, other.price)) @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, self._types[1]): raise TypeError(f'Expected {self._types[1].__name__}') if value < 0: raise ValueError('shares must be >= 0') self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, self._types[2]): raise TypeError(f'Expected {self._types[2].__name__}') if value < 0: raise ValueError('price must be >= 0') self._price = value @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares # Sample if __name__ == '__main__': import tableformat import reader from tableformat import ( print_table, create_formatter, TextTableFormatter, ColumnFormatMixin, UpperHeadersMixin ) portfolio = reader.read_csv_as_instances('../../Data/portfolio.csv', Stock) class PortfolioFormatter(ColumnFormatMixin, TextTableFormatter): formats = ['%s','%d','%0.2f'] formatter = PortfolioFormatter() print_table(portfolio,['name','shares','price'], formatter) class PortfolioFormatter(UpperHeadersMixin, TextTableFormatter): pass formatter = PortfolioFormatter() print_table(portfolio, ['name','shares','price'], formatter) # Factory function version formatter = create_formatter('text', column_formats=['%s','%d','%0.2f']) print_table(portfolio, ['name','shares','price'], formatter) formatter = create_formatter('text', upper_headers=True) print_table(portfolio, ['name','shares','price'], formatter) ================================================ FILE: Solutions/3_8/tableformat.py ================================================ # tableformat.py from abc import ABC, abstractmethod def print_table(records, fields, formatter): if not isinstance(formatter, TableFormatter): raise TypeError('Expected a TableFormatter') formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) class TableFormatter(ABC): @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass class TextTableFormatter(TableFormatter): def headings(self, headers): print(' '.join('%10s' % h for h in headers)) print(('-'*10 + ' ')*len(headers)) def row(self, rowdata): print(' '.join('%10s' % d for d in rowdata)) class CSVTableFormatter(TableFormatter): def headings(self, headers): print(','.join(headers)) def row(self, rowdata): print(','.join(str(d) for d in rowdata)) class HTMLTableFormatter(TableFormatter): def headings(self, headers): print('', end=' ') for h in headers: print('%s' % h, end=' ') print('') def row(self, rowdata): print('', end=' ') for d in rowdata: print('%s' % d, end=' ') print('') class ColumnFormatMixin: formats = [] def row(self, rowdata): rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)] super().row(rowdata) class UpperHeadersMixin: def headings(self, headers): super().headings([h.upper() for h in headers]) def create_formatter(name, column_formats=None, upper_headers=False): if name == 'text': formatter_cls = TextTableFormatter elif name == 'csv': formatter_cls = CSVTableFormatter elif name == 'html': formatter_cls = HTMLTableFormatter else: raise RuntimeError('Unknown format %s' % name) if column_formats: class formatter_cls(ColumnFormatMixin, formatter_cls): formats = column_formats if upper_headers: class formatter_cls(UpperHeadersMixin, formatter_cls): pass return formatter_cls() ================================================ FILE: Solutions/4_2/validate.py ================================================ class Validator: @classmethod def check(cls, value): return value class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'Expected {cls.expected_type}') return super().check(value) class Integer(Typed): expected_type = int class Float(Typed): expected_type = float class String(Typed): expected_type = str class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('Must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('Must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass # Examples if __name__ == '__main__': def add(x, y): Integer.check(x) Integer.check(y) return x + y class Stock: __slots__ = ('name','_shares','_price') def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def shares(self): return self._shares @shares.setter def shares(self, value): self._shares = PositiveInteger.check(value) @property def price(self): return self._price @price.setter def price(self, value): self._price = PositiveFloat.check(value) @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ================================================ FILE: Solutions/4_3/validate.py ================================================ class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') return super().check(value) class Integer(Typed): expected_type = int class Float(Typed): expected_type = float class String(Typed): expected_type = str class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass # Examples if __name__ == '__main__': def add(x, y): Integer.check(x) Integer.check(y) return x + y class Stock: name = NonEmptyString() shares = PositiveInteger() price = PositiveFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ================================================ FILE: Solutions/5_2/reader.py ================================================ # reader.py from abc import ABC, abstractmethod import csv import logging log = logging.getLogger(__name__) class CSVParser(ABC): def parse(self, filename): records = [] with open(filename) as f: rows = csv.reader(f) headers = next(rows) for rowno, row in enumerate(rows, start=1): try: record = self.make_record(headers, row) records.append(record) except ValueError as e: log.warning('Row %d: Bad row: %s', rowno, row) log.debug('Row %d: Reason: %s', rowno, e) return records @abstractmethod def make_record(self, headers, row): raise NotImplementedError() class DictCSVParser(CSVParser): def __init__(self, types): self.types = types def make_record(self, headers, row): return { name: func(val) for name, func, val in zip(headers, self.types, row) } class InstanceCSVParser(CSVParser): def __init__(self, cls): self.cls = cls def make_record(self, headers, row): return self.cls.from_row(row) def read_csv_as_dicts(filename, types): parser = DictCSVParser(types) return parser.parse(filename) def read_csv_as_instances(filename, cls): parser = InstanceCSVParser(cls) return parser.parse(filename) ================================================ FILE: Solutions/5_2/stock.py ================================================ # stock.py class Stock: __slots__ = ('name','_shares','_price') _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): # Note: The !r format code produces the repr() string return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})' def __eq__(self, other): return isinstance(other, Stock) and ((self.name, self.shares, self.price) == (other.name, other.shares, other.price)) @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, self._types[1]): raise TypeError(f'Expected {self._types[1].__name__}') if value < 0: raise ValueError('shares must be >= 0') self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, self._types[2]): raise TypeError(f'Expected {self._types[2].__name__}') if value < 0: raise ValueError('price must be >= 0') self._price = value @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ================================================ FILE: Solutions/5_3/reader.py ================================================ # reader.py import csv def convert_csv(lines, converter, *, headers=None): rows = csv.reader(lines) if headers is None: headers = next(rows) return list(map(lambda row: converter(headers, row), rows)) def csv_as_dicts(lines, types, *, headers=None): return convert_csv(lines, lambda headers, row: {name: func(val) for name, func, val in zip(headers, types, row)}, headers=headers) def csv_as_instances(lines, cls, *, headers=None): return convert_csv(lines, lambda headers, row: cls.from_row(row), headers=headers) def read_csv_as_dicts(filename, types, *, headers=None): ''' Read CSV data into a list of dictionaries with optional type conversion ''' with open(filename) as file: return csv_as_dicts(file, types, headers=headers) def read_csv_as_instances(filename, cls, *, headers=None): ''' Read CSV data into a list of instances ''' with open(filename) as file: return csv_as_instances(file, cls, headers=headers) ================================================ FILE: Solutions/5_3/stock.py ================================================ # stock.py class Stock: __slots__ = ('name','_shares','_price') _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): # Note: The !r format code produces the repr() string return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})' def __eq__(self, other): return isinstance(other, Stock) and ((self.name, self.shares, self.price) == (other.name, other.shares, other.price)) @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, self._types[1]): raise TypeError(f'Expected {self._types[1].__name__}') if value < 0: raise ValueError('shares must be >= 0') self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, self._types[2]): raise TypeError(f'Expected {self._types[2].__name__}') if value < 0: raise ValueError('price must be >= 0') self._price = value @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ================================================ FILE: Solutions/5_4/typedproperty.py ================================================ # typedproperty.py def typedproperty(name, expected_type): private_name = '_' + name @property def value(self): return getattr(self, private_name) @value.setter def value(self, val): if not isinstance(val, expected_type): raise TypeError(f'Expected {expected_type}') setattr(self, private_name, val) return value String = lambda name: typedproperty(name, str) Integer = lambda name: typedproperty(name, int) Float = lambda name: typedproperty(name, float) # Example if __name__ == '__main__': class Stock: name = String('name') shares = Integer('shares') price = Float('price') def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price ================================================ FILE: Solutions/5_5/reader.py ================================================ # reader.py import csv import logging log = logging.getLogger(__name__) def convert_csv(lines, converter, *, headers=None): rows = csv.reader(lines) if headers is None: headers = next(rows) records = [] for rowno, row in enumerate(rows, start=1): try: records.append(converter(headers, row)) except ValueError as e: log.warning('Row %s: Bad row: %s', rowno, row) log.debug('Row %s: Reason: %s', rowno, row) return records def csv_as_dicts(lines, types, *, headers=None): return convert_csv(lines, lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) }) def csv_as_instances(lines, cls, *, headers=None): return convert_csv(lines, lambda headers, row: cls.from_row(row)) def read_csv_as_dicts(filename, types, *, headers=None): ''' Read CSV data into a list of dictionaries with optional type conversion ''' with open(filename) as file: return csv_as_dicts(file, types, headers=headers) def read_csv_as_instances(filename, cls, *, headers=None): ''' Read CSV data into a list of instances ''' with open(filename) as file: return csv_as_instances(file, cls, headers=headers) ================================================ FILE: Solutions/5_6/stock.py ================================================ # stock.py class Stock: __slots__ = ('name','_shares','_price') _types = (str, int, float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): # Note: The !r format code produces the repr() string return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})' def __eq__(self, other): return isinstance(other, Stock) and ((self.name, self.shares, self.price) == (other.name, other.shares, other.price)) @classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values) @property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, self._types[1]): raise TypeError(f'Expected {self._types[1].__name__}') if value < 0: raise ValueError('shares must be >= 0') self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, self._types[2]): raise TypeError(f'Expected {self._types[2].__name__}') if value < 0: raise ValueError('price must be >= 0') self._price = value @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ================================================ FILE: Solutions/5_6/teststock.py ================================================ # teststock.py import stock import unittest class TestStock(unittest.TestCase): def test_create(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_create_keyword(self): s = stock.Stock(name='GOOG', shares=100, price=490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_cost(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.cost, 49010.0) def test_sell(self): s = stock.Stock('GOOG', 100, 490.1) s.sell(25) self.assertEqual(s.shares, 75) def test_from_row(self): s = stock.Stock.from_row(['GOOG','100','490.1']) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_repr(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)") def test_eq(self): a = stock.Stock('GOOG', 100, 490.1) b = stock.Stock('GOOG', 100, 490.1) self.assertTrue(a==b) # Tests for failure conditions def test_shares_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.shares = '50' def test_shares_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.shares = -50 def test_price_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.price = '45.23' def test_price_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.price = -45.23 def test_bad_attribute(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(AttributeError): s.share = 100 if __name__ == '__main__': unittest.main() ================================================ FILE: Solutions/6_1/stock.py ================================================ # stock.py from structure import Structure class Stock(Structure): _fields = ('name', 'shares', 'price') @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ================================================ FILE: Solutions/6_1/structure.py ================================================ # structure.py class Structure: _fields = () def __init__(self, *args): if len(args) != len(self._fields): raise TypeError('Expected %d arguments' % len(self._fields)) for name, val in zip(self._fields, args): setattr(self, name, val) def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) ================================================ FILE: Solutions/6_1/teststock.py ================================================ # teststock.py import stock import unittest class TestStock(unittest.TestCase): def test_create(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_create_keyword(self): s = stock.Stock(name='GOOG', shares=100, price=490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_cost(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.cost, 49010.0) def test_sell(self): s = stock.Stock('GOOG', 100, 490.1) s.sell(25) self.assertEqual(s.shares, 75) def test_from_row(self): s = stock.Stock.from_row(['GOOG','100','490.1']) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_repr(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)") def test_eq(self): a = stock.Stock('GOOG', 100, 490.1) b = stock.Stock('GOOG', 100, 490.1) self.assertTrue(a==b) # Tests for failure conditions def test_shares_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.shares = '50' def test_shares_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.shares = -50 def test_price_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.price = '45.23' def test_price_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.price = -45.23 def test_bad_attribute(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(AttributeError): s.share = 100 if __name__ == '__main__': unittest.main() ================================================ FILE: Solutions/6_2/stock.py ================================================ # stock.py from structure import Structure class Stock(Structure): _fields = ('name', 'shares', 'price') def __init__(self, name, shares, price): self._init() @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares ================================================ FILE: Solutions/6_2/structure.py ================================================ # structure.py import sys class Structure: _fields = () @staticmethod def _init(): locs = sys._getframe(1).f_locals self = locs['self'] for name, val in locs.items(): if name == 'self': continue setattr(self, name, val) def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) ================================================ FILE: Solutions/6_2/teststock.py ================================================ # teststock.py import stock import unittest class TestStock(unittest.TestCase): def test_create(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_create_keyword(self): s = stock.Stock(name='GOOG', shares=100, price=490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_cost(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.cost, 49010.0) def test_sell(self): s = stock.Stock('GOOG', 100, 490.1) s.sell(25) self.assertEqual(s.shares, 75) def test_from_row(self): s = stock.Stock.from_row(['GOOG','100','490.1']) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_repr(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)") def test_eq(self): a = stock.Stock('GOOG', 100, 490.1) b = stock.Stock('GOOG', 100, 490.1) self.assertTrue(a==b) # Tests for failure conditions def test_shares_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.shares = '50' def test_shares_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.shares = -50 def test_price_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.price = '45.23' def test_price_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.price = -45.23 def test_bad_attribute(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(AttributeError): s.share = 100 if __name__ == '__main__': unittest.main() ================================================ FILE: Solutions/6_3/stock.py ================================================ # stock.py from structure import Structure class Stock(Structure): def __init__(self, name, shares, price): self._init() @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares Stock.set_fields() ================================================ FILE: Solutions/6_3/structure.py ================================================ # structure.py import sys import inspect class Structure: _fields = () @staticmethod def _init(): locs = sys._getframe(1).f_locals self = locs['self'] for name, val in locs.items(): if name == 'self': continue setattr(self, name, val) def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) @classmethod def set_fields(cls): sig = inspect.signature(cls) cls._fields = tuple(sig.parameters) ================================================ FILE: Solutions/6_3/teststock.py ================================================ # teststock.py import stock import unittest class TestStock(unittest.TestCase): def test_create(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_create_keyword(self): s = stock.Stock(name='GOOG', shares=100, price=490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_cost(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.cost, 49010.0) def test_sell(self): s = stock.Stock('GOOG', 100, 490.1) s.sell(25) self.assertEqual(s.shares, 75) def test_from_row(self): s = stock.Stock.from_row(['GOOG','100','490.1']) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_repr(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)") def test_eq(self): a = stock.Stock('GOOG', 100, 490.1) b = stock.Stock('GOOG', 100, 490.1) self.assertTrue(a==b) # Tests for failure conditions def test_shares_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.shares = '50' def test_shares_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.shares = -50 def test_price_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.price = '45.23' def test_price_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.price = -45.23 def test_bad_attribute(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(AttributeError): s.share = 100 if __name__ == '__main__': unittest.main() ================================================ FILE: Solutions/6_4/stock.py ================================================ # stock.py from structure import Structure class Stock(Structure): _fields = ('name', 'shares', 'price') @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares Stock.create_init() ================================================ FILE: Solutions/6_4/structure.py ================================================ # structure.py class Structure: _fields = () def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) @classmethod def create_init(cls): ''' Create an __init__ method from _fields ''' args = ','.join(cls._fields) code = f'def __init__(self, {args}):\n' for name in cls._fields: code += f' self.{name} = {name}\n' locs = { } exec(code, locs) cls.__init__ = locs['__init__'] ================================================ FILE: Solutions/6_4/teststock.py ================================================ # teststock.py import stock import unittest class TestStock(unittest.TestCase): def test_create(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_create_keyword(self): s = stock.Stock(name='GOOG', shares=100, price=490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_cost(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.cost, 49010.0) def test_sell(self): s = stock.Stock('GOOG', 100, 490.1) s.sell(25) self.assertEqual(s.shares, 75) def test_from_row(self): s = stock.Stock.from_row(['GOOG','100','490.1']) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_repr(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)") def test_eq(self): a = stock.Stock('GOOG', 100, 490.1) b = stock.Stock('GOOG', 100, 490.1) self.assertTrue(a==b) # Tests for failure conditions def test_shares_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.shares = '50' def test_shares_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.shares = -50 def test_price_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.price = '45.23' def test_price_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.price = -45.23 def test_bad_attribute(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(AttributeError): s.share = 100 if __name__ == '__main__': unittest.main() ================================================ FILE: Solutions/6_5/validate.py ================================================ class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') return super().check(value) class Integer(Typed): expected_type = int class Float(Typed): expected_type = float class String(Typed): expected_type = str class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass from inspect import signature class ValidatedFunction: def __init__(self, func): self.func = func self.signature = signature(func) self.annotations = dict(func.__annotations__) self.retcheck = self.annotations.pop('return', None) def __call__(self, *args, **kwargs): bound = self.signature.bind(*args, **kwargs) for name, val in self.annotations.items(): val.check(bound.arguments[name]) result = self.func(*args, **kwargs) if self.retcheck: self.retcheck.check(result) return result # Examples if __name__ == '__main__': def add(x:Integer, y:Integer) -> Integer: return x + y add = ValidatedFunction(add) class Stock: name = NonEmptyString() shares = PositiveInteger() price = PositiveFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares sell = ValidatedFunction(sell) # Broken ================================================ FILE: Solutions/7_1/logcall.py ================================================ # logcall.py def logged(func): print('Adding logging to', func.__name__) def wrapper(*args,**kwargs): print('Calling', func.__name__) return func(*args,**kwargs) return wrapper ================================================ FILE: Solutions/7_1/sample.py ================================================ # sample.py from logcall import logged @logged def add(x,y): return x+y @logged def sub(x,y): return x-y ================================================ FILE: Solutions/7_1/validate.py ================================================ # validate.py class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') return super().check(value) class Integer(Typed): expected_type = int class Float(Typed): expected_type = float class String(Typed): expected_type = str class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass from inspect import signature def isvalidator(item): return isinstance(item, type) and issubclass(item, Validator) def validated(func): sig = signature(func) # Gather the function annotations annotations = { name:val for name, val in func.__annotations__.items() if isvalidator(val) } # Get the return annotation (if any) retcheck = annotations.pop('return', None) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) # Enforce return check (if any) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper # Examples if __name__ == '__main__': @validated def add(x:Integer, y:Integer) -> Integer: return x + y @validated def div(x:Integer, y:Integer) -> Integer: return x / y class Stock: name = NonEmptyString() shares = PositiveInteger() price = PositiveFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def cost(self): return self.shares * self.price @validated def sell(self, nshares:PositiveInteger): self.shares -= nshares ================================================ FILE: Solutions/7_2/logcall.py ================================================ # logcall.py from functools import wraps def logformat(fmt): def logged(func): print('Adding logging to', func.__name__) @wraps(func) def wrapper(*args,**kwargs): print(fmt.format(func=func)) return func(*args, **kwargs) return wrapper return logged # Original no-argument @logged decorator defined in terms of the more # general @logformat decorator logged = logformat('Calling {func.__name__}') ================================================ FILE: Solutions/7_2/sample.py ================================================ # sample.py from logcall import logged, logformat @logged def add(x,y): return x+y @logged def sub(x,y): return x-y @logformat('{func.__code__.co_filename}:{func.__name__}') def mul(x,y): return x*y ================================================ FILE: Solutions/7_2/spam.py ================================================ from logcall import logged class Spam: @logged def instance_method(self): pass @classmethod @logged def class_method(cls): pass @staticmethod @logged def static_method(): pass @property @logged def property_method(self): pass ================================================ FILE: Solutions/7_2/validate.py ================================================ # validate.py class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') return super().check(value) class Integer(Typed): expected_type = int class Float(Typed): expected_type = float class String(Typed): expected_type = str class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass from inspect import signature from functools import wraps def isvalidator(item): return isinstance(item, type) and issubclass(item, Validator) def validated(func): sig = signature(func) # Gather the function annotations annotations = { name:val for name, val in func.__annotations__.items() if isvalidator(val) } # Get the return annotation (if any) retcheck = annotations.pop('return', None) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) # Enforce return check (if any) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper def enforce(**annotations): retcheck = annotations.pop('return_', None) def decorate(func): sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper return decorate # Examples if __name__ == '__main__': @validated def add(x:Integer, y:Integer) -> Integer: return x + y @validated def div(x:Integer, y:Integer) -> Integer: return x / y @enforce(x=Integer, y=Integer) def sub(x, y): return x - y class Stock: name = NonEmptyString() shares = PositiveInteger() price = PositiveFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def cost(self): return self.shares * self.price @validated def sell(self, nshares:PositiveInteger): self.shares -= nshares ================================================ FILE: Solutions/7_3/reader.py ================================================ # reader.py import csv import logging log = logging.getLogger(__name__) def convert_csv(lines, converter, *, headers=None): rows = csv.reader(lines) if headers is None: headers = next(rows) records = [] for rowno, row in enumerate(rows, start=1): try: records.append(converter(headers, row)) except ValueError as e: log.warning('Row %s: Bad row: %s', rowno, row) log.debug('Row %s: Reason: %s', rowno, row) return records def csv_as_dicts(lines, types, *, headers=None): return convert_csv(lines, lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) }) def csv_as_instances(lines, cls, *, headers=None): return convert_csv(lines, lambda headers, row: cls.from_row(row)) def read_csv_as_dicts(filename, types, *, headers=None): ''' Read CSV data into a list of dictionaries with optional type conversion ''' with open(filename) as file: return csv_as_dicts(file, types, headers=headers) def read_csv_as_instances(filename, cls, *, headers=None): ''' Read CSV data into a list of instances ''' with open(filename) as file: return csv_as_instances(file, cls, headers=headers) ================================================ FILE: Solutions/7_3/stock.py ================================================ # stock.py from structure import Structure from validate import String, PositiveInteger, PositiveFloat class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares: PositiveInteger): self.shares -= nshares ================================================ FILE: Solutions/7_3/structure.py ================================================ # structure.py from validate import Validator, validated class Structure: _fields = () _types = () def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) @classmethod def from_row(cls, row): rowdata = [ func(val) for func, val in zip(cls._types, row) ] return cls(*rowdata) @classmethod def create_init(cls): ''' Create an __init__ method from _fields ''' args = ','.join(cls._fields) code = f'def __init__(self, {args}):\n' for name in cls._fields: code += f' self.{name} = {name}\n' locs = { } exec(code, locs) cls.__init__ = locs['__init__'] @classmethod def __init_subclass__(cls): # Apply the validated decorator to subclasses validate_attributes(cls) def validate_attributes(cls): ''' Class decorator that scans a class definition for Validators and builds a _fields variable that captures their definition order. ''' validators = [] for name, val in vars(cls).items(): if isinstance(val, Validator): validators.append(val) # Apply validated decorator to any callable with annotations elif callable(val) and val.__annotations__: setattr(cls, name, validated(val)) # Collect all of the field names cls._fields = tuple([v.name for v in validators]) # Collect type conversions. The lambda x:x is an identity # function that's used in case no expected_type is found. cls._types = tuple([ getattr(v, 'expected_type', lambda x: x) for v in validators ]) # Create the __init__ method if cls._fields: cls.create_init() return cls ================================================ FILE: Solutions/7_3/teststock.py ================================================ # teststock.py import stock import unittest class TestStock(unittest.TestCase): def test_create(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_create_keyword(self): s = stock.Stock(name='GOOG', shares=100, price=490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_cost(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.cost, 49010.0) def test_sell(self): s = stock.Stock('GOOG', 100, 490.1) s.sell(25) self.assertEqual(s.shares, 75) def test_from_row(self): s = stock.Stock.from_row(['GOOG','100','490.1']) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_repr(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)") def test_eq(self): a = stock.Stock('GOOG', 100, 490.1) b = stock.Stock('GOOG', 100, 490.1) self.assertTrue(a==b) # Tests for failure conditions def test_shares_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.shares = '50' def test_shares_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.shares = -50 def test_price_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.price = '45.23' def test_price_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.price = -45.23 def test_bad_attribute(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(AttributeError): s.share = 100 if __name__ == '__main__': unittest.main() ================================================ FILE: Solutions/7_3/validate.py ================================================ # validate.py class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') return super().check(value) class Integer(Typed): expected_type = int class Float(Typed): expected_type = float class String(Typed): expected_type = str class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass from inspect import signature from functools import wraps def isvalidator(item): return isinstance(item, type) and issubclass(item, Validator) def validated(func): sig = signature(func) # Gather the function annotations annotations = { name:val for name, val in func.__annotations__.items() if isvalidator(val) } # Get the return annotation (if any) retcheck = annotations.pop('return', None) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) # Enforce return check (if any) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper def enforce(**annotations): retcheck = annotations.pop('return_', None) def decorate(func): sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper return decorate # Examples if __name__ == '__main__': @validated def add(x:Integer, y:Integer) -> Integer: return x + y @validated def div(x:Integer, y:Integer) -> Integer: return x / y @enforce(x=Integer, y=Integer) def sub(x, y): return x - y class Stock: name = NonEmptyString() shares = PositiveInteger() price = PositiveFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def cost(self): return self.shares * self.price @validated def sell(self, nshares:PositiveInteger): self.shares -= nshares ================================================ FILE: Solutions/7_4/stock.py ================================================ # stock.py from structure import Structure from validate import String, PositiveInteger, PositiveFloat class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares: PositiveInteger): self.shares -= nshares ================================================ FILE: Solutions/7_4/structure.py ================================================ # structure.py from validate import Validator, validated class Structure: _fields = () _types = () def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) @classmethod def from_row(cls, row): rowdata = [ func(val) for func, val in zip(cls._types, row) ] return cls(*rowdata) @classmethod def create_init(cls): ''' Create an __init__ method from _fields ''' args = ','.join(cls._fields) code = f'def __init__(self, {args}):\n' for name in cls._fields: code += f' self.{name} = {name}\n' locs = { } exec(code, locs) cls.__init__ = locs['__init__'] @classmethod def __init_subclass__(cls): # Apply the validated decorator to subclasses validate_attributes(cls) def validate_attributes(cls): ''' Class decorator that scans a class definition for Validators and builds a _fields variable that captures their definition order. ''' validators = [] for name, val in vars(cls).items(): if isinstance(val, Validator): validators.append(val) # Apply validated decorator to any callable with annotations elif callable(val) and val.__annotations__: setattr(cls, name, validated(val)) # Collect all of the field names cls._fields = tuple([v.name for v in validators]) # Collect type conversions. The lambda x:x is an identity # function that's used in case no expected_type is found. cls._types = tuple([ getattr(v, 'expected_type', lambda x: x) for v in validators ]) # Create the __init__ method if cls._fields: cls.create_init() return cls def typed_structure(clsname, **validators): cls = type(clsname, (Structure,), validators) return cls ================================================ FILE: Solutions/7_4/teststock.py ================================================ # teststock.py import stock import unittest class TestStock(unittest.TestCase): def test_create(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_create_keyword(self): s = stock.Stock(name='GOOG', shares=100, price=490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_cost(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.cost, 49010.0) def test_sell(self): s = stock.Stock('GOOG', 100, 490.1) s.sell(25) self.assertEqual(s.shares, 75) def test_from_row(self): s = stock.Stock.from_row(['GOOG','100','490.1']) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_repr(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)") def test_eq(self): a = stock.Stock('GOOG', 100, 490.1) b = stock.Stock('GOOG', 100, 490.1) self.assertTrue(a==b) # Tests for failure conditions def test_shares_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.shares = '50' def test_shares_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.shares = -50 def test_price_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.price = '45.23' def test_price_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.price = -45.23 def test_bad_attribute(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(AttributeError): s.share = 100 if __name__ == '__main__': unittest.main() ================================================ FILE: Solutions/7_4/validate.py ================================================ # validate.py class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') return super().check(value) _typed_classes = [ ('Integer', int), ('Float', float), ('String', str) ] globals().update((name, type(name, (Typed,), {'expected_type':ty})) for name, ty in _typed_classes) class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass from inspect import signature from functools import wraps def isvalidator(item): return isinstance(item, type) and issubclass(item, Validator) def validated(func): sig = signature(func) # Gather the function annotations annotations = { name:val for name, val in func.__annotations__.items() if isvalidator(val) } # Get the return annotation (if any) retcheck = annotations.pop('return', None) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) # Enforce return check (if any) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper def enforce(**annotations): retcheck = annotations.pop('return_', None) def decorate(func): sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper return decorate # Examples if __name__ == '__main__': @validated def add(x:Integer, y:Integer) -> Integer: return x + y @validated def div(x:Integer, y:Integer) -> Integer: return x / y @enforce(x=Integer, y=Integer) def sub(x, y): return x - y class Stock: name = NonEmptyString() shares = PositiveInteger() price = PositiveFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def cost(self): return self.shares * self.price @validated def sell(self, nshares:PositiveInteger): self.shares -= nshares ================================================ FILE: Solutions/7_5/mymeta.py ================================================ # mymeta.py class mytype(type): @staticmethod def __new__(meta, name, bases, __dict__): print("Creating class :", name) print("Base classes :", bases) print("Attributes :", list(__dict__.keys())) return super().__new__(meta, name, bases, __dict__) class myobject(metaclass=mytype): pass class Stock(myobject): def __init__(self,name,shares,price): self.name = name self.shares = shares self.price = price def cost(self): return self.shares*self.price def sell(self,nshares): self.shares -= nshares class MyStock(Stock): pass ================================================ FILE: Solutions/7_6/reader.py ================================================ # reader.py import csv import logging log = logging.getLogger(__name__) def convert_csv(lines, converter, *, headers=None): rows = csv.reader(lines) if headers is None: headers = next(rows) records = [] for rowno, row in enumerate(rows, start=1): try: records.append(converter(headers, row)) except ValueError as e: log.warning('Row %s: Bad row: %s', rowno, row) log.debug('Row %s: Reason: %s', rowno, row) return records def csv_as_dicts(lines, types, *, headers=None): return convert_csv(lines, lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) }) def csv_as_instances(lines, cls, *, headers=None): return convert_csv(lines, lambda headers, row: cls.from_row(row)) def read_csv_as_dicts(filename, types, *, headers=None): ''' Read CSV data into a list of dictionaries with optional type conversion ''' with open(filename) as file: return csv_as_dicts(file, types, headers=headers) def read_csv_as_instances(filename, cls, *, headers=None): ''' Read CSV data into a list of instances ''' with open(filename) as file: return csv_as_instances(file, cls, headers=headers) ================================================ FILE: Solutions/7_6/stock.py ================================================ # stock.py from structure import Structure class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares: PositiveInteger): self.shares -= nshares if __name__ == '__main__': from reader import read_csv_as_instances from tableformat import create_formatter, print_table portfolio = read_csv_as_instances('../../Data/portfolio.csv', Stock) formatter = create_formatter('text') print_table(portfolio, ['name','shares','price'], formatter) ================================================ FILE: Solutions/7_6/structure.py ================================================ # structure.py from validate import Validator, validated from collections import ChainMap class StructureMeta(type): @classmethod def __prepare__(meta, clsname, bases): return ChainMap({}, Validator.validators) @staticmethod def __new__(meta, name, bases, methods): methods = methods.maps[0] return super().__new__(meta, name, bases, methods) class Structure(metaclass=StructureMeta): _fields = () _types = () def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) @classmethod def from_row(cls, row): rowdata = [ func(val) for func, val in zip(cls._types, row) ] return cls(*rowdata) @classmethod def create_init(cls): ''' Create an __init__ method from _fields ''' args = ','.join(cls._fields) code = f'def __init__(self, {args}):\n' for name in cls._fields: code += f' self.{name} = {name}\n' locs = { } exec(code, locs) cls.__init__ = locs['__init__'] @classmethod def __init_subclass__(cls): # Apply the validated decorator to subclasses validate_attributes(cls) def validate_attributes(cls): ''' Class decorator that scans a class definition for Validators and builds a _fields variable that captures their definition order. ''' validators = [] for name, val in vars(cls).items(): if isinstance(val, Validator): validators.append(val) # Apply validated decorator to any callable with annotations elif callable(val) and val.__annotations__: setattr(cls, name, validated(val)) # Collect all of the field names cls._fields = tuple([v.name for v in validators]) # Collect type conversions. The lambda x:x is an identity # function that's used in case no expected_type is found. cls._types = tuple([ getattr(v, 'expected_type', lambda x: x) for v in validators ]) # Create the __init__ method if cls._fields: cls.create_init() return cls def typed_structure(clsname, **validators): cls = type(clsname, (Structure,), validators) return cls ================================================ FILE: Solutions/7_6/tableformat.py ================================================ # tableformat.py from abc import ABC, abstractmethod def print_table(records, fields, formatter): if not isinstance(formatter, TableFormatter): raise RuntimeError('Expected a TableFormatter') formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) class TableFormatter(ABC): @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass class TextTableFormatter(TableFormatter): def headings(self, headers): print(' '.join('%10s' % h for h in headers)) print(('-'*10 + ' ')*len(headers)) def row(self, rowdata): print(' '.join('%10s' % d for d in rowdata)) class CSVTableFormatter(TableFormatter): def headings(self, headers): print(','.join(headers)) def row(self, rowdata): print(','.join(str(d) for d in rowdata)) class HTMLTableFormatter(TableFormatter): def headings(self, headers): print('', end=' ') for h in headers: print('%s' % h, end=' ') print('') def row(self, rowdata): print('', end=' ') for d in rowdata: print('%s' % d, end=' ') print('') class ColumnFormatMixin: formats = [] def row(self, rowdata): rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)] super().row(rowdata) class UpperHeadersMixin: def headings(self, headers): super().headings([h.upper() for h in headers]) def create_formatter(name, column_formats=None, upper_headers=False): if name == 'text': formatter_cls = TextTableFormatter elif name == 'csv': formatter_cls = CSVTableFormatter elif name == 'html': formatter_cls = HTMLTableFormatter else: raise RuntimeError('Unknown format %s' % name) if column_formats: class formatter_cls(ColumnFormatMixin, formatter_cls): formats = column_formats if upper_headers: class formatter_cls(UpperHeadersMixin, formatter_cls): pass return formatter_cls() ================================================ FILE: Solutions/7_6/teststock.py ================================================ # teststock.py import stock import unittest class TestStock(unittest.TestCase): def test_create(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_create_keyword(self): s = stock.Stock(name='GOOG', shares=100, price=490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_cost(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.cost, 49010.0) def test_sell(self): s = stock.Stock('GOOG', 100, 490.1) s.sell(25) self.assertEqual(s.shares, 75) def test_from_row(self): s = stock.Stock.from_row(['GOOG','100','490.1']) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_repr(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)") def test_eq(self): a = stock.Stock('GOOG', 100, 490.1) b = stock.Stock('GOOG', 100, 490.1) self.assertTrue(a==b) # Tests for failure conditions def test_shares_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.shares = '50' def test_shares_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.shares = -50 def test_price_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.price = '45.23' def test_price_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.price = -45.23 def test_bad_attribute(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(AttributeError): s.share = 100 if __name__ == '__main__': unittest.main() ================================================ FILE: Solutions/7_6/validate.py ================================================ # validate.py class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) # Collect all derived classes into a dict validators = { } @classmethod def __init_subclass__(cls): cls.validators[cls.__name__] = cls class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') return super().check(value) _typed_classes = [ ('Integer', int), ('Float', float), ('String', str) ] globals().update((name, type(name, (Typed,), {'expected_type':ty})) for name, ty in _typed_classes) class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass from inspect import signature from functools import wraps def isvalidator(item): return isinstance(item, type) and issubclass(item, Validator) def validated(func): sig = signature(func) # Gather the function annotations annotations = { name:val for name, val in func.__annotations__.items() if isvalidator(val) } # Get the return annotation (if any) retcheck = annotations.pop('return', None) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) # Enforce return check (if any) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper def enforce(**annotations): retcheck = annotations.pop('return_', None) def decorate(func): sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper return decorate # Examples if __name__ == '__main__': @validated def add(x:Integer, y:Integer) -> Integer: return x + y @validated def div(x:Integer, y:Integer) -> Integer: return x / y @enforce(x=Integer, y=Integer) def sub(x, y): return x - y class Stock: name = NonEmptyString() shares = PositiveInteger() price = PositiveFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def cost(self): return self.shares * self.price @validated def sell(self, nshares:PositiveInteger): self.shares -= nshares ================================================ FILE: Solutions/8_1/follow.py ================================================ # follow.py import os import time def follow(filename): ''' Generator that produces a sequence of lines being written at the end of a file. ''' with open(filename,'r') as f: f.seek(0,os.SEEK_END) while True: line = f.readline() if line == '': time.sleep(0.1) # Sleep briefly to avoid busy wait continue yield line # Example use if __name__ == '__main__': for line in follow('../../Data/stocklog.csv'): fields = line.split(',') name = fields[0].strip('"') price = float(fields[1]) change = float(fields[4]) if change < 0: print('%10s %10.2f %10.2f' % (name, price, change)) ================================================ FILE: Solutions/8_1/reader.py ================================================ # reader.py import csv import logging log = logging.getLogger(__name__) def convert_csv(lines, converter, *, headers=None): rows = csv.reader(lines) if headers is None: headers = next(rows) records = [] for rowno, row in enumerate(rows, start=1): try: records.append(converter(headers, row)) except ValueError as e: log.warning('Row %s: Bad row: %s', rowno, row) log.debug('Row %s: Reason: %s', rowno, row) return records def csv_as_dicts(lines, types, *, headers=None): return convert_csv(lines, lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) }) def csv_as_instances(lines, cls, *, headers=None): return convert_csv(lines, lambda headers, row: cls.from_row(row)) def read_csv_as_dicts(filename, types, *, headers=None): ''' Read CSV data into a list of dictionaries with optional type conversion ''' with open(filename) as file: return csv_as_dicts(file, types, headers=headers) def read_csv_as_instances(filename, cls, *, headers=None): ''' Read CSV data into a list of instances ''' with open(filename) as file: return csv_as_instances(file, cls, headers=headers) ================================================ FILE: Solutions/8_1/stock.py ================================================ # stock.py from structure import Structure from validate import String, PositiveInteger, PositiveFloat class Stock(Structure): name = String('name') shares = PositiveInteger('shares') price = PositiveFloat('price') @property def cost(self): return self.shares * self.price def sell(self, nshares): self.shares -= nshares if __name__ == '__main__': from reader import read_csv_as_instances from tableformat import create_formatter, print_table portfolio = read_csv_as_instances('../../Data/portfolio.csv', Stock) formatter = create_formatter('text') print_table(portfolio, ['name','shares','price'], formatter) ================================================ FILE: Solutions/8_1/structure.py ================================================ # structure.py from validate import Validator, validated from collections import ChainMap class StructureMeta(type): @classmethod def __prepare__(meta, clsname, bases): return ChainMap({}, Validator.validators) @staticmethod def __new__(meta, name, bases, methods): methods = methods.maps[0] return super().__new__(meta, name, bases, methods) class Structure(metaclass=StructureMeta): _fields = () _types = () def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) def __iter__(self): for name in self._fields: yield getattr(self, name) def __eq__(self, other): return isinstance(other, type(self)) and tuple(self) == tuple(other) @classmethod def from_row(cls, row): rowdata = [ func(val) for func, val in zip(cls._types, row) ] return cls(*rowdata) @classmethod def create_init(cls): ''' Create an __init__ method from _fields ''' args = ','.join(cls._fields) code = f'def __init__(self, {args}):\n' for name in cls._fields: code += f' self.{name} = {name}\n' locs = { } exec(code, locs) cls.__init__ = locs['__init__'] @classmethod def __init_subclass__(cls): # Apply the validated decorator to subclasses validate_attributes(cls) def validate_attributes(cls): ''' Class decorator that scans a class definition for Validators and builds a _fields variable that captures their definition order. ''' validators = [] for name, val in vars(cls).items(): if isinstance(val, Validator): validators.append(val) # Apply validated decorator to any callable with annotations elif callable(val) and val.__annotations__: setattr(cls, name, validated(val)) # Collect all of the field names cls._fields = tuple([v.name for v in validators]) # Collect type conversions. The lambda x:x is an identity # function that's used in case no expected_type is found. cls._types = tuple([ getattr(v, 'expected_type', lambda x: x) for v in validators ]) # Create the __init__ method if cls._fields: cls.create_init() return cls def typed_structure(clsname, **validators): cls = type(clsname, (Structure,), validators) return cls ================================================ FILE: Solutions/8_1/teststock.py ================================================ # teststock.py import stock import unittest class TestStock(unittest.TestCase): def test_create(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_create_keyword(self): s = stock.Stock(name='GOOG', shares=100, price=490.1) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_cost(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(s.cost, 49010.0) def test_sell(self): s = stock.Stock('GOOG', 100, 490.1) s.sell(25) self.assertEqual(s.shares, 75) def test_from_row(self): s = stock.Stock.from_row(['GOOG','100','490.1']) self.assertEqual(s.name, 'GOOG') self.assertEqual(s.shares, 100) self.assertEqual(s.price, 490.1) def test_repr(self): s = stock.Stock('GOOG', 100, 490.1) self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)") def test_eq(self): a = stock.Stock('GOOG', 100, 490.1) b = stock.Stock('GOOG', 100, 490.1) self.assertTrue(a==b) # Tests for failure conditions def test_shares_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.shares = '50' def test_shares_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.shares = -50 def test_price_badtype(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(TypeError): s.price = '45.23' def test_price_badvalue(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(ValueError): s.price = -45.23 def test_bad_attribute(self): s = stock.Stock('GOOG', 100, 490.1) with self.assertRaises(AttributeError): s.share = 100 if __name__ == '__main__': unittest.main() ================================================ FILE: Solutions/8_1/validate.py ================================================ # validate.py class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) # Collect all derived classes into a dict validators = { } @classmethod def __init_subclass__(cls): cls.validators[cls.__name__] = cls class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') return super().check(value) _typed_classes = [ ('Integer', int), ('Float', float), ('String', str) ] globals().update((name, type(name, (Typed,), {'expected_type':ty})) for name, ty in _typed_classes) class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass from inspect import signature from functools import wraps def isvalidator(item): return isinstance(item, type) and issubclass(item, Validator) def validated(func): sig = signature(func) # Gather the function annotations annotations = { name:val for name, val in func.__annotations__.items() if isvalidator(val) } # Get the return annotation (if any) retcheck = annotations.pop('return', None) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) # Enforce return check (if any) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper def enforce(**annotations): retcheck = annotations.pop('return_', None) def decorate(func): sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper return decorate # Examples if __name__ == '__main__': @validated def add(x:Integer, y:Integer) -> Integer: return x + y @validated def div(x:Integer, y:Integer) -> Integer: return x / y @enforce(x=Integer, y=Integer) def sub(x, y): return x - y class Stock: name = NonEmptyString() shares = PositiveInteger() price = PositiveFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def cost(self): return self.shares * self.price @validated def sell(self, nshares:PositiveInteger): self.shares -= nshares ================================================ FILE: Solutions/8_2/follow.py ================================================ # follow.py import os import time import csv def follow(filename): ''' Generator that produces a sequence of lines being written at the end of a file. ''' with open(filename,'r') as f: f.seek(0,os.SEEK_END) while True: line = f.readline() if line == '': time.sleep(0.1) # Sleep briefly to avoid busy wait continue yield line ================================================ FILE: Solutions/8_2/structure.py ================================================ # structure.py from validate import Validator, validated from collections import ChainMap class StructureMeta(type): @classmethod def __prepare__(meta, clsname, bases): return ChainMap({}, Validator.validators) @staticmethod def __new__(meta, name, bases, methods): methods = methods.maps[0] return super().__new__(meta, name, bases, methods) class Structure(metaclass=StructureMeta): _fields = () _types = () def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) def __iter__(self): for name in self._fields: yield getattr(self, name) def __eq__(self, other): return isinstance(other, type(self)) and tuple(self) == tuple(other) @classmethod def from_row(cls, row): rowdata = [ func(val) for func, val in zip(cls._types, row) ] return cls(*rowdata) @classmethod def create_init(cls): ''' Create an __init__ method from _fields ''' args = ','.join(cls._fields) code = f'def __init__(self, {args}):\n' for name in cls._fields: code += f' self.{name} = {name}\n' locs = { } exec(code, locs) cls.__init__ = locs['__init__'] @classmethod def __init_subclass__(cls): # Apply the validated decorator to subclasses validate_attributes(cls) def validate_attributes(cls): ''' Class decorator that scans a class definition for Validators and builds a _fields variable that captures their definition order. ''' validators = [] for name, val in vars(cls).items(): if isinstance(val, Validator): validators.append(val) # Apply validated decorator to any callable with annotations elif callable(val) and val.__annotations__: setattr(cls, name, validated(val)) # Collect all of the field names cls._fields = tuple([v.name for v in validators]) # Collect type conversions. The lambda x:x is an identity # function that's used in case no expected_type is found. cls._types = tuple([ getattr(v, 'expected_type', lambda x: x) for v in validators ]) # Create the __init__ method if cls._fields: cls.create_init() return cls def typed_structure(clsname, **validators): cls = type(clsname, (Structure,), validators) return cls ================================================ FILE: Solutions/8_2/tableformat.py ================================================ # tableformat.py from abc import ABC, abstractmethod def print_table(records, fields, formatter): if not isinstance(formatter, TableFormatter): raise RuntimeError('Expected a TableFormatter') formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) class TableFormatter(ABC): @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass class TextTableFormatter(TableFormatter): def headings(self, headers): print(' '.join('%10s' % h for h in headers)) print(('-'*10 + ' ')*len(headers)) def row(self, rowdata): print(' '.join('%10s' % d for d in rowdata)) class CSVTableFormatter(TableFormatter): def headings(self, headers): print(','.join(headers)) def row(self, rowdata): print(','.join(str(d) for d in rowdata)) class HTMLTableFormatter(TableFormatter): def headings(self, headers): print('', end=' ') for h in headers: print('%s' % h, end=' ') print('') def row(self, rowdata): print('', end=' ') for d in rowdata: print('%s' % d, end=' ') print('') class ColumnFormatMixin: formats = [] def row(self, rowdata): rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)] super().row(rowdata) class UpperHeadersMixin: def headings(self, headers): super().headings([h.upper() for h in headers]) def create_formatter(name, column_formats=None, upper_headers=False): if name == 'text': formatter_cls = TextTableFormatter elif name == 'csv': formatter_cls = CSVTableFormatter elif name == 'html': formatter_cls = HTMLTableFormatter else: raise RuntimeError('Unknown format %s' % name) if column_formats: class formatter_cls(ColumnFormatMixin, formatter_cls): formats = column_formats if upper_headers: class formatter_cls(UpperHeadersMixin, formatter_cls): pass return formatter_cls() ================================================ FILE: Solutions/8_2/ticker.py ================================================ # ticker.py from structure import Structure class Ticker(Structure): name = String() price = Float() date = String() time = String() change = Float() open = Float() high = Float() low = Float() volume = Integer() if __name__ == '__main__': from follow import follow import csv from tableformat import create_formatter, print_table formatter = create_formatter('text') lines = follow('../../Data/stocklog.csv') rows = csv.reader(lines) records = (Ticker.from_row(row) for row in rows) negative = (rec for rec in records if rec.change < 0) print_table(negative, ['name','price','change'], formatter) ================================================ FILE: Solutions/8_2/validate.py ================================================ # validate.py class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) # Collect all derived classes into a dict validators = { } @classmethod def __init_subclass__(cls): cls.validators[cls.__name__] = cls class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') return super().check(value) _typed_classes = [ ('Integer', int), ('Float', float), ('String', str) ] globals().update((name, type(name, (Typed,), {'expected_type':ty})) for name, ty in _typed_classes) class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass from inspect import signature from functools import wraps def isvalidator(item): return isinstance(item, type) and issubclass(item, Validator) def validated(func): sig = signature(func) # Gather the function annotations annotations = { name:val for name, val in func.__annotations__.items() if isvalidator(val) } # Get the return annotation (if any) retcheck = annotations.pop('return', None) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) # Enforce return check (if any) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper def enforce(**annotations): retcheck = annotations.pop('return_', None) def decorate(func): sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper return decorate # Examples if __name__ == '__main__': @validated def add(x:Integer, y:Integer) -> Integer: return x + y @validated def div(x:Integer, y:Integer) -> Integer: return x / y @enforce(x=Integer, y=Integer) def sub(x, y): return x - y class Stock: name = NonEmptyString() shares = PositiveInteger() price = PositiveFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def cost(self): return self.shares * self.price @validated def sell(self, nshares:PositiveInteger): self.shares -= nshares ================================================ FILE: Solutions/8_3/cofollow.py ================================================ # cofollow.py import os import time def follow(filename, target): with open(filename, 'r') as f: f.seek(0,os.SEEK_END) while True: line = f.readline() if line != '': target.send(line) else: time.sleep(0.1) # Decorator for coroutines from functools import wraps def consumer(func): @wraps(func) def start(*args,**kwargs): f = func(*args,**kwargs) f.send(None) return f return start # Sample coroutine @consumer def printer(): while True: item = yield print(item) # Example use. if __name__ == '__main__': follow('../../Data/stocklog.csv', printer()) ================================================ FILE: Solutions/8_3/coticker.py ================================================ # coticker.py from structure import Structure class Ticker(Structure): name = String() price = Float() date = String() time = String() change = Float() open = Float() high = Float() low = Float() volume = Integer() from cofollow import consumer, follow from tableformat import create_formatter import csv @consumer def to_csv(target): def producer(): while True: yield line reader = csv.reader(producer()) while True: line = yield target.send(next(reader)) @consumer def create_ticker(target): while True: row = yield target.send(Ticker.from_row(row)) @consumer def negchange(target): while True: record = yield if record.change < 0: target.send(record) @consumer def ticker(fmt, fields): formatter = create_formatter(fmt) formatter.headings(fields) while True: rec = yield row = [getattr(rec, name) for name in fields] formatter.row(row) if __name__ == '__main__': follow('../../Data/stocklog.csv', to_csv( create_ticker( negchange( ticker('text', ['name','price','change']))))) ================================================ FILE: Solutions/8_3/structure.py ================================================ # structure.py from validate import Validator, validated from collections import ChainMap class StructureMeta(type): @classmethod def __prepare__(meta, clsname, bases): return ChainMap({}, Validator.validators) @staticmethod def __new__(meta, name, bases, methods): methods = methods.maps[0] return super().__new__(meta, name, bases, methods) class Structure(metaclass=StructureMeta): _fields = () _types = () def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) def __iter__(self): for name in self._fields: yield getattr(self, name) def __eq__(self, other): return isinstance(other, type(self)) and tuple(self) == tuple(other) @classmethod def from_row(cls, row): rowdata = [ func(val) for func, val in zip(cls._types, row) ] return cls(*rowdata) @classmethod def create_init(cls): ''' Create an __init__ method from _fields ''' args = ','.join(cls._fields) code = f'def __init__(self, {args}):\n' for name in cls._fields: code += f' self.{name} = {name}\n' locs = { } exec(code, locs) cls.__init__ = locs['__init__'] @classmethod def __init_subclass__(cls): # Apply the validated decorator to subclasses validate_attributes(cls) def validate_attributes(cls): ''' Class decorator that scans a class definition for Validators and builds a _fields variable that captures their definition order. ''' validators = [] for name, val in vars(cls).items(): if isinstance(val, Validator): validators.append(val) # Apply validated decorator to any callable with annotations elif callable(val) and val.__annotations__: setattr(cls, name, validated(val)) # Collect all of the field names cls._fields = tuple([v.name for v in validators]) # Collect type conversions. The lambda x:x is an identity # function that's used in case no expected_type is found. cls._types = tuple([ getattr(v, 'expected_type', lambda x: x) for v in validators ]) # Create the __init__ method if cls._fields: cls.create_init() return cls def typed_structure(clsname, **validators): cls = type(clsname, (Structure,), validators) return cls ================================================ FILE: Solutions/8_3/tableformat.py ================================================ # tableformat.py from abc import ABC, abstractmethod def print_table(records, fields, formatter): if not isinstance(formatter, TableFormatter): raise RuntimeError('Expected a TableFormatter') formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) class TableFormatter(ABC): @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass class TextTableFormatter(TableFormatter): def headings(self, headers): print(' '.join('%10s' % h for h in headers)) print(('-'*10 + ' ')*len(headers)) def row(self, rowdata): print(' '.join('%10s' % d for d in rowdata)) class CSVTableFormatter(TableFormatter): def headings(self, headers): print(','.join(headers)) def row(self, rowdata): print(','.join(str(d) for d in rowdata)) class HTMLTableFormatter(TableFormatter): def headings(self, headers): print('', end=' ') for h in headers: print('%s' % h, end=' ') print('') def row(self, rowdata): print('', end=' ') for d in rowdata: print('%s' % d, end=' ') print('') class ColumnFormatMixin: formats = [] def row(self, rowdata): rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)] super().row(rowdata) class UpperHeadersMixin: def headings(self, headers): super().headings([h.upper() for h in headers]) def create_formatter(name, column_formats=None, upper_headers=False): if name == 'text': formatter_cls = TextTableFormatter elif name == 'csv': formatter_cls = CSVTableFormatter elif name == 'html': formatter_cls = HTMLTableFormatter else: raise RuntimeError('Unknown format %s' % name) if column_formats: class formatter_cls(ColumnFormatMixin, formatter_cls): formats = column_formats if upper_headers: class formatter_cls(UpperHeadersMixin, formatter_cls): pass return formatter_cls() ================================================ FILE: Solutions/8_3/validate.py ================================================ # validate.py class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) # Collect all derived classes into a dict validators = { } @classmethod def __init_subclass__(cls): cls.validators[cls.__name__] = cls class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') return super().check(value) _typed_classes = [ ('Integer', int), ('Float', float), ('String', str) ] globals().update((name, type(name, (Typed,), {'expected_type':ty})) for name, ty in _typed_classes) class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass from inspect import signature from functools import wraps def isvalidator(item): return isinstance(item, type) and issubclass(item, Validator) def validated(func): sig = signature(func) # Gather the function annotations annotations = { name:val for name, val in func.__annotations__.items() if isvalidator(val) } # Get the return annotation (if any) retcheck = annotations.pop('return', None) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) # Enforce return check (if any) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper def enforce(**annotations): retcheck = annotations.pop('return_', None) def decorate(func): sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper return decorate # Examples if __name__ == '__main__': @validated def add(x:Integer, y:Integer) -> Integer: return x + y @validated def div(x:Integer, y:Integer) -> Integer: return x / y @enforce(x=Integer, y=Integer) def sub(x, y): return x - y class Stock: name = NonEmptyString() shares = PositiveInteger() price = PositiveFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def cost(self): return self.shares * self.price @validated def sell(self, nshares:PositiveInteger): self.shares -= nshares ================================================ FILE: Solutions/8_4/cofollow.py ================================================ # cofollow.py import os import time import csv def follow(filename,target): with open(filename,"r") as f: f.seek(0,os.SEEK_END) while True: line = f.readline() if line != '': target.send(line) else: time.sleep(0.1) # Decorator for coroutines from functools import wraps def consumer(func): @wraps(func) def start(*args,**kwargs): f = func(*args,**kwargs) f.send(None) return f return start # Sample coroutine @consumer def printer(): while True: try: item = yield print(item) except Exception as e: print('ERROR: %r' % e) # Example use. if __name__ == '__main__': follow('../../Data/stocklog.csv', printer()) ================================================ FILE: Solutions/8_4/follow.py ================================================ # follow.py import os import time def follow(filename): ''' Generator that produces a sequence of lines being written at the end of a file. ''' try: with open(filename,'r') as f: f.seek(0,os.SEEK_END) while True: line = f.readline() if line == '': time.sleep(0.1) # Sleep briefly to avoid busy wait continue yield line except GeneratorExit: print('Following Done') def splitter(lines): for line in lines: yield line.split(',') def make_records(rows,names): for row in rows: yield dict(zip(names,row)) def unquote(records,keylist): for r in records: for key in keylist: r[key] = r[key].strip('"') yield r def convert(records,converter,keylist): for r in records: for key in keylist: r[key] = converter(r[key]) yield r def parse_stock_data(lines): rows = splitter(lines) records = make_records(rows,['name','price','date','time', 'change','open','high','low','volume']) records = unquote(records,["name","date","time"]) records = convert(records,float,['price','change','open','high','low']) records = convert(records,int,['volume']) return records # Sample use if __name__ == '__main__': lines = follow("../../Data/stocklog.dat") records = parse_stock_data(lines) for r in records: print("%(name)10s %(price)10.2f %(change)10.2f" % r) ================================================ FILE: Solutions/8_5/multitask.py ================================================ # multitask.py from collections import deque tasks = deque() def run(): while tasks: task = tasks.popleft() try: next(task) tasks.append(task) except StopIteration: print('Task done') def countdown(n): while n > 0: print('T-minus', n) yield n -= 1 def countup(n): x = 0 while x < n: print('Up we go', x) yield x += 1 if __name__ == '__main__': tasks.append(countdown(10)) tasks.append(countdown(5)) tasks.append(countup(20)) run() ================================================ FILE: Solutions/8_5/server.py ================================================ # server.py from socket import * from select import select from collections import deque tasks = deque() recv_wait = {} # sock -> task send_wait = {} # sock -> task def run(): while any([tasks, recv_wait, send_wait]): while not tasks: can_recv, can_send, _ = select(recv_wait, send_wait, []) for s in can_recv: tasks.append(recv_wait.pop(s)) for s in can_send: tasks.append(send_wait.pop(s)) task = tasks.popleft() try: reason, resource = task.send(None) if reason == 'recv': recv_wait[resource] = task elif reason == 'send': send_wait[resource] = task else: raise RuntimeError('Unknown reason %r' % reason) except StopIteration: print('Task done') def tcp_server(address, handler): sock = socket(AF_INET, SOCK_STREAM) sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.bind(address) sock.listen(5) while True: yield 'recv', sock client, addr = sock.accept() tasks.append(handler(client, addr)) def echo_handler(client, address): print('Connection from', address) while True: yield 'recv', client data = client.recv(1000) if not data: break yield 'send', client client.send(b'GOT:' + data) print('Connection closed') if __name__ == '__main__': tasks.append(tcp_server(('',25000), echo_handler)) run() ================================================ FILE: Solutions/8_6/asyncserver.py ================================================ # server.py from socket import * from select import select from collections import deque from types import coroutine tasks = deque() recv_wait = {} # sock -> task send_wait = {} # sock -> task def run(): while any([tasks, recv_wait, send_wait]): while not tasks: can_recv, can_send, _ = select(recv_wait, send_wait, []) for s in can_recv: tasks.append(recv_wait.pop(s)) for s in can_send: tasks.append(send_wait.pop(s)) task = tasks.popleft() try: reason, resource = task.send(None) if reason == 'recv': recv_wait[resource] = task elif reason == 'send': send_wait[resource] = task else: raise RuntimeError('Unknown reason %r' % reason) except StopIteration: print('Task done') class GenSocket: def __init__(self, sock): self.sock = sock @coroutine def accept(self): yield 'recv', self.sock client, addr = self.sock.accept() return GenSocket(client), addr @coroutine def recv(self, maxsize): yield 'recv', self.sock return self.sock.recv(maxsize) @coroutine def send(self, data): yield 'send', self.sock return self.sock.send(data) def __getattr__(self, name): return getattr(self.sock, name) async def tcp_server(address, handler): sock = GenSocket(socket(AF_INET, SOCK_STREAM)) sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.bind(address) sock.listen(5) while True: client, addr = await sock.accept() tasks.append(handler(client, addr)) async def echo_handler(client, address): print('Connection from', address) while True: data = await client.recv(1000) if not data: break await client.send(b'GOT:' + data) print('Connection closed') if __name__ == '__main__': tasks.append(tcp_server(('',25000), echo_handler)) run() ================================================ FILE: Solutions/8_6/cofollow.py ================================================ # cofollow.py import os import time import csv def follow(filename,target): with open(filename,"r") as f: f.seek(0,os.SEEK_END) while True: line = f.readline() if line != '': target.send(line) else: time.sleep(0.1) def receive(expected_type): msg = yield assert isinstance(msg, expected_type), 'Expected type %s' % (expected_type) return msg # Decorator for coroutines from functools import wraps def consumer(func): @wraps(func) def start(*args,**kwargs): f = func(*args,**kwargs) f.send(None) return f return start # Sample coroutine @consumer def printer(): while True: item = yield from receive(object) print(item) # Example use. if __name__ == '__main__': follow('../../Data/stocklog.csv', printer()) ================================================ FILE: Solutions/8_6/coticker.py ================================================ # coticker.py from structure import Structure from validate import String, Integer, Float class Ticker(Structure): name = String() price = Float() date = String() time = String() change = Float() open = Float() high = Float() low = Float() volume = Integer() from cofollow import consumer, follow, receive from tableformat import create_formatter import csv @consumer def to_csv(target): def producer(): while True: yield line reader = csv.reader(producer()) while True: line = yield from receive(str) target.send(next(reader)) @consumer def create_ticker(target): while True: row = yield from receive(list) target.send(Ticker.from_row(row)) @consumer def negchange(target): while True: record = yield from receive(Ticker) if record.change < 0: target.send(record) @consumer def ticker(fmt, fields): formatter = create_formatter('text') formatter.headings(fields) while True: rec = yield from receive(Ticker) row = [getattr(rec, name) for name in fields] formatter.row(row) if __name__ == '__main__': follow('../../Data/stocklog.csv', to_csv( create_ticker( negchange( ticker('text', ['name','price','change']))))) ================================================ FILE: Solutions/8_6/server.py ================================================ # server.py from socket import * from select import select from collections import deque tasks = deque() recv_wait = {} # sock -> task send_wait = {} # sock -> task def run(): while any([tasks, recv_wait, send_wait]): while not tasks: can_recv, can_send, _ = select(recv_wait, send_wait, []) for s in can_recv: tasks.append(recv_wait.pop(s)) for s in can_send: tasks.append(send_wait.pop(s)) task = tasks.popleft() try: reason, resource = task.send(None) if reason == 'recv': recv_wait[resource] = task elif reason == 'send': send_wait[resource] = task else: raise RuntimeError('Unknown reason %r' % reason) except StopIteration: print('Task done') class GenSocket: def __init__(self, sock): self.sock = sock def accept(self): yield 'recv', self.sock client, addr = self.sock.accept() return GenSocket(client), addr def recv(self, maxsize): yield 'recv', self.sock return self.sock.recv(maxsize) def send(self, data): yield 'send', self.sock return self.sock.send(data) def __getattr__(self, name): return getattr(self.sock, name) def tcp_server(address, handler): sock = GenSocket(socket(AF_INET, SOCK_STREAM)) sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.bind(address) sock.listen(5) while True: client, addr = yield from sock.accept() tasks.append(handler(client, addr)) def echo_handler(client, address): print('Connection from', address) while True: data = yield from client.recv(1000) if not data: break yield from client.send(b'GOT:' + data) print('Connection closed') if __name__ == '__main__': tasks.append(tcp_server(('',25000), echo_handler)) run() ================================================ FILE: Solutions/8_6/structure.py ================================================ # structure.py from validate import Validator, validated from collections import ChainMap class StructureMeta(type): @classmethod def __prepare__(meta, clsname, bases): return ChainMap({}, Validator.validators) @staticmethod def __new__(meta, name, bases, methods): methods = methods.maps[0] return super().__new__(meta, name, bases, methods) class Structure(metaclass=StructureMeta): _fields = () _types = () def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) def __iter__(self): for name in self._fields: yield getattr(self, name) def __eq__(self, other): return isinstance(other, type(self)) and tuple(self) == tuple(other) @classmethod def from_row(cls, row): rowdata = [ func(val) for func, val in zip(cls._types, row) ] return cls(*rowdata) @classmethod def create_init(cls): ''' Create an __init__ method from _fields ''' args = ','.join(cls._fields) code = f'def __init__(self, {args}):\n' for name in cls._fields: code += f' self.{name} = {name}\n' locs = { } exec(code, locs) cls.__init__ = locs['__init__'] @classmethod def __init_subclass__(cls): # Apply the validated decorator to subclasses validate_attributes(cls) def validate_attributes(cls): ''' Class decorator that scans a class definition for Validators and builds a _fields variable that captures their definition order. ''' validators = [] for name, val in vars(cls).items(): if isinstance(val, Validator): validators.append(val) # Apply validated decorator to any callable with annotations elif callable(val) and val.__annotations__: setattr(cls, name, validated(val)) # Collect all of the field names cls._fields = tuple([v.name for v in validators]) # Collect type conversions. The lambda x:x is an identity # function that's used in case no expected_type is found. cls._types = tuple([ getattr(v, 'expected_type', lambda x: x) for v in validators ]) # Create the __init__ method if cls._fields: cls.create_init() return cls def typed_structure(clsname, **validators): cls = type(clsname, (Structure,), validators) return cls ================================================ FILE: Solutions/8_6/tableformat.py ================================================ # tableformat.py from abc import ABC, abstractmethod def print_table(records, fields, formatter): if not isinstance(formatter, TableFormatter): raise RuntimeError('Expected a TableFormatter') formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) class TableFormatter(ABC): @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass class TextTableFormatter(TableFormatter): def headings(self, headers): print(' '.join('%10s' % h for h in headers)) print(('-'*10 + ' ')*len(headers)) def row(self, rowdata): print(' '.join('%10s' % d for d in rowdata)) class CSVTableFormatter(TableFormatter): def headings(self, headers): print(','.join(headers)) def row(self, rowdata): print(','.join(str(d) for d in rowdata)) class HTMLTableFormatter(TableFormatter): def headings(self, headers): print('', end=' ') for h in headers: print('%s' % h, end=' ') print('') def row(self, rowdata): print('', end=' ') for d in rowdata: print('%s' % d, end=' ') print('') class ColumnFormatMixin: formats = [] def row(self, rowdata): rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)] super().row(rowdata) class UpperHeadersMixin: def headings(self, headers): super().headings([h.upper() for h in headers]) def create_formatter(name, column_formats=None, upper_headers=False): if name == 'text': formatter_cls = TextTableFormatter elif name == 'csv': formatter_cls = CSVTableFormatter elif name == 'html': formatter_cls = HTMLTableFormatter else: raise RuntimeError('Unknown format %s' % name) if column_formats: class formatter_cls(ColumnFormatMixin, formatter_cls): formats = column_formats if upper_headers: class formatter_cls(UpperHeadersMixin, formatter_cls): pass return formatter_cls() ================================================ FILE: Solutions/8_6/validate.py ================================================ # validate.py class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) # Collect all derived classes into a dict validators = { } @classmethod def __init_subclass__(cls): cls.validators[cls.__name__] = cls class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') return super().check(value) _typed_classes = [ ('Integer', int), ('Float', float), ('String', str) ] globals().update((name, type(name, (Typed,), {'expected_type':ty})) for name, ty in _typed_classes) class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass from inspect import signature from functools import wraps def isvalidator(item): return isinstance(item, type) and issubclass(item, Validator) def validated(func): sig = signature(func) # Gather the function annotations annotations = { name:val for name, val in func.__annotations__.items() if isvalidator(val) } # Get the return annotation (if any) retcheck = annotations.pop('return', None) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) # Enforce return check (if any) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper def enforce(**annotations): retcheck = annotations.pop('return_', None) def decorate(func): sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper return decorate # Examples if __name__ == '__main__': @validated def add(x:Integer, y:Integer) -> Integer: return x + y @validated def div(x:Integer, y:Integer) -> Integer: return x / y @enforce(x=Integer, y=Integer) def sub(x, y): return x - y class Stock: name = NonEmptyString() shares = PositiveInteger() price = PositiveFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def cost(self): return self.shares * self.price @validated def sell(self, nshares:PositiveInteger): self.shares -= nshares ================================================ FILE: Solutions/9_1/simplemod.py ================================================ # simplemod.py x = 42 # A global variable # A simple function def foo(): print("x is %s" % x) # A simple class class Spam: def yow(self): print('Yow!') # A scripting statement print("Loaded simplemod") ================================================ FILE: Solutions/9_2/stock.py ================================================ # stock.py from structly.structure import Structure class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares: PositiveInteger): self.shares -= nshares if __name__ == '__main__': from structly.reader import read_csv_as_instances from structly.tableformat import create_formatter, print_table portfolio = read_csv_as_instances('../../Data/portfolio.csv', Stock) formatter = create_formatter('text') print_table(portfolio, ['name','shares','price'], formatter) ================================================ FILE: Solutions/9_2/structly/__init__.py ================================================ ================================================ FILE: Solutions/9_2/structly/reader.py ================================================ # reader.py import csv import logging log = logging.getLogger(__name__) def convert_csv(lines, converter, *, headers=None): rows = csv.reader(lines) if headers is None: headers = next(rows) records = [] for rowno, row in enumerate(rows, start=1): try: records.append(converter(headers, row)) except ValueError as e: log.warning('Row %s: Bad row: %s', rowno, row) log.debug('Row %s: Reason: %s', rowno, row) return records def csv_as_dicts(lines, types, *, headers=None): return convert_csv(lines, lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) }) def csv_as_instances(lines, cls, *, headers=None): return convert_csv(lines, lambda headers, row: cls.from_row(row)) def read_csv_as_dicts(filename, types, *, headers=None): ''' Read CSV data into a list of dictionaries with optional type conversion ''' with open(filename) as file: return csv_as_dicts(file, types, headers=headers) def read_csv_as_instances(filename, cls, *, headers=None): ''' Read CSV data into a list of instances ''' with open(filename) as file: return csv_as_instances(file, cls, headers=headers) ================================================ FILE: Solutions/9_2/structly/structure.py ================================================ # structure.py from .validate import Validator, validated from collections import ChainMap class StructureMeta(type): @classmethod def __prepare__(meta, clsname, bases): return ChainMap({}, Validator.validators) @staticmethod def __new__(meta, name, bases, methods): methods = methods.maps[0] return super().__new__(meta, name, bases, methods) class Structure(metaclass=StructureMeta): _fields = () _types = () def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) def __iter__(self): for name in self._fields: yield getattr(self, name) def __eq__(self, other): return isinstance(other, type(self)) and tuple(self) == tuple(other) @classmethod def from_row(cls, row): rowdata = [ func(val) for func, val in zip(cls._types, row) ] return cls(*rowdata) @classmethod def create_init(cls): ''' Create an __init__ method from _fields ''' args = ','.join(cls._fields) code = f'def __init__(self, {args}):\n' for name in cls._fields: code += f' self.{name} = {name}\n' locs = { } exec(code, locs) cls.__init__ = locs['__init__'] @classmethod def __init_subclass__(cls): # Apply the validated decorator to subclasses validate_attributes(cls) def validate_attributes(cls): ''' Class decorator that scans a class definition for Validators and builds a _fields variable that captures their definition order. ''' validators = [] for name, val in vars(cls).items(): if isinstance(val, Validator): validators.append(val) # Apply validated decorator to any callable with annotations elif callable(val) and val.__annotations__: setattr(cls, name, validated(val)) # Collect all of the field names cls._fields = tuple([v.name for v in validators]) # Collect type conversions. The lambda x:x is an identity # function that's used in case no expected_type is found. cls._types = tuple([ getattr(v, 'expected_type', lambda x: x) for v in validators ]) # Create the __init__ method if cls._fields: cls.create_init() return cls def typed_structure(clsname, **validators): cls = type(clsname, (Structure,), validators) return cls ================================================ FILE: Solutions/9_2/structly/tableformat.py ================================================ # tableformat.py from abc import ABC, abstractmethod def print_table(records, fields, formatter): if not isinstance(formatter, TableFormatter): raise RuntimeError('Expected a TableFormatter') formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) class TableFormatter(ABC): @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass class TextTableFormatter(TableFormatter): def headings(self, headers): print(' '.join('%10s' % h for h in headers)) print(('-'*10 + ' ')*len(headers)) def row(self, rowdata): print(' '.join('%10s' % d for d in rowdata)) class CSVTableFormatter(TableFormatter): def headings(self, headers): print(','.join(headers)) def row(self, rowdata): print(','.join(str(d) for d in rowdata)) class HTMLTableFormatter(TableFormatter): def headings(self, headers): print('', end=' ') for h in headers: print('%s' % h, end=' ') print('') def row(self, rowdata): print('', end=' ') for d in rowdata: print('%s' % d, end=' ') print('') class ColumnFormatMixin: formats = [] def row(self, rowdata): rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)] super().row(rowdata) class UpperHeadersMixin: def headings(self, headers): super().headings([h.upper() for h in headers]) def create_formatter(name, column_formats=None, upper_headers=False): if name == 'text': formatter_cls = TextTableFormatter elif name == 'csv': formatter_cls = CSVTableFormatter elif name == 'html': formatter_cls = HTMLTableFormatter else: raise RuntimeError('Unknown format %s' % name) if column_formats: class formatter_cls(ColumnFormatMixin, formatter_cls): formats = column_formats if upper_headers: class formatter_cls(UpperHeadersMixin, formatter_cls): pass return formatter_cls() ================================================ FILE: Solutions/9_2/structly/validate.py ================================================ # validate.py class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) # Collect all derived classes into a dict validators = { } @classmethod def __init_subclass__(cls): cls.validators[cls.__name__] = cls class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') return super().check(value) _typed_classes = [ ('Integer', int), ('Float', float), ('String', str) ] globals().update((name, type(name, (Typed,), {'expected_type':ty})) for name, ty in _typed_classes) class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass from inspect import signature from functools import wraps def isvalidator(item): return isinstance(item, type) and issubclass(item, Validator) def validated(func): sig = signature(func) # Gather the function annotations annotations = { name:val for name, val in func.__annotations__.items() if isvalidator(val) } # Get the return annotation (if any) retcheck = annotations.pop('return', None) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) # Enforce return check (if any) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper def enforce(**annotations): retcheck = annotations.pop('return_', None) def decorate(func): sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper return decorate # Examples if __name__ == '__main__': @validated def add(x:Integer, y:Integer) -> Integer: return x + y @validated def div(x:Integer, y:Integer) -> Integer: return x / y @enforce(x=Integer, y=Integer) def sub(x, y): return x - y class Stock: name = NonEmptyString() shares = PositiveInteger() price = PositiveFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def cost(self): return self.shares * self.price @validated def sell(self, nshares:PositiveInteger): self.shares -= nshares ================================================ FILE: Solutions/9_3/stock.py ================================================ # stock.py from structly import * class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares: PositiveInteger): self.shares -= nshares if __name__ == '__main__': portfolio = read_csv_as_instances('../../Data/portfolio.csv', Stock) formatter = create_formatter('text') print_table(portfolio, ['name','shares','price'], formatter) ================================================ FILE: Solutions/9_3/structly/__init__.py ================================================ # structly/__init__.py from .structure import * from .reader import * from .tableformat import * __all__ = [ *structure.__all__, *reader.__all__, *tableformat.__all__ ] ================================================ FILE: Solutions/9_3/structly/reader.py ================================================ # reader.py import csv import logging log = logging.getLogger(__name__) def convert_csv(lines, converter, *, headers=None): rows = csv.reader(lines) if headers is None: headers = next(rows) records = [] for rowno, row in enumerate(rows, start=1): try: records.append(converter(headers, row)) except ValueError as e: log.warning('Row %s: Bad row: %s', rowno, row) log.debug('Row %s: Reason: %s', rowno, row) return records def csv_as_dicts(lines, types, *, headers=None): return convert_csv(lines, lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) }) def csv_as_instances(lines, cls, *, headers=None): return convert_csv(lines, lambda headers, row: cls.from_row(row)) def read_csv_as_dicts(filename, types, *, headers=None): ''' Read CSV data into a list of dictionaries with optional type conversion ''' with open(filename) as file: return csv_as_dicts(file, types, headers=headers) def read_csv_as_instances(filename, cls, *, headers=None): ''' Read CSV data into a list of instances ''' with open(filename) as file: return csv_as_instances(file, cls, headers=headers) ================================================ FILE: Solutions/9_3/structly/structure.py ================================================ # structure.py __all__ = [ 'Structure' ] from .validate import Validator, validated from collections import ChainMap class StructureMeta(type): @classmethod def __prepare__(meta, clsname, bases): return ChainMap({}, Validator.validators) @staticmethod def __new__(meta, name, bases, methods): methods = methods.maps[0] return super().__new__(meta, name, bases, methods) class Structure(metaclass=StructureMeta): _fields = () _types = () def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) def __iter__(self): for name in self._fields: yield getattr(self, name) def __eq__(self, other): return isinstance(other, type(self)) and tuple(self) == tuple(other) @classmethod def from_row(cls, row): rowdata = [ func(val) for func, val in zip(cls._types, row) ] return cls(*rowdata) @classmethod def create_init(cls): ''' Create an __init__ method from _fields ''' args = ','.join(cls._fields) code = f'def __init__(self, {args}):\n' for name in cls._fields: code += f' self.{name} = {name}\n' locs = { } exec(code, locs) cls.__init__ = locs['__init__'] @classmethod def __init_subclass__(cls): # Apply the validated decorator to subclasses validate_attributes(cls) def validate_attributes(cls): ''' Class decorator that scans a class definition for Validators and builds a _fields variable that captures their definition order. ''' validators = [] for name, val in vars(cls).items(): if isinstance(val, Validator): validators.append(val) # Apply validated decorator to any callable with annotations elif callable(val) and val.__annotations__: setattr(cls, name, validated(val)) # Collect all of the field names cls._fields = tuple([v.name for v in validators]) # Collect type conversions. The lambda x:x is an identity # function that's used in case no expected_type is found. cls._types = tuple([ getattr(v, 'expected_type', lambda x: x) for v in validators ]) # Create the __init__ method if cls._fields: cls.create_init() return cls def typed_structure(clsname, **validators): cls = type(clsname, (Structure,), validators) return cls ================================================ FILE: Solutions/9_3/structly/tableformat/__init__.py ================================================ # __init__.py from .formatter import print_table, create_formatter __all__ = [ 'print_table', 'create_formatter' ] ================================================ FILE: Solutions/9_3/structly/tableformat/formats/__init__.py ================================================ # formats/__init__.py ================================================ FILE: Solutions/9_3/structly/tableformat/formats/csv.py ================================================ # csv.py from ..formatter import TableFormatter class CSVTableFormatter(TableFormatter): def headings(self, headers): print(','.join(headers)) def row(self, rowdata): print(','.join(str(d) for d in rowdata)) ================================================ FILE: Solutions/9_3/structly/tableformat/formats/html.py ================================================ # html.py from ..formatter import TableFormatter class HTMLTableFormatter(TableFormatter): def headings(self, headers): print('', end=' ') for h in headers: print('%s' % h, end=' ') print('') def row(self, rowdata): print('', end=' ') for d in rowdata: print('%s' % d, end=' ') print('') ================================================ FILE: Solutions/9_3/structly/tableformat/formats/text.py ================================================ # text.py from ..formatter import TableFormatter class TextTableFormatter(TableFormatter): def headings(self, headers): print(' '.join('%10s' % h for h in headers)) print(('-'*10 + ' ')*len(headers)) def row(self, rowdata): print(' '.join('%10s' % d for d in rowdata)) ================================================ FILE: Solutions/9_3/structly/tableformat/formatter.py ================================================ # tableformat.py from abc import ABC, abstractmethod def print_table(records, fields, formatter): if not isinstance(formatter, TableFormatter): raise RuntimeError('Expected a TableFormatter') formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) class TableFormatter(ABC): @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass from .formats.text import TextTableFormatter from .formats.csv import CSVTableFormatter from .formats.html import HTMLTableFormatter class ColumnFormatMixin: formats = [] def row(self, rowdata): rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)] super().row(rowdata) class UpperHeadersMixin: def headings(self, headers): super().headings([h.upper() for h in headers]) def create_formatter(name, column_formats=None, upper_headers=False): if name == 'text': formatter_cls = TextTableFormatter elif name == 'csv': formatter_cls = CSVTableFormatter elif name == 'html': formatter_cls = HTMLTableFormatter else: raise RuntimeError('Unknown format %s' % name) if column_formats: class formatter_cls(ColumnFormatMixin, formatter_cls): formats = column_formats if upper_headers: class formatter_cls(UpperHeadersMixin, formatter_cls): pass return formatter_cls() ================================================ FILE: Solutions/9_3/structly/validate.py ================================================ # validate.py class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) # Collect all derived classes into a dict validators = { } @classmethod def __init_subclass__(cls): cls.validators[cls.__name__] = cls class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') return super().check(value) _typed_classes = [ ('Integer', int), ('Float', float), ('String', str) ] globals().update((name, type(name, (Typed,), {'expected_type':ty})) for name, ty in _typed_classes) class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass from inspect import signature from functools import wraps def isvalidator(item): return isinstance(item, type) and issubclass(item, Validator) def validated(func): sig = signature(func) # Gather the function annotations annotations = { name:val for name, val in func.__annotations__.items() if isvalidator(val) } # Get the return annotation (if any) retcheck = annotations.pop('return', None) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) # Enforce return check (if any) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper def enforce(**annotations): retcheck = annotations.pop('return_', None) def decorate(func): sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper return decorate # Examples if __name__ == '__main__': @validated def add(x:Integer, y:Integer) -> Integer: return x + y @validated def div(x:Integer, y:Integer) -> Integer: return x / y @enforce(x=Integer, y=Integer) def sub(x, y): return x - y class Stock: name = NonEmptyString() shares = PositiveInteger() price = PositiveFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def cost(self): return self.shares * self.price @validated def sell(self, nshares:PositiveInteger): self.shares -= nshares ================================================ FILE: Solutions/9_4/stock.py ================================================ # stock.py from structly import * class Stock(Structure): name = String() shares = PositiveInteger() price = PositiveFloat() @property def cost(self): return self.shares * self.price def sell(self, nshares: PositiveInteger): self.shares -= nshares if __name__ == '__main__': portfolio = read_csv_as_instances('../../Data/portfolio.csv', Stock) formatter = create_formatter('text') print_table(portfolio, ['name','shares','price'], formatter) ================================================ FILE: Solutions/9_4/structly/__init__.py ================================================ # structly/__init__.py from .structure import * from .reader import * from .tableformat import * __all__ = [ *structure.__all__, *reader.__all__, *tableformat.__all__ ] ================================================ FILE: Solutions/9_4/structly/reader.py ================================================ # reader.py import csv import logging log = logging.getLogger(__name__) def convert_csv(lines, converter, *, headers=None): rows = csv.reader(lines) if headers is None: headers = next(rows) records = [] for rowno, row in enumerate(rows, start=1): try: records.append(converter(headers, row)) except ValueError as e: log.warning('Row %s: Bad row: %s', rowno, row) log.debug('Row %s: Reason: %s', rowno, row) return records def csv_as_dicts(lines, types, *, headers=None): return convert_csv(lines, lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) }) def csv_as_instances(lines, cls, *, headers=None): return convert_csv(lines, lambda headers, row: cls.from_row(row)) def read_csv_as_dicts(filename, types, *, headers=None): ''' Read CSV data into a list of dictionaries with optional type conversion ''' with open(filename) as file: return csv_as_dicts(file, types, headers=headers) def read_csv_as_instances(filename, cls, *, headers=None): ''' Read CSV data into a list of instances ''' with open(filename) as file: return csv_as_instances(file, cls, headers=headers) ================================================ FILE: Solutions/9_4/structly/structure.py ================================================ # structure.py __all__ = [ 'Structure' ] from .validate import Validator, validated from collections import ChainMap class StructureMeta(type): @classmethod def __prepare__(meta, clsname, bases): return ChainMap({}, Validator.validators) @staticmethod def __new__(meta, name, bases, methods): methods = methods.maps[0] return super().__new__(meta, name, bases, methods) class Structure(metaclass=StructureMeta): _fields = () _types = () def __setattr__(self, name, value): if name.startswith('_') or name in self._fields: super().__setattr__(name, value) else: raise AttributeError('No attribute %s' % name) def __repr__(self): return '%s(%s)' % (type(self).__name__, ', '.join(repr(getattr(self, name)) for name in self._fields)) def __iter__(self): for name in self._fields: yield getattr(self, name) def __eq__(self, other): return isinstance(other, type(self)) and tuple(self) == tuple(other) @classmethod def from_row(cls, row): rowdata = [ func(val) for func, val in zip(cls._types, row) ] return cls(*rowdata) @classmethod def create_init(cls): ''' Create an __init__ method from _fields ''' args = ','.join(cls._fields) code = f'def __init__(self, {args}):\n' for name in cls._fields: code += f' self.{name} = {name}\n' locs = { } exec(code, locs) cls.__init__ = locs['__init__'] @classmethod def __init_subclass__(cls): # Apply the validated decorator to subclasses validate_attributes(cls) def validate_attributes(cls): ''' Class decorator that scans a class definition for Validators and builds a _fields variable that captures their definition order. ''' validators = [] for name, val in vars(cls).items(): if isinstance(val, Validator): validators.append(val) # Apply validated decorator to any callable with annotations elif callable(val) and val.__annotations__: setattr(cls, name, validated(val)) # Collect all of the field names cls._fields = tuple([v.name for v in validators]) # Collect type conversions. The lambda x:x is an identity # function that's used in case no expected_type is found. cls._types = tuple([ getattr(v, 'expected_type', lambda x: x) for v in validators ]) # Create the __init__ method if cls._fields: cls.create_init() return cls def typed_structure(clsname, **validators): cls = type(clsname, (Structure,), validators) return cls ================================================ FILE: Solutions/9_4/structly/tableformat/__init__.py ================================================ # __init__.py from .formatter import print_table, create_formatter __all__ = [ 'print_table', 'create_formatter' ] ================================================ FILE: Solutions/9_4/structly/tableformat/formats/__init__.py ================================================ # formats/__init__.py ================================================ FILE: Solutions/9_4/structly/tableformat/formats/csv.py ================================================ # csv.py from ..formatter import TableFormatter class CSVTableFormatter(TableFormatter): def headings(self, headers): print(','.join(headers)) def row(self, rowdata): print(','.join(str(d) for d in rowdata)) ================================================ FILE: Solutions/9_4/structly/tableformat/formats/html.py ================================================ # html.py from ..formatter import TableFormatter class HTMLTableFormatter(TableFormatter): def headings(self, headers): print('', end=' ') for h in headers: print('%s' % h, end=' ') print('') def row(self, rowdata): print('', end=' ') for d in rowdata: print('%s' % d, end=' ') print('') ================================================ FILE: Solutions/9_4/structly/tableformat/formats/text.py ================================================ # text.py from ..formatter import TableFormatter class TextTableFormatter(TableFormatter): def headings(self, headers): print(' '.join('%10s' % h for h in headers)) print(('-'*10 + ' ')*len(headers)) def row(self, rowdata): print(' '.join('%10s' % d for d in rowdata)) ================================================ FILE: Solutions/9_4/structly/tableformat/formats/tsv.py ================================================ # tsv.py from ..formatter import TableFormatter class TSVTableFormatter(TableFormatter): def headings(self, headers): print('\t'.join(headers)) def row(self, rowdata): print('\t'.join(str(d) for d in rowdata)) ================================================ FILE: Solutions/9_4/structly/tableformat/formatter.py ================================================ # formatter.py from abc import ABC, abstractmethod def print_table(records, fields, formatter): if not isinstance(formatter, TableFormatter): raise RuntimeError('Expected a TableFormatter') formatter.headings(fields) for r in records: rowdata = [getattr(r, fieldname) for fieldname in fields] formatter.row(rowdata) class TableFormatter(ABC): _formats = { } @classmethod def __init_subclass__(cls): name = cls.__module__.split('.')[-1] TableFormatter._formats[name] = cls @abstractmethod def headings(self, headers): pass @abstractmethod def row(self, rowdata): pass class ColumnFormatMixin: formats = [] def row(self, rowdata): rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)] super().row(rowdata) class UpperHeadersMixin: def headings(self, headers): super().headings([h.upper() for h in headers]) def create_formatter(name, column_formats=None, upper_headers=False): if name not in TableFormatter._formats: __import__(f'{__package__}.formats.{name}') formatter_cls = TableFormatter._formats.get(name) if not formatter_cls: raise RuntimeError('Unknown format %s' % name) if column_formats: class formatter_cls(ColumnFormatMixin, formatter_cls): formats = column_formats if upper_headers: class formatter_cls(UpperHeadersMixin, formatter_cls): pass return formatter_cls() ================================================ FILE: Solutions/9_4/structly/validate.py ================================================ # validate.py class Validator: def __init__(self, name=None): self.name = name def __set_name__(self, cls, name): self.name = name @classmethod def check(cls, value): return value def __set__(self, instance, value): instance.__dict__[self.name] = self.check(value) # Collect all derived classes into a dict validators = { } @classmethod def __init_subclass__(cls): cls.validators[cls.__name__] = cls class Typed(Validator): expected_type = object @classmethod def check(cls, value): if not isinstance(value, cls.expected_type): raise TypeError(f'expected {cls.expected_type}') return super().check(value) _typed_classes = [ ('Integer', int), ('Float', float), ('String', str) ] globals().update((name, type(name, (Typed,), {'expected_type':ty})) for name, ty in _typed_classes) class Positive(Validator): @classmethod def check(cls, value): if value < 0: raise ValueError('must be >= 0') return super().check(value) class NonEmpty(Validator): @classmethod def check(cls, value): if len(value) == 0: raise ValueError('must be non-empty') return super().check(value) class PositiveInteger(Integer, Positive): pass class PositiveFloat(Float, Positive): pass class NonEmptyString(String, NonEmpty): pass from inspect import signature from functools import wraps def isvalidator(item): return isinstance(item, type) and issubclass(item, Validator) def validated(func): sig = signature(func) # Gather the function annotations annotations = { name:val for name, val in func.__annotations__.items() if isvalidator(val) } # Get the return annotation (if any) retcheck = annotations.pop('return', None) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) # Enforce return check (if any) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper def enforce(**annotations): retcheck = annotations.pop('return_', None) def decorate(func): sig = signature(func) @wraps(func) def wrapper(*args, **kwargs): bound = sig.bind(*args, **kwargs) errors = [] # Enforce argument checks for name, validator in annotations.items(): try: validator.check(bound.arguments[name]) except Exception as e: errors.append(f' {name}: {e}') if errors: raise TypeError('Bad Arguments\n' + '\n'.join(errors)) result = func(*args, **kwargs) if retcheck: try: retcheck.check(result) except Exception as e: raise TypeError(f'Bad return: {e}') from None return result return wrapper return decorate # Examples if __name__ == '__main__': @validated def add(x:Integer, y:Integer) -> Integer: return x + y @validated def div(x:Integer, y:Integer) -> Integer: return x / y @enforce(x=Integer, y=Integer) def sub(x, y): return x - y class Stock: name = NonEmptyString() shares = PositiveInteger() price = PositiveFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price def __repr__(self): return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' @property def cost(self): return self.shares * self.price @validated def sell(self, nshares:PositiveInteger): self.shares -= nshares ================================================ FILE: Solutions/README.md ================================================ # Solutions This directory contains fully worked out solution code for every exercise. The code is written to be runnable from this directory. When taking the course, I would encourage you to try and come up with your own solution first. However, if you're stuck or if everything has become broken, you can come here to get a fresh copy of code to work with.