[
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/#use-with-ide\n.pdm.toml\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n"
  },
  {
    "path": "Data/dowstocks.csv",
    "content": "\"AA\",39.48,\"6/11/2007\",\"9:36am\",-0.18,39.67,39.69,39.45,181800\r\n\"AIG\",71.38,\"6/11/2007\",\"9:36am\",-0.15,71.29,71.60,71.15,195500\r\n\"AXP\",62.58,\"6/11/2007\",\"9:36am\",-0.46,62.79,63.00,62.57,935000\r\n\"BA\",98.31,\"6/11/2007\",\"9:36am\",+0.12,98.25,98.58,98.19,104800\r\n\"C\",53.08,\"6/11/2007\",\"9:36am\",-0.25,53.20,53.25,53.03,360900\r\n\"CAT\",78.29,\"6/11/2007\",\"9:36am\",-0.23,78.32,78.33,78.06,225400\r\n\"DD\",50.75,\"6/11/2007\",\"9:36am\",-0.38,51.13,51.14,50.69,96900\r\n\"DIS\",34.20,\"6/11/2007\",\"9:35am\",0.00,34.28,34.30,34.12,191000\r\n\"GE\",37.23,\"6/11/2007\",\"9:36am\",-0.09,37.07,37.27,37.05,694300\r\n\"GM\",31.44,\"6/11/2007\",\"9:36am\",+0.44,31.00,31.62,30.90,715429\r\n\"HD\",37.67,\"6/11/2007\",\"9:35am\",-0.28,37.78,37.83,37.62,218369\r\n\"HON\",57.12,\"6/11/2007\",\"9:35am\",-0.26,57.25,57.33,57.11,131100\r\n\"HPQ\",45.81,\"6/11/2007\",\"9:36am\",+0.11,45.80,45.85,45.46,303200\r\n\"IBM\",102.86,\"6/11/2007\",\"9:35am\",-0.21,102.87,102.99,102.50,151700\r\n\"INTC\",21.84,\"6/11/2007\",\"9:40am\",+0.01,21.70,21.85,21.69,2927268\r\n\"JNJ\",62.25,\"6/11/2007\",\"9:35am\",+0.12,62.89,62.89,62.15,343500\r\n\"JPM\",50.35,\"6/11/2007\",\"9:36am\",-0.06,50.41,50.49,50.27,351100\r\n\"KO\",51.65,\"6/11/2007\",\"9:35am\",-0.02,51.67,51.73,51.54,3981400\r\n\"MCD\",51.11,\"6/11/2007\",\"9:35am\",-0.30,51.47,51.47,51.00,169100\r\n\"MMM\",85.60,\"6/11/2007\",\"9:35am\",-0.34,85.94,85.98,85.50,190800\r\n\"MO\",70.09,\"6/11/2007\",\"9:36am\",-0.21,70.25,70.30,70.04,471200\r\n\"MRK\",50.21,\"6/11/2007\",\"9:36am\",+0.07,50.30,50.46,50.04,1453300\r\n\"MSFT\",30.08,\"6/11/2007\",\"9:41am\",+0.03,30.05,30.09,29.93,6166010\r\n\"PFE\",26.40,\"6/11/2007\",\"9:36am\",-0.12,26.50,26.50,26.34,835600\r\n\"PG\",62.79,\"6/11/2007\",\"9:35am\",-0.28,62.80,62.87,62.75,256000\r\n\"T\",40.03,\"6/11/2007\",\"9:35am\",-0.23,40.20,40.25,39.89,691400\r\n\"UTX\",69.81,\"6/11/2007\",\"9:36am\",-0.42,69.85,70.20,69.51,153900\r\n\"VZ\",42.92,\"6/11/2007\",\"9:35am\",-0.15,42.95,43.00,42.89,221000\r\n\"WMT\",49.78,\"6/11/2007\",\"9:36am\",-0.30,49.90,50.00,49.76,676200\r\n\"XOM\",82.50,\"6/11/2007\",\"9:36am\",-0.18,82.68,82.84,82.41,481200\r\n\"AA\",39.59,\"6/11/2007\",\"9:40am\",-0.07,39.67,39.69,39.43,252600\r\n\"AIG\",71.46,\"6/11/2007\",\"9:40am\",-0.07,71.29,71.60,71.15,276900\r\n\"AXP\",62.71,\"6/11/2007\",\"9:40am\",-0.33,62.79,63.00,62.42,1002100\r\n\"BA\",98.31,\"6/11/2007\",\"9:40am\",+0.12,98.25,98.58,98.17,149700\r\n\"C\",53.14,\"6/11/2007\",\"9:40am\",-0.19,53.20,53.25,53.02,546500\r\n\"CAT\",78.49,\"6/11/2007\",\"9:40am\",-0.03,78.32,78.49,78.06,262812\r\n\"DD\",50.85,\"6/11/2007\",\"9:40am\",-0.28,51.13,51.14,50.69,155000\r\n\"DIS\",34.36,\"6/11/2007\",\"9:40am\",+0.16,34.28,34.38,34.12,276900\r\n\"GE\",37.30,\"6/11/2007\",\"9:40am\",-0.02,37.07,37.32,37.05,1039900\r\n\"GM\",31.40,\"6/11/2007\",\"9:40am\",+0.40,31.00,31.62,30.90,1074079\r\n\"HD\",37.72,\"6/11/2007\",\"9:40am\",-0.23,37.78,37.83,37.62,321769\r\n\"HON\",57.22,\"6/11/2007\",\"9:40am\",-0.16,57.25,57.33,57.05,150400\r\n\"HPQ\",45.954,\"6/11/2007\",\"9:40am\",+0.254,45.80,45.98,45.46,424600\r\n\"IBM\",102.95,\"6/11/2007\",\"9:40am\",-0.12,102.87,102.99,102.50,229500\r\n\"INTC\",21.85,\"6/11/2007\",\"9:46am\",+0.02,21.70,21.86,21.69,3605793\r\n\"JNJ\",62.42,\"6/11/2007\",\"9:40am\",+0.29,62.89,62.89,62.15,433600\r\n\"JPM\",50.42,\"6/11/2007\",\"9:40am\",+0.01,50.41,50.49,50.27,461400\r\n\"KO\",51.67,\"6/11/2007\",\"9:40am\",0.00,51.67,51.73,51.54,4010650\r\n\"MCD\",51.42,\"6/11/2007\",\"9:40am\",+0.01,51.47,51.47,50.98,245800\r\n\"MMM\",85.45,\"6/11/2007\",\"9:40am\",-0.49,85.94,85.98,85.44,225500\r\n\"MO\",69.95,\"6/11/2007\",\"9:40am\",-0.35,70.25,70.30,69.95,543600\r\n\"MRK\",50.58,\"6/11/2007\",\"9:40am\",+0.44,50.30,50.63,50.04,1586100\r\n\"MSFT\",30.14,\"6/11/2007\",\"9:46am\",+0.09,30.05,30.15,29.93,6758871\r\n\"PFE\",26.46,\"6/11/2007\",\"9:40am\",-0.06,26.50,26.50,26.34,1101900\r\n\"PG\",62.97,\"6/11/2007\",\"9:40am\",-0.10,62.80,63.00,62.75,431246\r\n\"T\",40.19,\"6/11/2007\",\"9:40am\",-0.07,40.20,40.25,39.89,874100\r\n\"UTX\",69.88,\"6/11/2007\",\"9:40am\",-0.35,69.85,70.20,69.51,191800\r\n\"VZ\",43.06,\"6/11/2007\",\"9:40am\",-0.01,42.95,43.07,42.89,322700\r\n\"WMT\",49.72,\"6/11/2007\",\"9:40am\",-0.36,49.90,50.00,49.70,822700\r\n\"XOM\",82.41,\"6/11/2007\",\"9:40am\",-0.27,82.68,82.84,82.35,705500\r\n\"AA\",39.78,\"6/11/2007\",\"9:46am\",+0.12,39.67,39.7946,39.43,347300\r\n\"AIG\",71.41,\"6/11/2007\",\"9:46am\",-0.12,71.29,71.60,71.15,378000\r\n\"AXP\",62.87,\"6/11/2007\",\"9:46am\",-0.17,62.79,63.00,62.42,1033500\r\n\"BA\",98.50,\"6/11/2007\",\"9:46am\",+0.31,98.25,98.58,98.17,195100\r\n\"C\",53.13,\"6/11/2007\",\"9:46am\",-0.20,53.20,53.25,53.02,715514\r\n\"CAT\",78.77,\"6/11/2007\",\"9:46am\",+0.25,78.32,78.8128,78.06,329712\r\n\"DD\",50.81,\"6/11/2007\",\"9:46am\",-0.32,51.13,51.14,50.69,198300\r\n\"DIS\",34.34,\"6/11/2007\",\"9:46am\",+0.14,34.28,34.44,34.12,392500\r\n\"GE\",37.27,\"6/11/2007\",\"9:46am\",-0.05,37.07,37.34,37.05,1311900\r\n\"GM\",31.42,\"6/11/2007\",\"9:46am\",+0.42,31.00,31.62,30.90,1340279\r\n\"HD\",37.75,\"6/11/2007\",\"9:46am\",-0.20,37.78,37.83,37.62,459769\r\n\"HON\",57.20,\"6/11/2007\",\"9:46am\",-0.18,57.25,57.33,57.05,185700\r\n\"HPQ\",46.14,\"6/11/2007\",\"9:46am\",+0.44,45.80,46.15,45.46,797800\r\n\"IBM\",103.39,\"6/11/2007\",\"9:46am\",+0.32,102.87,103.47,102.50,413800\r\n\"INTC\",21.85,\"6/11/2007\",\"9:50am\",+0.02,21.70,21.93,21.69,4380516\r\n\"JNJ\",62.52,\"6/11/2007\",\"9:46am\",+0.39,62.89,62.89,62.15,548200\r\n\"JPM\",50.48,\"6/11/2007\",\"9:46am\",+0.07,50.41,50.55,50.27,657600\r\n\"KO\",51.67,\"6/11/2007\",\"9:45am\",0.00,51.67,51.77,51.54,4092550\r\n\"MCD\",51.36,\"6/11/2007\",\"9:46am\",-0.05,51.47,51.47,50.98,343719\r\n\"MMM\",85.48,\"6/11/2007\",\"9:46am\",-0.46,85.94,85.98,85.41,270900\r\n\"MO\",70.00,\"6/11/2007\",\"9:46am\",-0.30,70.25,70.30,69.88,723100\r\n\"MRK\",50.50,\"6/11/2007\",\"9:46am\",+0.36,50.30,50.63,50.04,1689100\r\n\"MSFT\",30.175,\"6/11/2007\",\"9:50am\",+0.125,30.05,30.20,29.93,7234309\r\n\"PFE\",26.48,\"6/11/2007\",\"9:45am\",-0.04,26.50,26.52,26.34,1363300\r\n\"PG\",62.89,\"6/11/2007\",\"9:46am\",-0.18,62.80,63.00,62.75,553446\r\n\"T\",40.12,\"6/11/2007\",\"9:46am\",-0.14,40.20,40.25,39.89,1073400\r\n\"UTX\",69.92,\"6/11/2007\",\"9:46am\",-0.31,69.85,70.20,69.51,244600\r\n\"VZ\",43.09,\"6/11/2007\",\"9:46am\",+0.02,42.95,43.17,42.89,515110\r\n\"WMT\",49.78,\"6/11/2007\",\"9:46am\",-0.30,49.90,50.00,49.65,949000\r\n\"XOM\",82.60,\"6/11/2007\",\"9:46am\",-0.08,82.68,82.84,82.35,934200\r\n\"AA\",40.15,\"6/11/2007\",\"9:51am\",+0.49,39.67,40.15,39.43,510630\r\n\"AIG\",71.50,\"6/11/2007\",\"9:50am\",-0.03,71.29,71.60,71.15,441900\r\n\"AXP\",62.99,\"6/11/2007\",\"9:50am\",-0.05,62.79,63.07,62.42,1067820\r\n\"BA\",98.73,\"6/11/2007\",\"9:50am\",+0.54,98.25,98.79,98.17,233900\r\n\"C\",53.15,\"6/11/2007\",\"9:51am\",-0.18,53.20,53.25,53.02,885114\r\n\"CAT\",78.88,\"6/11/2007\",\"9:50am\",+0.36,78.32,78.99,78.06,386652\r\n\"DD\",51.13,\"6/11/2007\",\"9:50am\",0.00,51.13,51.14,50.69,274100\r\n\"DIS\",34.42,\"6/11/2007\",\"9:51am\",+0.22,34.28,34.44,34.12,448200\r\n\"GE\",37.35,\"6/11/2007\",\"9:50am\",+0.03,37.07,37.37,37.05,1541100\r\n\"GM\",31.28,\"6/11/2007\",\"9:50am\",+0.28,31.00,31.62,30.90,1715679\r\n\"HD\",37.74,\"6/11/2007\",\"9:51am\",-0.21,37.78,37.83,37.62,553869\r\n\"HON\",57.30,\"6/11/2007\",\"9:51am\",-0.08,57.25,57.40,57.05,218000\r\n\"HPQ\",46.26,\"6/11/2007\",\"9:51am\",+0.56,45.80,46.26,45.46,1497780\r\n\"IBM\",103.3781,\"6/11/2007\",\"9:50am\",+0.3081,102.87,103.47,102.50,485800\r\n\"INTC\",21.83,\"6/11/2007\",\"9:55am\",0.00,21.70,21.93,21.69,4802496\r\n\"JNJ\",62.68,\"6/11/2007\",\"9:51am\",+0.55,62.89,62.89,62.15,756500\r\n\"JPM\",50.50,\"6/11/2007\",\"9:50am\",+0.09,50.41,50.55,50.27,851700\r\n\"KO\",51.79,\"6/11/2007\",\"9:50am\",+0.12,51.67,51.796,51.54,4175960\r\n\"MCD\",51.39,\"6/11/2007\",\"9:50am\",-0.02,51.47,51.47,50.98,407819\r\n\"MMM\",85.74,\"6/11/2007\",\"9:50am\",-0.20,85.94,85.98,85.41,311100\r\n\"MO\",70.08,\"6/11/2007\",\"9:51am\",-0.22,70.25,70.30,69.88,840100\r\n\"MRK\",50.45,\"6/11/2007\",\"9:50am\",+0.31,50.30,50.63,50.04,1830600\r\n\"MSFT\",30.22,\"6/11/2007\",\"9:56am\",+0.17,30.05,30.24,29.93,7724578\r\n\"PFE\",26.45,\"6/11/2007\",\"9:51am\",-0.07,26.50,26.52,26.34,1805950\r\n\"PG\",63.10,\"6/11/2007\",\"9:50am\",+0.03,62.80,63.10,62.75,715146\r\n\"T\",40.16,\"6/11/2007\",\"9:50am\",-0.10,40.20,40.25,39.89,1199500\r\n\"UTX\",70.06,\"6/11/2007\",\"9:50am\",-0.17,69.85,70.20,69.51,270900\r\n\"VZ\",43.16,\"6/11/2007\",\"9:50am\",+0.09,42.95,43.18,42.89,614810\r\n\"WMT\",49.87,\"6/11/2007\",\"9:51am\",-0.21,49.90,50.00,49.65,1181700\r\n\"XOM\",82.77,\"6/11/2007\",\"9:50am\",+0.09,82.68,82.86,82.35,1121100\r\n\"AA\",39.97,\"6/11/2007\",\"9:55am\",+0.31,39.67,40.18,39.43,598130\r\n\"AIG\",71.43,\"6/11/2007\",\"9:55am\",-0.10,71.29,71.60,71.15,487600\r\n\"AXP\",62.89,\"6/11/2007\",\"9:55am\",-0.15,62.79,63.07,62.42,1084520\r\n\"BA\",98.66,\"6/11/2007\",\"9:56am\",+0.47,98.25,98.79,98.17,265800\r\n\"C\",53.14,\"6/11/2007\",\"9:56am\",-0.19,53.20,53.25,53.02,954314\r\n\"CAT\",78.87,\"6/11/2007\",\"9:55am\",+0.35,78.32,78.99,78.06,425952\r\n\"DD\",51.18,\"6/11/2007\",\"9:55am\",+0.05,51.13,51.21,50.69,341500\r\n\"DIS\",34.39,\"6/11/2007\",\"9:55am\",+0.19,34.28,34.44,34.12,523200\r\n\"GE\",37.39,\"6/11/2007\",\"9:56am\",+0.07,37.07,37.40,37.05,1786900\r\n\"GM\",31.31,\"6/11/2007\",\"9:55am\",+0.31,31.00,31.62,30.90,2399379\r\n\"HD\",37.75,\"6/11/2007\",\"9:55am\",-0.20,37.78,37.83,37.62,622969\r\n\"HON\",57.29,\"6/11/2007\",\"9:56am\",-0.09,57.25,57.40,57.05,245000\r\n\"HPQ\",46.25,\"6/11/2007\",\"9:56am\",+0.55,45.80,46.27,45.46,1615680\r\n\"IBM\",103.60,\"6/11/2007\",\"9:56am\",+0.53,102.87,103.62,102.50,569900\r\n\"INTC\",21.85,\"6/11/2007\",\"10:00am\",+0.02,21.70,21.95,21.69,5440945\r\n\"JNJ\",62.74,\"6/11/2007\",\"9:56am\",+0.61,62.89,62.89,62.15,887700\r\n\"JPM\",50.48,\"6/11/2007\",\"9:56am\",+0.07,50.41,50.55,50.27,924300\r\n\"KO\",51.63,\"6/11/2007\",\"9:56am\",-0.04,51.67,51.82,51.54,4247360\r\n\"MCD\",51.40,\"6/11/2007\",\"9:56am\",-0.01,51.47,51.47,50.98,448219\r\n\"MMM\",85.69,\"6/11/2007\",\"9:55am\",-0.25,85.94,85.98,85.41,323200\r\n\"MO\",70.10,\"6/11/2007\",\"9:56am\",-0.20,70.25,70.30,69.88,932900\r\n\"MRK\",50.38,\"6/11/2007\",\"9:55am\",+0.24,50.30,50.63,50.04,1943600\r\n\"MSFT\",30.20,\"6/11/2007\",\"10:01am\",+0.15,30.05,30.24,29.93,8053754\r\n\"PFE\",26.48,\"6/11/2007\",\"9:55am\",-0.04,26.50,26.53,26.34,2029150\r\n\"PG\",63.07,\"6/11/2007\",\"9:56am\",0.00,62.80,63.10,62.75,786646\r\n\"T\",40.08,\"6/11/2007\",\"9:56am\",-0.18,40.20,40.25,39.89,1364200\r\n\"UTX\",70.06,\"6/11/2007\",\"9:56am\",-0.17,69.85,70.20,69.51,289300\r\n\"VZ\",43.17,\"6/11/2007\",\"9:56am\",+0.10,42.95,43.19,42.89,683810\r\n\"WMT\",49.88,\"6/11/2007\",\"9:56am\",-0.20,49.90,50.00,49.65,1321100\r\n\"XOM\",82.68,\"6/11/2007\",\"9:56am\",0.00,82.68,82.86,82.35,1235100\r\n\"AA\",39.94,\"6/11/2007\",\"10:00am\",+0.28,39.67,40.18,39.43,660280\r\n\"AIG\",71.36,\"6/11/2007\",\"10:00am\",-0.17,71.29,71.60,71.15,575042\r\n\"AXP\",62.83,\"6/11/2007\",\"10:00am\",-0.21,62.79,63.07,62.42,1110520\r\n\"BA\",98.62,\"6/11/2007\",\"10:01am\",+0.43,98.25,98.79,98.17,285700\r\n\"C\",53.06,\"6/11/2007\",\"10:01am\",-0.27,53.20,53.25,53.02,1030199\r\n\"CAT\",78.66,\"6/11/2007\",\"10:00am\",+0.14,78.32,78.99,78.06,452352\r\n\"DD\",51.09,\"6/11/2007\",\"10:01am\",-0.04,51.13,51.21,50.69,402900\r\n\"DIS\",34.33,\"6/11/2007\",\"10:01am\",+0.13,34.28,34.44,34.12,740300\r\n\"GE\",37.41,\"6/11/2007\",\"10:01am\",+0.09,37.07,37.41,37.05,2106900\r\n\"GM\",31.29,\"6/11/2007\",\"10:01am\",+0.29,31.00,31.62,30.90,2570679\r\n\"HD\",37.76,\"6/11/2007\",\"10:00am\",-0.19,37.78,37.83,37.62,683769\r\n\"HON\",57.335,\"6/11/2007\",\"10:01am\",-0.045,57.25,57.40,57.05,289100\r\n\"HPQ\",46.25,\"6/11/2007\",\"10:01am\",+0.55,45.80,46.29,45.46,1752080\r\n\"IBM\",103.51,\"6/11/2007\",\"10:01am\",+0.44,102.87,103.63,102.50,607300\r\n\"INTC\",21.85,\"6/11/2007\",\"10:05am\",+0.02,21.70,21.95,21.69,5955030\r\n\"JNJ\",62.75,\"6/11/2007\",\"10:01am\",+0.62,62.89,62.89,62.15,1031900\r\n\"JPM\",50.45,\"6/11/2007\",\"10:00am\",+0.04,50.41,50.55,50.27,1010000\r\n\"KO\",51.55,\"6/11/2007\",\"10:00am\",-0.12,51.67,51.82,51.54,4283460\r\n\"MCD\",51.34,\"6/11/2007\",\"10:01am\",-0.07,51.47,51.47,50.98,481919\r\n\"MMM\",85.75,\"6/11/2007\",\"10:00am\",-0.19,85.94,85.98,85.41,332600\r\n\"MO\",70.07,\"6/11/2007\",\"10:00am\",-0.23,70.25,70.30,69.88,1007630\r\n\"MRK\",50.32,\"6/11/2007\",\"10:01am\",+0.18,50.30,50.64,50.04,2165200\r\n\"MSFT\",30.21,\"6/11/2007\",\"10:05am\",+0.16,30.05,30.25,29.93,8448925\r\n\"PFE\",26.45,\"6/11/2007\",\"10:01am\",-0.07,26.50,26.53,26.34,2252150\r\n\"PG\",63.02,\"6/11/2007\",\"10:01am\",-0.05,62.80,63.10,62.75,867746\r\n\"T\",40.05,\"6/11/2007\",\"10:01am\",-0.21,40.20,40.25,39.89,1496200\r\n\"UTX\",70.00,\"6/11/2007\",\"10:00am\",-0.23,69.85,70.20,69.51,308500\r\n\"VZ\",43.11,\"6/11/2007\",\"10:00am\",+0.04,42.95,43.19,42.89,804110\r\n\"WMT\",49.78,\"6/11/2007\",\"10:01am\",-0.30,49.90,50.00,49.65,1378500\r\n\"XOM\",82.71,\"6/11/2007\",\"10:00am\",+0.03,82.68,82.86,82.35,1379100\r\n\"AA\",39.92,\"6/11/2007\",\"10:05am\",+0.26,39.67,40.18,39.43,693080\r\n\"AIG\",71.29,\"6/11/2007\",\"10:05am\",-0.24,71.29,71.60,71.15,630742\r\n\"AXP\",62.74,\"6/11/2007\",\"10:05am\",-0.30,62.79,63.07,62.42,1149120\r\n\"BA\",98.47,\"6/11/2007\",\"10:06am\",+0.28,98.25,98.79,98.17,306700\r\n\"C\",52.97,\"6/11/2007\",\"10:06am\",-0.36,53.20,53.25,52.92,1191699\r\n\"CAT\",78.56,\"6/11/2007\",\"10:06am\",+0.04,78.32,78.99,78.06,512752\r\n\"DD\",51.068,\"6/11/2007\",\"10:05am\",-0.062,51.13,51.21,50.69,490000\r\n\"DIS\",34.29,\"6/11/2007\",\"10:05am\",+0.09,34.28,34.44,34.12,790550\r\n\"GE\",37.36,\"6/11/2007\",\"10:06am\",+0.04,37.07,37.41,37.05,2388519\r\n\"GM\",31.35,\"6/11/2007\",\"10:05am\",+0.35,31.00,31.62,30.90,2770879\r\n\"HD\",37.75,\"6/11/2007\",\"10:06am\",-0.20,37.78,37.83,37.62,909569\r\n\"HON\",57.22,\"6/11/2007\",\"10:06am\",-0.16,57.25,57.40,57.05,307300\r\n\"HPQ\",46.22,\"6/11/2007\",\"10:05am\",+0.52,45.80,46.29,45.46,1904480\r\n\"IBM\",103.57,\"6/11/2007\",\"10:05am\",+0.50,102.87,103.63,102.50,666400\r\n\"INTC\",21.83,\"6/11/2007\",\"10:10am\",0.00,21.70,21.95,21.69,6319637\r\n\"JNJ\",62.70,\"6/11/2007\",\"10:06am\",+0.57,62.89,62.89,62.15,1138900\r\n\"JPM\",50.35,\"6/11/2007\",\"10:05am\",-0.06,50.41,50.55,50.27,1112900\r\n\"KO\",51.57,\"6/11/2007\",\"10:06am\",-0.10,51.67,51.82,51.53,4320060\r\n\"MCD\",51.31,\"6/11/2007\",\"10:06am\",-0.10,51.47,51.47,50.98,533219\r\n\"MMM\",85.76,\"6/11/2007\",\"10:05am\",-0.18,85.94,85.98,85.41,384000\r\n\"MO\",69.99,\"6/11/2007\",\"10:05am\",-0.31,70.25,70.30,69.88,1191130\r\n\"MRK\",50.35,\"6/11/2007\",\"10:06am\",+0.21,50.30,50.64,50.04,2250500\r\n\"MSFT\",30.18,\"6/11/2007\",\"10:11am\",+0.13,30.05,30.25,29.93,9049059\r\n\"PFE\",26.41,\"6/11/2007\",\"10:05am\",-0.11,26.50,26.53,26.34,2353550\r\n\"PG\",63.03,\"6/11/2007\",\"10:06am\",-0.04,62.80,63.10,62.75,920246\r\n\"T\",40.04,\"6/11/2007\",\"10:06am\",-0.22,40.20,40.25,39.89,1644100\r\n\"UTX\",69.95,\"6/11/2007\",\"10:05am\",-0.28,69.85,70.20,69.51,332000\r\n\"VZ\",43.04,\"6/11/2007\",\"10:05am\",-0.03,42.95,43.19,42.89,886910\r\n\"WMT\",49.70,\"6/11/2007\",\"10:05am\",-0.38,49.90,50.00,49.65,1450500\r\n\"XOM\",82.8681,\"6/11/2007\",\"10:05am\",+0.1881,82.68,82.89,82.35,1544600\r\n\"AA\",39.91,\"6/11/2007\",\"10:11am\",+0.25,39.67,40.18,39.43,755880\r\n\"AIG\",71.29,\"6/11/2007\",\"10:11am\",-0.24,71.29,71.60,71.15,706842\r\n\"AXP\",62.79,\"6/11/2007\",\"10:10am\",-0.25,62.79,63.07,62.42,1183320\r\n\"BA\",98.45,\"6/11/2007\",\"10:11am\",+0.26,98.25,98.79,98.17,344500\r\n\"C\",52.926,\"6/11/2007\",\"10:11am\",-0.404,53.20,53.25,52.91,1347199\r\n\"CAT\",78.58,\"6/11/2007\",\"10:11am\",+0.06,78.32,78.99,78.06,559652\r\n\"DD\",50.999,\"6/11/2007\",\"10:11am\",-0.131,51.13,51.21,50.69,542900\r\n\"DIS\",34.29,\"6/11/2007\",\"10:11am\",+0.09,34.28,34.44,34.12,836650\r\n\"GE\",37.38,\"6/11/2007\",\"10:11am\",+0.06,37.07,37.41,37.05,2643919\r\n\"GM\",31.30,\"6/11/2007\",\"10:11am\",+0.30,31.00,31.62,30.90,2943679\r\n\"HD\",37.75,\"6/11/2007\",\"10:11am\",-0.20,37.78,37.83,37.62,1015369\r\n\"HON\",57.23,\"6/11/2007\",\"10:11am\",-0.15,57.25,57.40,57.05,332200\r\n\"HPQ\",46.18,\"6/11/2007\",\"10:11am\",+0.48,45.80,46.29,45.46,2006780\r\n\"IBM\",103.35,\"6/11/2007\",\"10:11am\",+0.28,102.87,103.63,102.50,734000\r\n\"INTC\",21.83,\"6/11/2007\",\"10:16am\",0.00,21.70,21.95,21.69,6790042\r\n\"JNJ\",62.71,\"6/11/2007\",\"10:11am\",+0.58,62.89,62.89,62.15,1274000\r\n\"JPM\",50.28,\"6/11/2007\",\"10:11am\",-0.13,50.41,50.55,50.26,1187600\r\n\"KO\",51.56,\"6/11/2007\",\"10:11am\",-0.11,51.67,51.82,51.53,4370560\r\n\"MCD\",51.25,\"6/11/2007\",\"10:11am\",-0.16,51.47,51.47,50.98,587519\r\n\"MMM\",85.78,\"6/11/2007\",\"10:10am\",-0.16,85.94,85.98,85.41,414300\r\n\"MO\",69.95,\"6/11/2007\",\"10:11am\",-0.35,70.25,70.30,69.88,1273530\r\n\"MRK\",50.29,\"6/11/2007\",\"10:11am\",+0.15,50.30,50.64,50.04,2332500\r\n\"MSFT\",30.14,\"6/11/2007\",\"10:16am\",+0.09,30.05,30.25,29.93,9998935\r\n\"PFE\",26.449,\"6/11/2007\",\"10:10am\",-0.071,26.50,26.53,26.34,2551150\r\n\"PG\",63.07,\"6/11/2007\",\"10:10am\",0.00,62.80,63.10,62.75,1010846\r\n\"T\",40.04,\"6/11/2007\",\"10:10am\",-0.22,40.20,40.25,39.89,1922900\r\n\"UTX\",69.91,\"6/11/2007\",\"10:11am\",-0.32,69.85,70.20,69.51,354300\r\n\"VZ\",43.10,\"6/11/2007\",\"10:10am\",+0.03,42.95,43.19,42.89,998110\r\n\"WMT\",49.73,\"6/11/2007\",\"10:11am\",-0.35,49.90,50.00,49.65,1577400\r\n\"XOM\",83.02,\"6/11/2007\",\"10:11am\",+0.34,82.68,83.11,82.35,1789600\r\n\"AA\",39.76,\"6/11/2007\",\"10:15am\",+0.10,39.67,40.18,39.43,804580\r\n\"AIG\",71.29,\"6/11/2007\",\"10:16am\",-0.24,71.29,71.60,71.15,781442\r\n\"AXP\",62.69,\"6/11/2007\",\"10:16am\",-0.35,62.79,63.07,62.42,1212120\r\n\"BA\",98.29,\"6/11/2007\",\"10:16am\",+0.10,98.25,98.79,98.17,373200\r\n\"C\",52.91,\"6/11/2007\",\"10:16am\",-0.42,53.20,53.25,52.84,1455899\r\n\"CAT\",78.51,\"6/11/2007\",\"10:16am\",-0.01,78.32,78.99,78.06,607452\r\n\"DD\",51.02,\"6/11/2007\",\"10:16am\",-0.11,51.13,51.21,50.69,576600\r\n\"DIS\",34.25,\"6/11/2007\",\"10:16am\",+0.05,34.28,34.44,34.12,880150\r\n\"GE\",37.32,\"6/11/2007\",\"10:16am\",0.00,37.07,37.41,37.05,2784119\r\n\"GM\",31.28,\"6/11/2007\",\"10:16am\",+0.28,31.00,31.62,30.90,3270779\r\n\"HD\",37.76,\"6/11/2007\",\"10:16am\",-0.19,37.78,37.83,37.62,1141469\r\n\"HON\",57.11,\"6/11/2007\",\"10:16am\",-0.27,57.25,57.40,57.05,379600\r\n\"HPQ\",46.06,\"6/11/2007\",\"10:16am\",+0.36,45.80,46.29,45.46,2073280\r\n\"IBM\",103.17,\"6/11/2007\",\"10:16am\",+0.10,102.87,103.63,102.50,798600\r\n\"INTC\",21.84,\"6/11/2007\",\"10:20am\",+0.01,21.70,21.95,21.69,7235829\r\n\"JNJ\",62.66,\"6/11/2007\",\"10:16am\",+0.53,62.89,62.89,62.15,1412600\r\n\"JPM\",50.23,\"6/11/2007\",\"10:16am\",-0.18,50.41,50.55,50.20,1304200\r\n\"KO\",51.52,\"6/11/2007\",\"10:16am\",-0.15,51.67,51.82,51.50,4471160\r\n\"MCD\",51.17,\"6/11/2007\",\"10:15am\",-0.24,51.47,51.47,50.98,640119\r\n\"MMM\",85.69,\"6/11/2007\",\"10:15am\",-0.25,85.94,85.98,85.41,436200\r\n\"MO\",69.90,\"6/11/2007\",\"10:15am\",-0.40,70.25,70.30,69.88,1350130\r\n\"MRK\",50.375,\"6/11/2007\",\"10:16am\",+0.235,50.30,50.64,50.04,2501800\r\n\"MSFT\",30.105,\"6/11/2007\",\"10:21am\",+0.055,30.05,30.25,29.93,11615862\r\n\"PFE\",26.43,\"6/11/2007\",\"10:16am\",-0.09,26.50,26.53,26.34,2699150\r\n\"PG\",63.03,\"6/11/2007\",\"10:15am\",-0.04,62.80,63.10,62.75,1084346\r\n\"T\",40.04,\"6/11/2007\",\"10:16am\",-0.22,40.20,40.25,39.89,2049000\r\n\"UTX\",69.80,\"6/11/2007\",\"10:16am\",-0.43,69.85,70.20,69.51,390200\r\n\"VZ\",43.12,\"6/11/2007\",\"10:16am\",+0.05,42.95,43.19,42.89,1540810\r\n\"WMT\",49.71,\"6/11/2007\",\"10:16am\",-0.37,49.90,50.00,49.65,1700900\r\n\"XOM\",82.79,\"6/11/2007\",\"10:16am\",+0.11,82.68,83.11,82.35,1945300\r\n\"AA\",39.7264,\"6/11/2007\",\"10:21am\",+0.0664,39.67,40.18,39.43,860780\r\n\"AIG\",71.28,\"6/11/2007\",\"10:21am\",-0.25,71.29,71.60,71.15,826742\r\n\"AXP\",62.775,\"6/11/2007\",\"10:21am\",-0.265,62.79,63.07,62.42,1262720\r\n\"BA\",98.17,\"6/11/2007\",\"10:21am\",-0.02,98.25,98.79,98.15,420500\r\n\"C\",52.89,\"6/11/2007\",\"10:20am\",-0.44,53.20,53.25,52.84,1554299\r\n\"CAT\",78.46,\"6/11/2007\",\"10:21am\",-0.06,78.32,78.99,78.06,645152\r\n\"DD\",51.02,\"6/11/2007\",\"10:20am\",-0.11,51.13,51.21,50.69,616000\r\n\"DIS\",34.29,\"6/11/2007\",\"10:21am\",+0.09,34.28,34.44,34.12,961150\r\n\"GE\",37.31,\"6/11/2007\",\"10:21am\",-0.01,37.07,37.41,37.05,2994719\r\n\"GM\",31.16,\"6/11/2007\",\"10:21am\",+0.16,31.00,31.62,30.90,3450979\r\n\"HD\",37.75,\"6/11/2007\",\"10:20am\",-0.20,37.78,37.83,37.62,1239969\r\n\"HON\",57.14,\"6/11/2007\",\"10:21am\",-0.24,57.25,57.40,57.05,427800\r\n\"HPQ\",46.08,\"6/11/2007\",\"10:21am\",+0.38,45.80,46.29,45.46,2169080\r\n\"IBM\",103.24,\"6/11/2007\",\"10:21am\",+0.17,102.87,103.63,102.50,1110200\r\n\"INTC\",21.85,\"6/11/2007\",\"10:26am\",+0.02,21.70,21.95,21.69,7668158\r\n\"JNJ\",62.65,\"6/11/2007\",\"10:21am\",+0.52,62.89,62.89,62.15,1466978\r\n\"JPM\",50.14,\"6/11/2007\",\"10:21am\",-0.27,50.41,50.55,50.13,1391100\r\n\"KO\",51.44,\"6/11/2007\",\"10:20am\",-0.23,51.67,51.82,51.44,4528060\r\n\"MCD\",51.19,\"6/11/2007\",\"10:20am\",-0.22,51.47,51.47,50.98,694019\r\n\"MMM\",85.58,\"6/11/2007\",\"10:20am\",-0.36,85.94,85.98,85.41,455600\r\n\"MO\",69.86,\"6/11/2007\",\"10:21am\",-0.44,70.25,70.30,69.76,1542230\r\n\"MRK\",50.34,\"6/11/2007\",\"10:21am\",+0.20,50.30,50.64,50.04,2582400\r\n\"MSFT\",30.10,\"6/11/2007\",\"10:26am\",+0.05,30.05,30.25,29.93,12004109\r\n\"PFE\",26.43,\"6/11/2007\",\"10:21am\",-0.09,26.50,26.53,26.34,2986570\r\n\"PG\",63.06,\"6/11/2007\",\"10:21am\",-0.01,62.80,63.10,62.75,1165646\r\n\"T\",40.04,\"6/11/2007\",\"10:20am\",-0.22,40.20,40.25,39.89,2229600\r\n\"UTX\",69.66,\"6/11/2007\",\"10:21am\",-0.57,69.85,70.20,69.51,420400\r\n\"VZ\",43.12,\"6/11/2007\",\"10:21am\",+0.05,42.95,43.19,42.89,1615410\r\n\"WMT\",49.65,\"6/11/2007\",\"10:21am\",-0.43,49.90,50.00,49.65,1851200\r\n\"XOM\",82.84,\"6/11/2007\",\"10:21am\",+0.16,82.68,83.11,82.35,2132200\r\n\"AA\",39.63,\"6/11/2007\",\"10:26am\",-0.03,39.67,40.18,39.43,899080\r\n\"AIG\",71.30,\"6/11/2007\",\"10:26am\",-0.23,71.29,71.60,71.15,896542\r\n\"AXP\",62.80,\"6/11/2007\",\"10:26am\",-0.24,62.79,63.07,62.42,1296620\r\n\"BA\",98.04,\"6/11/2007\",\"10:26am\",-0.15,98.25,98.79,97.96,466900\r\n\"C\",52.91,\"6/11/2007\",\"10:26am\",-0.42,53.20,53.25,52.81,1672699\r\n\"CAT\",78.37,\"6/11/2007\",\"10:26am\",-0.15,78.32,78.99,78.06,704152\r\n\"DD\",50.94,\"6/11/2007\",\"10:25am\",-0.19,51.13,51.21,50.69,643200\r\n\"DIS\",34.24,\"6/11/2007\",\"10:26am\",+0.04,34.28,34.44,34.12,1025550\r\n\"GE\",37.26,\"6/11/2007\",\"10:26am\",-0.06,37.07,37.41,37.05,3290619\r\n\"GM\",31.22,\"6/11/2007\",\"10:26am\",+0.22,31.00,31.62,30.90,4096679\r\n\"HD\",37.75,\"6/11/2007\",\"10:26am\",-0.20,37.78,37.83,37.62,1559369\r\n\"HON\",57.10,\"6/11/2007\",\"10:26am\",-0.28,57.25,57.40,57.03,450900\r\n\"HPQ\",46.10,\"6/11/2007\",\"10:26am\",+0.40,45.80,46.29,45.46,2260680\r\n\"IBM\",103.20,\"6/11/2007\",\"10:26am\",+0.13,102.87,103.63,102.50,1147300\r\n\"INTC\",21.84,\"6/11/2007\",\"10:31am\",+0.01,21.70,21.95,21.69,8125254\r\n\"JNJ\",62.55,\"6/11/2007\",\"10:26am\",+0.42,62.89,62.89,62.15,1616778\r\n\"JPM\",50.14,\"6/11/2007\",\"10:26am\",-0.27,50.41,50.55,50.05,1498700\r\n\"KO\",51.39,\"6/11/2007\",\"10:26am\",-0.28,51.67,51.82,51.32,4607560\r\n\"MCD\",51.17,\"6/11/2007\",\"10:25am\",-0.24,51.47,51.47,50.98,740919\r\n\"MMM\",85.51,\"6/11/2007\",\"10:26am\",-0.43,85.94,85.98,85.41,478800\r\n\"MO\",69.85,\"6/11/2007\",\"10:26am\",-0.45,70.25,70.30,69.76,1670405\r\n\"MRK\",50.32,\"6/11/2007\",\"10:25am\",+0.18,50.30,50.64,50.04,2654700\r\n\"MSFT\",30.09,\"6/11/2007\",\"10:31am\",+0.04,30.05,30.25,29.93,12221463\r\n\"PFE\",26.39,\"6/11/2007\",\"10:26am\",-0.13,26.50,26.53,26.34,3224570\r\n\"PG\",63.04,\"6/11/2007\",\"10:25am\",-0.03,62.80,63.10,62.75,1243746\r\n\"T\",40.001,\"6/11/2007\",\"10:26am\",-0.259,40.20,40.25,39.89,2441800\r\n\"UTX\",69.63,\"6/11/2007\",\"10:26am\",-0.60,69.85,70.20,69.51,446700\r\n\"VZ\",43.12,\"6/11/2007\",\"10:26am\",+0.05,42.95,43.19,42.89,1681910\r\n\"WMT\",49.595,\"6/11/2007\",\"10:26am\",-0.485,49.90,50.00,49.57,1951400\r\n\"XOM\",82.79,\"6/11/2007\",\"10:26am\",+0.11,82.68,83.11,82.35,2330500\r\n\"AA\",39.555,\"6/11/2007\",\"10:31am\",-0.105,39.67,40.18,39.43,944980\r\n\"AIG\",71.32,\"6/11/2007\",\"10:30am\",-0.21,71.29,71.60,71.15,953042\r\n\"AXP\",62.79,\"6/11/2007\",\"10:31am\",-0.25,62.79,63.07,62.42,1329020\r\n\"BA\",97.85,\"6/11/2007\",\"10:31am\",-0.34,98.25,98.79,97.74,531200\r\n\"C\",52.92,\"6/11/2007\",\"10:31am\",-0.41,53.20,53.25,52.81,1746999\r\n\"CAT\",78.43,\"6/11/2007\",\"10:30am\",-0.09,78.32,78.99,78.06,751652\r\n\"DD\",50.88,\"6/11/2007\",\"10:30am\",-0.25,51.13,51.21,50.69,698200\r\n\"DIS\",34.21,\"6/11/2007\",\"10:30am\",+0.01,34.28,34.44,34.12,1097150\r\n\"GE\",37.28,\"6/11/2007\",\"10:31am\",-0.04,37.07,37.41,37.05,3653019\r\n\"GM\",31.15,\"6/11/2007\",\"10:31am\",+0.15,31.00,31.62,30.90,4256179\r\n\"HD\",37.75,\"6/11/2007\",\"10:31am\",-0.20,37.78,37.83,37.62,1712669\r\n\"HON\",57.09,\"6/11/2007\",\"10:30am\",-0.29,57.25,57.40,57.03,481100\r\n\"HPQ\",46.08,\"6/11/2007\",\"10:30am\",+0.38,45.80,46.29,45.46,2341480\r\n\"IBM\",103.20,\"6/11/2007\",\"10:30am\",+0.13,102.87,103.63,102.50,1182000\r\n\"INTC\",21.85,\"6/11/2007\",\"10:36am\",+0.02,21.70,21.95,21.69,8728776\r\n\"JNJ\",62.59,\"6/11/2007\",\"10:31am\",+0.46,62.89,62.89,62.15,1771778\r\n\"JPM\",50.13,\"6/11/2007\",\"10:30am\",-0.28,50.41,50.55,50.05,1552000\r\n\"KO\",51.34,\"6/11/2007\",\"10:31am\",-0.33,51.67,51.82,51.32,4669560\r\n\"MCD\",51.20,\"6/11/2007\",\"10:30am\",-0.21,51.47,51.47,50.98,779219\r\n\"MMM\",85.54,\"6/11/2007\",\"10:30am\",-0.40,85.94,85.98,85.41,502100\r\n\"MO\",69.89,\"6/11/2007\",\"10:31am\",-0.41,70.25,70.30,69.76,1759605\r\n\"MRK\",50.35,\"6/11/2007\",\"10:30am\",+0.21,50.30,50.64,50.04,2706300\r\n\"MSFT\",30.11,\"6/11/2007\",\"10:36am\",+0.06,30.05,30.25,29.93,12723552\r\n\"PFE\",26.38,\"6/11/2007\",\"10:30am\",-0.14,26.50,26.53,26.34,3420170\r\n\"PG\",63.07,\"6/11/2007\",\"10:31am\",0.00,62.80,63.10,62.75,1285746\r\n\"T\",40.05,\"6/11/2007\",\"10:30am\",-0.21,40.20,40.25,39.89,2609700\r\n\"UTX\",69.60,\"6/11/2007\",\"10:31am\",-0.63,69.85,70.20,69.51,500000\r\n\"VZ\",43.15,\"6/11/2007\",\"10:30am\",+0.08,42.95,43.19,42.89,1759910\r\n\"WMT\",49.61,\"6/11/2007\",\"10:31am\",-0.47,49.90,50.00,49.57,2038300\r\n\"XOM\",82.69,\"6/11/2007\",\"10:31am\",+0.01,82.68,83.11,82.35,2496800\r\n\"AA\",39.56,\"6/11/2007\",\"10:36am\",-0.10,39.67,40.18,39.43,999880\r\n\"AIG\",71.37,\"6/11/2007\",\"10:36am\",-0.16,71.29,71.60,71.15,991742\r\n\"AXP\",62.89,\"6/11/2007\",\"10:36am\",-0.15,62.79,63.07,62.42,1356220\r\n\"BA\",98.00,\"6/11/2007\",\"10:36am\",-0.19,98.25,98.79,97.74,561900\r\n\"C\",52.97,\"6/11/2007\",\"10:36am\",-0.36,53.20,53.25,52.81,1864099\r\n\"CAT\",78.53,\"6/11/2007\",\"10:36am\",+0.01,78.32,78.99,78.06,785752\r\n\"DD\",50.85,\"6/11/2007\",\"10:35am\",-0.28,51.13,51.21,50.69,754500\r\n\"DIS\",34.22,\"6/11/2007\",\"10:36am\",+0.02,34.28,34.44,34.12,1157550\r\n\"GE\",37.30,\"6/11/2007\",\"10:36am\",-0.02,37.07,37.41,37.05,3926919\r\n\"GM\",31.15,\"6/11/2007\",\"10:36am\",+0.15,31.00,31.62,30.90,4421279\r\n\"HD\",37.76,\"6/11/2007\",\"10:36am\",-0.19,37.78,37.83,37.62,1911069\r\n\"HON\",57.08,\"6/11/2007\",\"10:36am\",-0.30,57.25,57.40,57.00,530800\r\n\"HPQ\",46.01,\"6/11/2007\",\"10:35am\",+0.31,45.80,46.29,45.46,2464880\r\n\"IBM\",103.19,\"6/11/2007\",\"10:36am\",+0.12,102.87,103.63,102.50,1242400\r\n\"INTC\",21.88,\"6/11/2007\",\"10:41am\",+0.05,21.70,21.95,21.69,9039718\r\n\"JNJ\",62.54,\"6/11/2007\",\"10:35am\",+0.41,62.89,62.89,62.15,1848378\r\n\"JPM\",50.17,\"6/11/2007\",\"10:36am\",-0.24,50.41,50.55,50.05,1615800\r\n\"KO\",51.37,\"6/11/2007\",\"10:35am\",-0.30,51.67,51.82,51.32,4715160\r\n\"MCD\",51.19,\"6/11/2007\",\"10:36am\",-0.22,51.47,51.47,50.98,862419\r\n\"MMM\",85.60,\"6/11/2007\",\"10:36am\",-0.34,85.94,85.98,85.41,535100\r\n\"MO\",69.90,\"6/11/2007\",\"10:36am\",-0.40,70.25,70.30,69.76,1818105\r\n\"MRK\",50.405,\"6/11/2007\",\"10:36am\",+0.265,50.30,50.64,50.04,2773500\r\n\"MSFT\",30.08,\"6/11/2007\",\"10:41am\",+0.03,30.05,30.25,29.93,13110210\r\n\"PFE\",26.37,\"6/11/2007\",\"10:36am\",-0.15,26.50,26.53,26.34,3656096\r\n\"PG\",63.07,\"6/11/2007\",\"10:36am\",0.00,62.80,63.10,62.75,1344746\r\n\"T\",40.03,\"6/11/2007\",\"10:36am\",-0.23,40.20,40.25,39.89,2759900\r\n\"UTX\",69.6875,\"6/11/2007\",\"10:36am\",-0.5425,69.85,70.20,69.51,573400\r\n\"VZ\",43.17,\"6/11/2007\",\"10:36am\",+0.10,42.95,43.19,42.89,1809710\r\n\"WMT\",49.71,\"6/11/2007\",\"10:36am\",-0.37,49.90,50.00,49.56,2171500\r\n\"XOM\",82.87,\"6/11/2007\",\"10:36am\",+0.19,82.68,83.11,82.35,2657200\r\n\"AA\",39.59,\"6/11/2007\",\"10:41am\",-0.07,39.67,40.18,39.43,1035980\r\n\"AIG\",71.38,\"6/11/2007\",\"10:41am\",-0.15,71.29,71.60,71.15,1036342\r\n\"AXP\",62.99,\"6/11/2007\",\"10:40am\",-0.05,62.79,63.07,62.42,1401320\r\n\"BA\",97.98,\"6/11/2007\",\"10:40am\",-0.21,98.25,98.79,97.74,586200\r\n\"C\",53.04,\"6/11/2007\",\"10:41am\",-0.29,53.20,53.25,52.81,1974394\r\n\"CAT\",78.47,\"6/11/2007\",\"10:40am\",-0.05,78.32,78.99,78.06,811452\r\n\"DD\",50.75,\"6/11/2007\",\"10:41am\",-0.38,51.13,51.21,50.69,816700\r\n\"DIS\",34.22,\"6/11/2007\",\"10:41am\",+0.02,34.28,34.44,34.12,1264950\r\n\"GE\",37.32,\"6/11/2007\",\"10:41am\",0.00,37.07,37.41,37.05,4056919\r\n\"GM\",31.15,\"6/11/2007\",\"10:41am\",+0.15,31.00,31.62,30.90,4500679\r\n\"HD\",37.74,\"6/11/2007\",\"10:40am\",-0.21,37.78,37.83,37.62,1949569\r\n\"HON\",57.05,\"6/11/2007\",\"10:40am\",-0.33,57.25,57.40,57.00,566900\r\n\"HPQ\",45.95,\"6/11/2007\",\"10:41am\",+0.25,45.80,46.29,45.46,2574530\r\n\"IBM\",103.23,\"6/11/2007\",\"10:40am\",+0.16,102.87,103.63,102.50,1272000\r\n\"INTC\",21.88,\"6/11/2007\",\"10:46am\",+0.05,21.70,21.95,21.69,9537433\r\n\"JNJ\",62.52,\"6/11/2007\",\"10:41am\",+0.39,62.89,62.89,62.15,1896678\r\n\"JPM\",50.18,\"6/11/2007\",\"10:41am\",-0.23,50.41,50.55,50.05,1682000\r\n\"KO\",51.38,\"6/11/2007\",\"10:40am\",-0.29,51.67,51.82,51.32,4744560\r\n\"MCD\",51.20,\"6/11/2007\",\"10:40am\",-0.21,51.47,51.47,50.98,903219\r\n\"MMM\",85.61,\"6/11/2007\",\"10:40am\",-0.33,85.94,85.98,85.41,544900\r\n\"MO\",69.95,\"6/11/2007\",\"10:40am\",-0.35,70.25,70.30,69.76,1858005\r\n\"MRK\",50.49,\"6/11/2007\",\"10:40am\",+0.35,50.30,50.64,50.04,2889100\r\n\"MSFT\",30.022,\"6/11/2007\",\"10:45am\",-0.028,30.05,30.25,29.93,13503536\r\n\"PFE\",26.365,\"6/11/2007\",\"10:40am\",-0.155,26.50,26.53,26.34,3770896\r\n\"PG\",63.08,\"6/11/2007\",\"10:41am\",+0.01,62.80,63.12,62.75,1435146\r\n\"T\",39.99,\"6/11/2007\",\"10:41am\",-0.27,40.20,40.25,39.89,2921500\r\n\"UTX\",69.77,\"6/11/2007\",\"10:41am\",-0.46,69.85,70.20,69.51,589900\r\n\"VZ\",43.19,\"6/11/2007\",\"10:40am\",+0.12,42.95,43.19,42.89,1878010\r\n\"WMT\",49.625,\"6/11/2007\",\"10:41am\",-0.455,49.90,50.00,49.56,2239000\r\n\"XOM\",82.89,\"6/11/2007\",\"10:40am\",+0.21,82.68,83.11,82.35,2766800\r\n\"AA\",39.54,\"6/11/2007\",\"10:46am\",-0.12,39.67,40.18,39.43,1080380\r\n\"AIG\",71.32,\"6/11/2007\",\"10:46am\",-0.21,71.29,71.60,71.15,1071642\r\n\"AXP\",62.85,\"6/11/2007\",\"10:46am\",-0.19,62.79,63.07,62.42,1441520\r\n\"BA\",98.00,\"6/11/2007\",\"10:45am\",-0.19,98.25,98.79,97.74,632300\r\n\"C\",52.95,\"6/11/2007\",\"10:46am\",-0.38,53.20,53.25,52.81,2045194\r\n\"CAT\",78.50,\"6/11/2007\",\"10:45am\",-0.02,78.32,78.99,78.06,834252\r\n\"DD\",50.64,\"6/11/2007\",\"10:46am\",-0.49,51.13,51.21,50.63,893400\r\n\"DIS\",34.21,\"6/11/2007\",\"10:45am\",+0.01,34.28,34.44,34.12,1344850\r\n\"GE\",37.2627,\"6/11/2007\",\"10:46am\",-0.0573,37.07,37.41,37.05,4537419\r\n\"GM\",31.13,\"6/11/2007\",\"10:45am\",+0.13,31.00,31.62,30.90,4598979\r\n\"HD\",37.73,\"6/11/2007\",\"10:46am\",-0.22,37.78,37.83,37.62,2039969\r\n\"HON\",57.05,\"6/11/2007\",\"10:45am\",-0.33,57.25,57.40,57.00,611200\r\n\"HPQ\",45.89,\"6/11/2007\",\"10:46am\",+0.19,45.80,46.29,45.46,2729030\r\n\"IBM\",103.19,\"6/11/2007\",\"10:46am\",+0.12,102.87,103.63,102.50,1319400\r\n\"INTC\",21.90,\"6/11/2007\",\"10:51am\",+0.07,21.70,21.95,21.69,9897575\r\n\"JNJ\",62.42,\"6/11/2007\",\"10:46am\",+0.29,62.89,62.89,62.15,1948833\r\n\"JPM\",50.20,\"6/11/2007\",\"10:46am\",-0.21,50.41,50.55,50.05,1760600\r\n\"KO\",51.38,\"6/11/2007\",\"10:46am\",-0.29,51.67,51.82,51.32,4787060\r\n\"MCD\",51.21,\"6/11/2007\",\"10:45am\",-0.20,51.47,51.47,50.98,957919\r\n\"MMM\",85.54,\"6/11/2007\",\"10:45am\",-0.40,85.94,85.98,85.41,565800\r\n\"MO\",69.96,\"6/11/2007\",\"10:45am\",-0.34,70.25,70.30,69.76,1992005\r\n\"MRK\",50.41,\"6/11/2007\",\"10:45am\",+0.27,50.30,50.64,50.04,2985100\r\n\"MSFT\",30.04,\"6/11/2007\",\"10:50am\",-0.01,30.05,30.25,29.93,13852152\r\n\"PFE\",26.37,\"6/11/2007\",\"10:46am\",-0.15,26.50,26.53,26.34,4020496\r\n\"PG\",63.02,\"6/11/2007\",\"10:45am\",-0.05,62.80,63.12,62.75,1542146\r\n\"T\",39.95,\"6/11/2007\",\"10:46am\",-0.31,40.20,40.25,39.89,3119000\r\n\"UTX\",69.72,\"6/11/2007\",\"10:45am\",-0.51,69.85,70.20,69.51,611500\r\n\"VZ\",43.18,\"6/11/2007\",\"10:45am\",+0.11,42.95,43.20,42.89,1971710\r\n\"WMT\",49.60,\"6/11/2007\",\"10:46am\",-0.48,49.90,50.00,49.56,2280600\r\n\"XOM\",82.72,\"6/11/2007\",\"10:46am\",+0.04,82.68,83.11,82.35,2888400\r\n\"AA\",39.65,\"6/11/2007\",\"10:50am\",-0.01,39.67,40.18,39.43,1109080\r\n\"AIG\",71.44,\"6/11/2007\",\"10:50am\",-0.09,71.29,71.60,71.15,1102442\r\n\"AXP\",62.83,\"6/11/2007\",\"10:50am\",-0.21,62.79,63.07,62.42,1484920\r\n\"BA\",98.11,\"6/11/2007\",\"10:51am\",-0.08,98.25,98.79,97.74,653100\r\n\"C\",53.01,\"6/11/2007\",\"10:51am\",-0.32,53.20,53.25,52.81,2147094\r\n\"CAT\",78.56,\"6/11/2007\",\"10:50am\",+0.04,78.32,78.99,78.06,857252\r\n\"DD\",50.78,\"6/11/2007\",\"10:50am\",-0.35,51.13,51.21,50.62,931500\r\n\"DIS\",34.24,\"6/11/2007\",\"10:51am\",+0.04,34.28,34.44,34.12,1456950\r\n\"GE\",37.29,\"6/11/2007\",\"10:51am\",-0.03,37.07,37.41,37.05,6374978\r\n\"GM\",31.17,\"6/11/2007\",\"10:50am\",+0.17,31.00,31.62,30.90,4686579\r\n\"HD\",37.745,\"6/11/2007\",\"10:51am\",-0.205,37.78,37.83,37.62,2111769\r\n\"HON\",57.14,\"6/11/2007\",\"10:51am\",-0.24,57.25,57.40,57.00,672700\r\n\"HPQ\",46.03,\"6/11/2007\",\"10:51am\",+0.33,45.80,46.29,45.46,2822530\r\n\"IBM\",103.39,\"6/11/2007\",\"10:51am\",+0.32,102.87,103.63,102.50,1365400\r\n\"INTC\",21.89,\"6/11/2007\",\"10:56am\",+0.06,21.70,21.95,21.69,10794661\r\n\"JNJ\",62.60,\"6/11/2007\",\"10:51am\",+0.47,62.89,62.89,62.15,2051122\r\n\"JPM\",50.25,\"6/11/2007\",\"10:50am\",-0.16,50.41,50.55,50.05,1853000\r\n\"KO\",51.46,\"6/11/2007\",\"10:51am\",-0.21,51.67,51.82,51.32,4861660\r\n\"MCD\",51.34,\"6/11/2007\",\"10:50am\",-0.07,51.47,51.47,50.98,999019\r\n\"MMM\",85.53,\"6/11/2007\",\"10:50am\",-0.41,85.94,85.98,85.41,582200\r\n\"MO\",70.0411,\"6/11/2007\",\"10:51am\",-0.2589,70.25,70.30,69.76,2064105\r\n\"MRK\",50.48,\"6/11/2007\",\"10:51am\",+0.34,50.30,50.64,50.04,3053000\r\n\"MSFT\",30.01,\"6/11/2007\",\"10:56am\",-0.04,30.05,30.25,29.93,14553915\r\n\"PFE\",26.385,\"6/11/2007\",\"10:51am\",-0.135,26.50,26.53,26.34,4373096\r\n\"PG\",63.04,\"6/11/2007\",\"10:51am\",-0.03,62.80,63.12,62.75,1613346\r\n\"T\",39.97,\"6/11/2007\",\"10:51am\",-0.29,40.20,40.25,39.89,3263300\r\n\"UTX\",69.79,\"6/11/2007\",\"10:51am\",-0.44,69.85,70.20,69.51,629700\r\n\"VZ\",43.19,\"6/11/2007\",\"10:50am\",+0.12,42.95,43.20,42.89,2067610\r\n\"WMT\",49.70,\"6/11/2007\",\"10:51am\",-0.38,49.90,50.00,49.56,2349800\r\n\"XOM\",82.73,\"6/11/2007\",\"10:51am\",+0.05,82.68,83.11,82.35,2994800\r\n\"AA\",39.67,\"6/11/2007\",\"10:56am\",+0.01,39.67,40.18,39.43,1139080\r\n\"AIG\",71.45,\"6/11/2007\",\"10:55am\",-0.08,71.29,71.60,71.15,1141342\r\n\"AXP\",62.94,\"6/11/2007\",\"10:56am\",-0.10,62.79,63.07,62.42,1517420\r\n\"BA\",98.18,\"6/11/2007\",\"10:56am\",-0.01,98.25,98.79,97.74,695900\r\n\"C\",53.02,\"6/11/2007\",\"10:56am\",-0.31,53.20,53.25,52.81,2245494\r\n\"CAT\",78.57,\"6/11/2007\",\"10:56am\",+0.05,78.32,78.99,78.06,892252\r\n\"DD\",50.8276,\"6/11/2007\",\"10:55am\",-0.3024,51.13,51.21,50.62,991900\r\n\"DIS\",34.23,\"6/11/2007\",\"10:56am\",+0.03,34.28,34.44,34.12,1533350\r\n\"GE\",37.34,\"6/11/2007\",\"10:56am\",+0.02,37.07,37.41,37.05,6871678\r\n\"GM\",31.20,\"6/11/2007\",\"10:56am\",+0.20,31.00,31.62,30.90,4845779\r\n\"HD\",37.74,\"6/11/2007\",\"10:56am\",-0.21,37.78,37.83,37.62,2282669\r\n\"HON\",57.17,\"6/11/2007\",\"10:56am\",-0.21,57.25,57.40,57.00,700800\r\n\"HPQ\",46.07,\"6/11/2007\",\"10:56am\",+0.37,45.80,46.29,45.46,2941930\r\n\"IBM\",103.48,\"6/11/2007\",\"10:56am\",+0.41,102.87,103.63,102.50,1449900\r\n\"INTC\",21.93,\"6/11/2007\",\"11:01am\",+0.10,21.70,21.95,21.69,11463765\r\n\"JNJ\",62.47,\"6/11/2007\",\"10:56am\",+0.34,62.89,62.89,62.15,2184222\r\n\"JPM\",50.27,\"6/11/2007\",\"10:56am\",-0.14,50.41,50.55,50.05,1992300\r\n\"KO\",51.45,\"6/11/2007\",\"10:56am\",-0.22,51.67,51.82,51.32,4959242\r\n\"MCD\",51.39,\"6/11/2007\",\"10:56am\",-0.02,51.47,51.47,50.98,1065114\r\n\"MMM\",85.58,\"6/11/2007\",\"10:56am\",-0.36,85.94,85.98,85.41,603300\r\n\"MO\",70.09,\"6/11/2007\",\"10:56am\",-0.21,70.25,70.30,69.76,2152505\r\n\"MRK\",50.45,\"6/11/2007\",\"10:56am\",+0.31,50.30,50.64,50.04,3120700\r\n\"MSFT\",29.985,\"6/11/2007\",\"11:01am\",-0.065,30.05,30.25,29.93,15025412\r\n\"PFE\",26.35,\"6/11/2007\",\"10:55am\",-0.17,26.50,26.53,26.34,4707596\r\n\"PG\",63.01,\"6/11/2007\",\"10:56am\",-0.06,62.80,63.12,62.75,1804846\r\n\"T\",40.01,\"6/11/2007\",\"10:56am\",-0.25,40.20,40.25,39.89,3433900\r\n\"UTX\",69.90,\"6/11/2007\",\"10:56am\",-0.33,69.85,70.20,69.51,647800\r\n\"VZ\",43.17,\"6/11/2007\",\"10:56am\",+0.10,42.95,43.20,42.89,2246610\r\n\"WMT\",49.61,\"6/11/2007\",\"10:56am\",-0.47,49.90,50.00,49.56,2553200\r\n\"XOM\",82.90,\"6/11/2007\",\"10:56am\",+0.22,82.68,83.11,82.35,3157200\r\n\"AA\",39.65,\"6/11/2007\",\"11:01am\",-0.01,39.67,40.18,39.43,1162580\r\n\"AIG\",71.46,\"6/11/2007\",\"11:01am\",-0.07,71.29,71.60,71.15,1167442\r\n\"AXP\",62.99,\"6/11/2007\",\"11:00am\",-0.05,62.79,63.07,62.42,1532720\r\n\"BA\",98.09,\"6/11/2007\",\"11:01am\",-0.10,98.25,98.79,97.74,714300\r\n\"C\",53.11,\"6/11/2007\",\"11:01am\",-0.22,53.20,53.25,52.81,2344994\r\n\"CAT\",78.52,\"6/11/2007\",\"11:00am\",0.00,78.32,78.99,78.06,915752\r\n\"DD\",50.75,\"6/11/2007\",\"11:01am\",-0.38,51.13,51.21,50.62,1025100\r\n\"DIS\",34.22,\"6/11/2007\",\"11:01am\",+0.02,34.28,34.44,34.12,1567750\r\n\"GE\",37.32,\"6/11/2007\",\"11:01am\",0.00,37.07,37.41,37.05,7016378\r\n\"GM\",31.22,\"6/11/2007\",\"11:01am\",+0.22,31.00,31.62,30.90,4944879\r\n\"HD\",37.73,\"6/11/2007\",\"11:01am\",-0.22,37.78,37.83,37.62,2556369\r\n\"HON\",57.10,\"6/11/2007\",\"11:01am\",-0.28,57.25,57.40,57.00,794183\r\n\"HPQ\",46.06,\"6/11/2007\",\"11:01am\",+0.36,45.80,46.29,45.46,3023656\r\n\"IBM\",103.37,\"6/11/2007\",\"11:01am\",+0.30,102.87,103.63,102.50,1483400\r\n\"INTC\",21.97,\"6/11/2007\",\"11:06am\",+0.14,21.70,21.97,21.69,12553376\r\n\"JNJ\",62.46,\"6/11/2007\",\"11:01am\",+0.33,62.89,62.89,62.15,2309522\r\n\"JPM\",50.32,\"6/11/2007\",\"11:01am\",-0.09,50.41,50.55,50.05,2110500\r\n\"KO\",51.42,\"6/11/2007\",\"11:01am\",-0.25,51.67,51.82,51.32,5000442\r\n\"MCD\",51.50,\"6/11/2007\",\"11:01am\",+0.09,51.47,51.50,50.98,1190914\r\n\"MMM\",85.62,\"6/11/2007\",\"11:00am\",-0.32,85.94,85.98,85.41,620500\r\n\"MO\",70.12,\"6/11/2007\",\"11:01am\",-0.18,70.25,70.30,69.76,2246305\r\n\"MRK\",50.505,\"6/11/2007\",\"11:01am\",+0.365,50.30,50.64,50.04,3232600\r\n\"MSFT\",30.02,\"6/11/2007\",\"11:06am\",-0.03,30.05,30.25,29.93,15371602\r\n\"PFE\",26.34,\"6/11/2007\",\"11:01am\",-0.18,26.50,26.53,26.33,5134096\r\n\"PG\",62.98,\"6/11/2007\",\"11:01am\",-0.09,62.80,63.12,62.75,1854046\r\n\"T\",40.02,\"6/11/2007\",\"11:01am\",-0.24,40.20,40.25,39.89,3531300\r\n\"UTX\",69.91,\"6/11/2007\",\"11:00am\",-0.32,69.85,70.20,69.51,658800\r\n\"VZ\",43.16,\"6/11/2007\",\"11:01am\",+0.09,42.95,43.20,42.89,2288410\r\n\"WMT\",49.59,\"6/11/2007\",\"11:01am\",-0.49,49.90,50.00,49.56,2659033\r\n\"XOM\",82.98,\"6/11/2007\",\"11:01am\",+0.30,82.68,83.11,82.35,3296100\r\n\"AA\",39.60,\"6/11/2007\",\"11:06am\",-0.06,39.67,40.18,39.43,1189680\r\n\"AIG\",71.49,\"6/11/2007\",\"11:06am\",-0.04,71.29,71.60,71.15,1226342\r\n\"AXP\",63.11,\"6/11/2007\",\"11:06am\",+0.07,62.79,63.11,62.42,1576120\r\n\"BA\",98.20,\"6/11/2007\",\"11:06am\",+0.01,98.25,98.79,97.74,743800\r\n\"C\",53.17,\"6/11/2007\",\"11:06am\",-0.16,53.20,53.25,52.81,2445294\r\n\"CAT\",78.70,\"6/11/2007\",\"11:06am\",+0.18,78.32,78.99,78.06,950252\r\n\"DD\",50.73,\"6/11/2007\",\"11:06am\",-0.40,51.13,51.21,50.62,1165000\r\n\"DIS\",34.24,\"6/11/2007\",\"11:06am\",+0.04,34.28,34.44,34.12,1636250\r\n\"GE\",37.3314,\"6/11/2007\",\"11:06am\",+0.0114,37.07,37.41,37.05,7140328\r\n\"GM\",31.19,\"6/11/2007\",\"11:06am\",+0.19,31.00,31.62,30.90,5065479\r\n\"HD\",37.72,\"6/11/2007\",\"11:06am\",-0.23,37.78,37.83,37.62,2746169\r\n\"HON\",57.04,\"6/11/2007\",\"11:06am\",-0.34,57.25,57.40,57.00,851383\r\n\"HPQ\",46.09,\"6/11/2007\",\"11:06am\",+0.39,45.80,46.29,45.46,3113456\r\n\"IBM\",103.35,\"6/11/2007\",\"11:05am\",+0.28,102.87,103.63,102.50,1528300\r\n\"INTC\",21.95,\"6/11/2007\",\"11:11am\",+0.12,21.70,21.98,21.69,13630344\r\n\"JNJ\",62.45,\"6/11/2007\",\"11:06am\",+0.32,62.89,62.89,62.15,2409122\r\n\"JPM\",50.38,\"6/11/2007\",\"11:06am\",-0.03,50.41,50.55,50.05,2209200\r\n\"KO\",51.44,\"6/11/2007\",\"11:05am\",-0.23,51.67,51.82,51.32,5034642\r\n\"MCD\",51.61,\"6/11/2007\",\"11:06am\",+0.20,51.47,51.61,50.98,1307114\r\n\"MMM\",85.66,\"6/11/2007\",\"11:06am\",-0.28,85.94,85.98,85.41,642300\r\n\"MO\",70.15,\"6/11/2007\",\"11:06am\",-0.15,70.25,70.30,69.76,2298805\r\n\"MRK\",50.52,\"6/11/2007\",\"11:06am\",+0.38,50.30,50.64,50.04,3272300\r\n\"MSFT\",30.06,\"6/11/2007\",\"11:11am\",+0.01,30.05,30.25,29.93,15638561\r\n\"PFE\",26.36,\"6/11/2007\",\"11:06am\",-0.16,26.50,26.53,26.33,5407496\r\n\"PG\",62.99,\"6/11/2007\",\"11:06am\",-0.08,62.80,63.12,62.75,1906246\r\n\"T\",40.01,\"6/11/2007\",\"11:06am\",-0.25,40.20,40.25,39.89,3728000\r\n\"UTX\",69.92,\"6/11/2007\",\"11:06am\",-0.31,69.85,70.20,69.51,669300\r\n\"VZ\",43.17,\"6/11/2007\",\"11:06am\",+0.10,42.95,43.20,42.89,2322610\r\n\"WMT\",49.55,\"6/11/2007\",\"11:06am\",-0.53,49.90,50.00,49.55,2733633\r\n\"XOM\",83.08,\"6/11/2007\",\"11:06am\",+0.40,82.68,83.11,82.35,3448500\r\n\"AA\",39.52,\"6/11/2007\",\"11:11am\",-0.14,39.67,40.18,39.43,1258280\r\n\"AIG\",71.50,\"6/11/2007\",\"11:11am\",-0.03,71.29,71.60,71.15,1250442\r\n\"AXP\",63.05,\"6/11/2007\",\"11:10am\",+0.01,62.79,63.12,62.42,1588220\r\n\"BA\",98.16,\"6/11/2007\",\"11:11am\",-0.03,98.25,98.79,97.74,759800\r\n\"C\",53.18,\"6/11/2007\",\"11:11am\",-0.15,53.20,53.25,52.81,2614994\r\n\"CAT\",78.82,\"6/11/2007\",\"11:11am\",+0.30,78.32,78.99,78.06,992052\r\n\"DD\",50.72,\"6/11/2007\",\"11:11am\",-0.41,51.13,51.21,50.59,1241700\r\n\"DIS\",34.22,\"6/11/2007\",\"11:11am\",+0.02,34.28,34.44,34.12,1691750\r\n\"GE\",37.36,\"6/11/2007\",\"11:11am\",+0.04,37.07,37.41,37.05,7342028\r\n\"GM\",31.14,\"6/11/2007\",\"11:10am\",+0.14,31.00,31.62,30.90,5195679\r\n\"HD\",37.74,\"6/11/2007\",\"11:11am\",-0.21,37.78,37.83,37.62,2859969\r\n\"HON\",57.08,\"6/11/2007\",\"11:10am\",-0.30,57.25,57.40,57.00,888783\r\n\"HPQ\",46.08,\"6/11/2007\",\"11:11am\",+0.38,45.80,46.29,45.46,3164856\r\n\"IBM\",103.34,\"6/11/2007\",\"11:11am\",+0.27,102.87,103.63,102.50,1554100\r\n\"INTC\",21.92,\"6/11/2007\",\"11:15am\",+0.09,21.70,21.98,21.69,14546699\r\n\"JNJ\",62.49,\"6/11/2007\",\"11:11am\",+0.36,62.89,62.89,62.15,2467522\r\n\"JPM\",50.39,\"6/11/2007\",\"11:11am\",-0.02,50.41,50.55,50.05,2298000\r\n\"KO\",51.46,\"6/11/2007\",\"11:11am\",-0.21,51.67,51.82,51.32,5073542\r\n\"MCD\",51.60,\"6/11/2007\",\"11:11am\",+0.19,51.47,51.62,50.98,1438514\r\n\"MMM\",85.70,\"6/11/2007\",\"11:11am\",-0.24,85.94,85.98,85.41,673300\r\n\"MO\",70.2306,\"6/11/2007\",\"11:11am\",-0.0694,70.25,70.30,69.76,2382105\r\n\"MRK\",50.46,\"6/11/2007\",\"11:11am\",+0.32,50.30,50.64,50.04,3337000\r\n\"MSFT\",30.04,\"6/11/2007\",\"11:16am\",-0.01,30.05,30.25,29.93,15908978\r\n\"PFE\",26.32,\"6/11/2007\",\"11:11am\",-0.20,26.50,26.53,26.31,5832646\r\n\"PG\",62.99,\"6/11/2007\",\"11:11am\",-0.08,62.80,63.12,62.75,1960846\r\n\"T\",40.001,\"6/11/2007\",\"11:11am\",-0.259,40.20,40.25,39.89,3794300\r\n\"UTX\",69.85,\"6/11/2007\",\"11:10am\",-0.38,69.85,70.20,69.51,678300\r\n\"VZ\",43.16,\"6/11/2007\",\"11:10am\",+0.09,42.95,43.20,42.89,2358345\r\n\"WMT\",49.64,\"6/11/2007\",\"11:11am\",-0.44,49.90,50.00,49.55,2915433\r\n\"XOM\",83.09,\"6/11/2007\",\"11:11am\",+0.41,82.68,83.11,82.35,3609300\r\n\"AA\",39.48,\"6/11/2007\",\"11:16am\",-0.18,39.67,40.18,39.43,1290380\r\n\"AIG\",71.42,\"6/11/2007\",\"11:16am\",-0.11,71.29,71.60,71.15,1276442\r\n\"AXP\",63.01,\"6/11/2007\",\"11:16am\",-0.03,62.79,63.12,62.42,1616220\r\n\"BA\",98.01,\"6/11/2007\",\"11:16am\",-0.18,98.25,98.79,97.74,777300\r\n\"C\",53.23,\"6/11/2007\",\"11:16am\",-0.10,53.20,53.25,52.81,2793394\r\n\"CAT\",78.65,\"6/11/2007\",\"11:16am\",+0.13,78.32,78.99,78.06,1046952\r\n\"DD\",50.70,\"6/11/2007\",\"11:16am\",-0.43,51.13,51.21,50.59,1337600\r\n\"DIS\",34.20,\"6/11/2007\",\"11:15am\",0.00,34.28,34.44,34.12,1805250\r\n\"GE\",37.35,\"6/11/2007\",\"11:16am\",+0.03,37.07,37.41,37.05,7601178\r\n\"GM\",31.10,\"6/11/2007\",\"11:15am\",+0.10,31.00,31.62,30.90,5260279\r\n\"HD\",37.73,\"6/11/2007\",\"11:16am\",-0.22,37.78,37.83,37.62,2927469\r\n\"HON\",56.965,\"6/11/2007\",\"11:16am\",-0.415,57.25,57.40,56.93,940183\r\n\"HPQ\",46.04,\"6/11/2007\",\"11:16am\",+0.34,45.80,46.29,45.46,3255956\r\n\"IBM\",103.24,\"6/11/2007\",\"11:16am\",+0.17,102.87,103.63,102.50,1595900\r\n\"INTC\",21.92,\"6/11/2007\",\"11:21am\",+0.09,21.70,21.98,21.69,14851182\r\n\"JNJ\",62.55,\"6/11/2007\",\"11:16am\",+0.42,62.89,62.89,62.15,2628222\r\n\"JPM\",50.32,\"6/11/2007\",\"11:16am\",-0.09,50.41,50.55,50.05,2372200\r\n\"KO\",51.46,\"6/11/2007\",\"11:16am\",-0.21,51.67,51.82,51.32,5091242\r\n\"MCD\",51.49,\"6/11/2007\",\"11:15am\",+0.08,51.47,51.62,50.98,1719114\r\n\"MMM\",85.57,\"6/11/2007\",\"11:15am\",-0.37,85.94,85.98,85.41,691600\r\n\"MO\",70.17,\"6/11/2007\",\"11:16am\",-0.13,70.25,70.30,69.76,2466057\r\n\"MRK\",50.52,\"6/11/2007\",\"11:16am\",+0.38,50.30,50.64,50.04,3543600\r\n\"MSFT\",30.025,\"6/11/2007\",\"11:20am\",-0.025,30.05,30.25,29.93,16110835\r\n\"PFE\",26.33,\"6/11/2007\",\"11:16am\",-0.19,26.50,26.53,26.31,6034446\r\n\"PG\",62.96,\"6/11/2007\",\"11:16am\",-0.11,62.80,63.12,62.75,2028646\r\n\"T\",39.97,\"6/11/2007\",\"11:16am\",-0.29,40.20,40.25,39.89,3937400\r\n\"UTX\",69.81,\"6/11/2007\",\"11:15am\",-0.42,69.85,70.20,69.51,697600\r\n\"VZ\",43.15,\"6/11/2007\",\"11:16am\",+0.08,42.95,43.20,42.89,2414845\r\n\"WMT\",49.63,\"6/11/2007\",\"11:16am\",-0.45,49.90,50.00,49.55,3029233\r\n\"XOM\",82.96,\"6/11/2007\",\"11:16am\",+0.28,82.68,83.11,82.35,3750000\r\n\"AA\",39.46,\"6/11/2007\",\"11:20am\",-0.20,39.67,40.18,39.43,1318080\r\n\"AIG\",71.37,\"6/11/2007\",\"11:20am\",-0.16,71.29,71.60,71.15,1313142\r\n\"AXP\",63.09,\"6/11/2007\",\"11:21am\",+0.05,62.79,63.12,62.42,1639320\r\n\"BA\",98.06,\"6/11/2007\",\"11:21am\",-0.13,98.25,98.79,97.74,796300\r\n\"C\",53.24,\"6/11/2007\",\"11:21am\",-0.09,53.20,53.26,52.81,2937494\r\n\"CAT\",78.72,\"6/11/2007\",\"11:21am\",+0.20,78.32,78.99,78.06,1095252\r\n\"DD\",50.67,\"6/11/2007\",\"11:21am\",-0.46,51.13,51.21,50.59,1421500\r\n\"DIS\",34.21,\"6/11/2007\",\"11:21am\",+0.01,34.28,34.44,34.12,1882850\r\n\"GE\",37.41,\"6/11/2007\",\"11:21am\",+0.09,37.07,37.43,37.05,8394078\r\n\"GM\",31.06,\"6/11/2007\",\"11:21am\",+0.06,31.00,31.62,30.90,5321979\r\n\"HD\",37.72,\"6/11/2007\",\"11:21am\",-0.23,37.78,37.83,37.62,2989469\r\n\"HON\",56.98,\"6/11/2007\",\"11:21am\",-0.40,57.25,57.40,56.91,1018183\r\n\"HPQ\",46.01,\"6/11/2007\",\"11:21am\",+0.31,45.80,46.29,45.46,3347656\r\n\"IBM\",103.27,\"6/11/2007\",\"11:21am\",+0.20,102.87,103.63,102.50,1635600\r\n\"INTC\",21.93,\"6/11/2007\",\"11:26am\",+0.10,21.70,21.98,21.69,15018868\r\n\"JNJ\",62.58,\"6/11/2007\",\"11:21am\",+0.45,62.89,62.89,62.15,2706422\r\n\"JPM\",50.30,\"6/11/2007\",\"11:21am\",-0.11,50.41,50.55,50.05,2419700\r\n\"KO\",51.45,\"6/11/2007\",\"11:20am\",-0.22,51.67,51.82,51.32,5160942\r\n\"MCD\",51.46,\"6/11/2007\",\"11:20am\",+0.05,51.47,51.62,50.98,1812914\r\n\"MMM\",85.47,\"6/11/2007\",\"11:21am\",-0.47,85.94,85.98,85.41,710100\r\n\"MO\",70.20,\"6/11/2007\",\"11:21am\",-0.10,70.25,70.30,69.76,2517457\r\n\"MRK\",50.48,\"6/11/2007\",\"11:21am\",+0.34,50.30,50.64,50.04,3625200\r\n\"MSFT\",30.07,\"6/11/2007\",\"11:26am\",+0.02,30.05,30.25,29.93,16343142\r\n\"PFE\",26.349,\"6/11/2007\",\"11:21am\",-0.171,26.50,26.53,26.31,6275846\r\n\"PG\",62.98,\"6/11/2007\",\"11:21am\",-0.09,62.80,63.12,62.75,2075846\r\n\"T\",39.99,\"6/11/2007\",\"11:21am\",-0.27,40.20,40.25,39.89,4085900\r\n\"UTX\",69.76,\"6/11/2007\",\"11:21am\",-0.47,69.85,70.20,69.51,714000\r\n\"VZ\",43.16,\"6/11/2007\",\"11:20am\",+0.09,42.95,43.20,42.89,2522445\r\n\"WMT\",49.63,\"6/11/2007\",\"11:21am\",-0.45,49.90,50.00,49.55,3096833\r\n\"XOM\",82.91,\"6/11/2007\",\"11:21am\",+0.23,82.68,83.11,82.35,3880100\r\n\"AA\",39.50,\"6/11/2007\",\"11:26am\",-0.16,39.67,40.18,39.43,1352980\r\n\"AIG\",71.37,\"6/11/2007\",\"11:26am\",-0.16,71.29,71.60,71.15,1345042\r\n\"AXP\",63.03,\"6/11/2007\",\"11:26am\",-0.01,62.79,63.12,62.42,1654030\r\n\"BA\",98.06,\"6/11/2007\",\"11:26am\",-0.13,98.25,98.79,97.74,817500\r\n\"C\",53.24,\"6/11/2007\",\"11:26am\",-0.09,53.20,53.26,52.81,3004394\r\n\"CAT\",78.75,\"6/11/2007\",\"11:26am\",+0.23,78.32,78.99,78.06,1112652\r\n\"DD\",50.80,\"6/11/2007\",\"11:26am\",-0.33,51.13,51.21,50.59,1443900\r\n\"DIS\",34.22,\"6/11/2007\",\"11:25am\",+0.02,34.28,34.44,34.12,1977950\r\n\"GE\",37.401,\"6/11/2007\",\"11:26am\",+0.081,37.07,37.43,37.05,8549378\r\n\"GM\",31.18,\"6/11/2007\",\"11:26am\",+0.18,31.00,31.62,30.90,5486579\r\n\"HD\",37.74,\"6/11/2007\",\"11:26am\",-0.21,37.78,37.83,37.62,3055769\r\n\"HON\",57.03,\"6/11/2007\",\"11:26am\",-0.35,57.25,57.40,56.91,1098842\r\n\"HPQ\",46.00,\"6/11/2007\",\"11:26am\",+0.30,45.80,46.29,45.46,3418156\r\n\"IBM\",103.33,\"6/11/2007\",\"11:26am\",+0.26,102.87,103.63,102.50,1657200\r\n\"INTC\",21.94,\"6/11/2007\",\"11:31am\",+0.11,21.70,21.98,21.69,15378550\r\n\"JNJ\",62.59,\"6/11/2007\",\"11:26am\",+0.46,62.89,62.89,62.15,2772722\r\n\"JPM\",50.38,\"6/11/2007\",\"11:26am\",-0.03,50.41,50.55,50.05,2481600\r\n\"KO\",51.45,\"6/11/2007\",\"11:26am\",-0.22,51.67,51.82,51.32,5180942\r\n\"MCD\",51.49,\"6/11/2007\",\"11:26am\",+0.08,51.47,51.62,50.98,1867514\r\n\"MMM\",85.45,\"6/11/2007\",\"11:25am\",-0.49,85.94,85.98,85.39,738400\r\n\"MO\",70.21,\"6/11/2007\",\"11:26am\",-0.09,70.25,70.30,69.76,2582357\r\n\"MRK\",50.55,\"6/11/2007\",\"11:26am\",+0.41,50.30,50.64,50.04,3773900\r\n\"MSFT\",30.05,\"6/11/2007\",\"11:31am\",0.00,30.05,30.25,29.93,16793888\r\n\"PFE\",26.34,\"6/11/2007\",\"11:26am\",-0.18,26.50,26.53,26.31,6551746\r\n\"PG\",62.99,\"6/11/2007\",\"11:26am\",-0.08,62.80,63.12,62.75,2114846\r\n\"T\",40.06,\"6/11/2007\",\"11:26am\",-0.20,40.20,40.25,39.89,4212600\r\n\"UTX\",69.73,\"6/11/2007\",\"11:25am\",-0.50,69.85,70.20,69.51,725600\r\n\"VZ\",43.16,\"6/11/2007\",\"11:26am\",+0.09,42.95,43.20,42.89,2595445\r\n\"WMT\",49.64,\"6/11/2007\",\"11:25am\",-0.44,49.90,50.00,49.55,3167033\r\n\"XOM\",82.89,\"6/11/2007\",\"11:26am\",+0.21,82.68,83.11,82.35,4029000\r\n\"AA\",39.47,\"6/11/2007\",\"11:31am\",-0.19,39.67,40.18,39.43,1409980\r\n\"AIG\",71.48,\"6/11/2007\",\"11:31am\",-0.05,71.29,71.60,71.15,1424042\r\n\"AXP\",63.07,\"6/11/2007\",\"11:30am\",+0.03,62.79,63.14,62.42,1695430\r\n\"BA\",98.00,\"6/11/2007\",\"11:31am\",-0.19,98.25,98.79,97.74,865400\r\n\"C\",53.31,\"6/11/2007\",\"11:31am\",-0.02,53.20,53.31,52.81,3119094\r\n\"CAT\",78.72,\"6/11/2007\",\"11:31am\",+0.20,78.32,78.99,78.06,1140252\r\n\"DD\",50.75,\"6/11/2007\",\"11:31am\",-0.38,51.13,51.21,50.59,1474900\r\n\"DIS\",34.20,\"6/11/2007\",\"11:30am\",0.00,34.28,34.44,34.12,2013250\r\n\"GE\",37.41,\"6/11/2007\",\"11:31am\",+0.09,37.07,37.44,37.05,8715001\r\n\"GM\",31.23,\"6/11/2007\",\"11:31am\",+0.23,31.00,31.62,30.90,5548179\r\n\"HD\",37.72,\"6/11/2007\",\"11:30am\",-0.23,37.78,37.83,37.62,3183869\r\n\"HON\",57.01,\"6/11/2007\",\"11:31am\",-0.37,57.25,57.40,56.91,1290742\r\n\"HPQ\",45.99,\"6/11/2007\",\"11:31am\",+0.29,45.80,46.29,45.46,3514456\r\n\"IBM\",103.31,\"6/11/2007\",\"11:31am\",+0.24,102.87,103.63,102.50,1841300\r\n\"INTC\",21.94,\"6/11/2007\",\"11:36am\",+0.11,21.70,21.98,21.69,15795668\r\n\"JNJ\",62.59,\"6/11/2007\",\"11:31am\",+0.46,62.89,62.89,62.15,2811022\r\n\"JPM\",50.41,\"6/11/2007\",\"11:30am\",0.00,50.41,50.55,50.05,2553800\r\n\"KO\",51.45,\"6/11/2007\",\"11:30am\",-0.22,51.67,51.82,51.32,5207142\r\n\"MCD\",51.47,\"6/11/2007\",\"11:30am\",+0.06,51.47,51.62,50.98,1913314\r\n\"MMM\",85.41,\"6/11/2007\",\"11:31am\",-0.53,85.94,85.98,85.39,805100\r\n\"MO\",70.36,\"6/11/2007\",\"11:31am\",+0.06,70.25,70.36,69.76,2672857\r\n\"MRK\",50.55,\"6/11/2007\",\"11:31am\",+0.41,50.30,50.64,50.04,3841500\r\n\"MSFT\",30.09,\"6/11/2007\",\"11:36am\",+0.04,30.05,30.25,29.93,17107810\r\n\"PFE\",26.35,\"6/11/2007\",\"11:31am\",-0.17,26.50,26.53,26.31,6720346\r\n\"PG\",62.98,\"6/11/2007\",\"11:31am\",-0.09,62.80,63.12,62.75,2155746\r\n\"T\",40.07,\"6/11/2007\",\"11:31am\",-0.19,40.20,40.25,39.89,4436800\r\n\"UTX\",69.74,\"6/11/2007\",\"11:31am\",-0.49,69.85,70.20,69.51,757000\r\n\"VZ\",43.18,\"6/11/2007\",\"11:31am\",+0.11,42.95,43.22,42.89,2918545\r\n\"WMT\",49.66,\"6/11/2007\",\"11:31am\",-0.42,49.90,50.00,49.55,3258333\r\n\"XOM\",82.92,\"6/11/2007\",\"11:31am\",+0.24,82.68,83.11,82.35,4163200\r\n\"AA\",39.50,\"6/11/2007\",\"11:36am\",-0.16,39.67,40.18,39.43,1444680\r\n\"AIG\",71.48,\"6/11/2007\",\"11:35am\",-0.05,71.29,71.60,71.15,1468642\r\n\"AXP\",63.0703,\"6/11/2007\",\"11:35am\",+0.0303,62.79,63.14,62.42,1717830\r\n\"BA\",98.0028,\"6/11/2007\",\"11:36am\",-0.1872,98.25,98.79,97.74,951000\r\n\"C\",53.34,\"6/11/2007\",\"11:36am\",+0.01,53.20,53.37,52.81,3207894\r\n\"CAT\",78.66,\"6/11/2007\",\"11:36am\",+0.14,78.32,78.99,78.06,1150952\r\n\"DD\",50.73,\"6/11/2007\",\"11:36am\",-0.40,51.13,51.21,50.59,1500000\r\n\"DIS\",34.19,\"6/11/2007\",\"11:36am\",-0.01,34.28,34.44,34.12,2167150\r\n\"GE\",37.38,\"6/11/2007\",\"11:36am\",+0.06,37.07,37.44,37.05,9041801\r\n\"GM\",31.25,\"6/11/2007\",\"11:35am\",+0.25,31.00,31.62,30.90,5608979\r\n\"HD\",37.75,\"6/11/2007\",\"11:36am\",-0.20,37.78,37.83,37.62,3269369\r\n\"HON\",56.98,\"6/11/2007\",\"11:36am\",-0.40,57.25,57.40,56.91,1330742\r\n\"HPQ\",45.94,\"6/11/2007\",\"11:36am\",+0.24,45.80,46.29,45.46,3621556\r\n\"IBM\",103.28,\"6/11/2007\",\"11:36am\",+0.21,102.87,103.63,102.50,1859400\r\n\"INTC\",21.97,\"6/11/2007\",\"11:41am\",+0.14,21.70,21.98,21.69,16127869\r\n\"JNJ\",62.56,\"6/11/2007\",\"11:35am\",+0.43,62.89,62.89,62.15,2852322\r\n\"JPM\",50.43,\"6/11/2007\",\"11:36am\",+0.02,50.41,50.55,50.05,2656300\r\n\"KO\",51.40,\"6/11/2007\",\"11:36am\",-0.27,51.67,51.82,51.32,5232342\r\n\"MCD\",51.47,\"6/11/2007\",\"11:35am\",+0.06,51.47,51.62,50.98,1953314\r\n\"MMM\",85.43,\"6/11/2007\",\"11:35am\",-0.51,85.94,85.98,85.39,828900\r\n\"MO\",70.39,\"6/11/2007\",\"11:35am\",+0.09,70.25,70.48,69.76,2823257\r\n\"MRK\",50.551,\"6/11/2007\",\"11:36am\",+0.411,50.30,50.64,50.04,3911800\r\n\"MSFT\",30.11,\"6/11/2007\",\"11:41am\",+0.06,30.05,30.25,29.93,17353272\r\n\"PFE\",26.38,\"6/11/2007\",\"11:36am\",-0.14,26.50,26.53,26.31,6992796\r\n\"PG\",62.93,\"6/11/2007\",\"11:35am\",-0.14,62.80,63.12,62.75,2196846\r\n\"T\",40.03,\"6/11/2007\",\"11:35am\",-0.23,40.20,40.25,39.89,4610000\r\n\"UTX\",69.77,\"6/11/2007\",\"11:35am\",-0.46,69.85,70.20,69.51,788900\r\n\"VZ\",43.18,\"6/11/2007\",\"11:35am\",+0.11,42.95,43.22,42.89,2997845\r\n\"WMT\",49.74,\"6/11/2007\",\"11:36am\",-0.34,49.90,50.00,49.55,3415433\r\n\"XOM\",83.04,\"6/11/2007\",\"11:36am\",+0.36,82.68,83.11,82.35,4297200\r\n\"AA\",39.53,\"6/11/2007\",\"11:41am\",-0.13,39.67,40.18,39.43,1481180\r\n\"AIG\",71.53,\"6/11/2007\",\"11:41am\",0.00,71.29,71.60,71.15,1498042\r\n\"AXP\",63.07,\"6/11/2007\",\"11:41am\",+0.03,62.79,63.14,62.42,1760630\r\n\"BA\",98.06,\"6/11/2007\",\"11:41am\",-0.13,98.25,98.79,97.74,968400\r\n\"C\",53.41,\"6/11/2007\",\"11:41am\",+0.08,53.20,53.41,52.81,3275394\r\n\"CAT\",78.72,\"6/11/2007\",\"11:41am\",+0.20,78.32,78.99,78.06,1165852\r\n\"DD\",50.77,\"6/11/2007\",\"11:41am\",-0.36,51.13,51.21,50.59,1523000\r\n\"DIS\",34.19,\"6/11/2007\",\"11:41am\",-0.01,34.28,34.44,34.12,2223450\r\n\"GE\",37.42,\"6/11/2007\",\"11:41am\",+0.10,37.07,37.44,37.05,9239801\r\n\"GM\",31.30,\"6/11/2007\",\"11:41am\",+0.30,31.00,31.62,30.90,5699079\r\n\"HD\",37.75,\"6/11/2007\",\"11:41am\",-0.20,37.78,37.83,37.62,3307869\r\n\"HON\",56.99,\"6/11/2007\",\"11:41am\",-0.39,57.25,57.40,56.91,1376642\r\n\"HPQ\",45.96,\"6/11/2007\",\"11:41am\",+0.26,45.80,46.29,45.46,3706756\r\n\"IBM\",103.44,\"6/11/2007\",\"11:41am\",+0.37,102.87,103.63,102.50,1880700\r\n\"INTC\",21.97,\"6/11/2007\",\"11:45am\",+0.14,21.70,21.98,21.69,16406027\r\n\"JNJ\",62.64,\"6/11/2007\",\"11:41am\",+0.51,62.89,62.89,62.15,2899022\r\n\"JPM\",50.50,\"6/11/2007\",\"11:41am\",+0.09,50.41,50.55,50.05,2715800\r\n\"KO\",51.45,\"6/11/2007\",\"11:41am\",-0.22,51.67,51.82,51.32,5260042\r\n\"MCD\",51.49,\"6/11/2007\",\"11:40am\",+0.08,51.47,51.62,50.98,2029314\r\n\"MMM\",85.52,\"6/11/2007\",\"11:41am\",-0.42,85.94,85.98,85.39,843500\r\n\"MO\",70.47,\"6/11/2007\",\"11:41am\",+0.17,70.25,70.50,69.76,3705557\r\n\"MRK\",50.65,\"6/11/2007\",\"11:41am\",+0.51,50.30,50.65,50.04,4091400\r\n\"MSFT\",30.135,\"6/11/2007\",\"11:46am\",+0.085,30.05,30.25,29.93,17741848\r\n\"PFE\",26.42,\"6/11/2007\",\"11:41am\",-0.10,26.50,26.53,26.31,7347396\r\n\"PG\",63.00,\"6/11/2007\",\"11:41am\",-0.07,62.80,63.12,62.75,2256146\r\n\"T\",40.09,\"6/11/2007\",\"11:41am\",-0.17,40.20,40.25,39.89,4805400\r\n\"UTX\",69.82,\"6/11/2007\",\"11:40am\",-0.41,69.85,70.20,69.51,796500\r\n\"VZ\",43.23,\"6/11/2007\",\"11:40am\",+0.16,42.95,43.23,42.89,3077145\r\n\"WMT\",49.81,\"6/11/2007\",\"11:41am\",-0.27,49.90,50.00,49.55,3532333\r\n\"XOM\",83.18,\"6/11/2007\",\"11:41am\",+0.50,82.68,83.18,82.35,4455700\r\n\"AA\",39.54,\"6/11/2007\",\"11:46am\",-0.12,39.67,40.18,39.43,1499880\r\n\"AIG\",71.59,\"6/11/2007\",\"11:45am\",+0.06,71.29,71.61,71.15,1522942\r\n\"AXP\",63.09,\"6/11/2007\",\"11:46am\",+0.05,62.79,63.14,62.42,1799530\r\n\"BA\",98.01,\"6/11/2007\",\"11:46am\",-0.18,98.25,98.79,97.74,1016800\r\n\"C\",53.48,\"6/11/2007\",\"11:46am\",+0.15,53.20,53.52,52.81,3465694\r\n\"CAT\",78.7346,\"6/11/2007\",\"11:45am\",+0.2146,78.32,78.99,78.06,1189152\r\n\"DD\",50.72,\"6/11/2007\",\"11:46am\",-0.41,51.13,51.21,50.59,1542800\r\n\"DIS\",34.202,\"6/11/2007\",\"11:46am\",+0.002,34.28,34.44,34.12,2263250\r\n\"GE\",37.43,\"6/11/2007\",\"11:46am\",+0.11,37.07,37.44,37.05,9402901\r\n\"GM\",31.30,\"6/11/2007\",\"11:46am\",+0.30,31.00,31.62,30.90,5791779\r\n\"HD\",37.74,\"6/11/2007\",\"11:46am\",-0.21,37.78,37.83,37.62,3438769\r\n\"HON\",57.01,\"6/11/2007\",\"11:45am\",-0.37,57.25,57.40,56.91,1411942\r\n\"HPQ\",45.99,\"6/11/2007\",\"11:46am\",+0.29,45.80,46.29,45.46,3780456\r\n\"IBM\",103.53,\"6/11/2007\",\"11:46am\",+0.46,102.87,103.63,102.50,1931500\r\n\"INTC\",21.98,\"6/11/2007\",\"11:51am\",+0.15,21.70,21.98,21.69,16615722\r\n\"JNJ\",62.64,\"6/11/2007\",\"11:45am\",+0.51,62.89,62.89,62.15,2931522\r\n\"JPM\",50.52,\"6/11/2007\",\"11:46am\",+0.11,50.41,50.55,50.05,2820200\r\n\"KO\",51.48,\"6/11/2007\",\"11:45am\",-0.19,51.67,51.82,51.32,5289842\r\n\"MCD\",51.54,\"6/11/2007\",\"11:45am\",+0.13,51.47,51.62,50.98,2063014\r\n\"MMM\",85.53,\"6/11/2007\",\"11:46am\",-0.41,85.94,85.98,85.39,860900\r\n\"MO\",70.30,\"6/11/2007\",\"11:46am\",0.00,70.25,70.50,69.76,3796557\r\n\"MRK\",50.73,\"6/11/2007\",\"11:46am\",+0.59,50.30,50.74,50.04,4201600\r\n\"MSFT\",30.1603,\"6/11/2007\",\"11:50am\",+0.1103,30.05,30.25,29.93,18232994\r\n\"PFE\",26.43,\"6/11/2007\",\"11:46am\",-0.09,26.50,26.53,26.31,7612046\r\n\"PG\",62.95,\"6/11/2007\",\"11:45am\",-0.12,62.80,63.12,62.75,2315446\r\n\"T\",40.14,\"6/11/2007\",\"11:46am\",-0.12,40.20,40.25,39.89,4928500\r\n\"UTX\",69.83,\"6/11/2007\",\"11:46am\",-0.40,69.85,70.20,69.51,808300\r\n\"VZ\",43.25,\"6/11/2007\",\"11:45am\",+0.18,42.95,43.26,42.89,3194645\r\n\"WMT\",49.81,\"6/11/2007\",\"11:46am\",-0.27,49.90,50.00,49.55,3691733\r\n\"XOM\",83.20,\"6/11/2007\",\"11:46am\",+0.52,82.68,83.25,82.35,4596200\r\n\"AA\",39.59,\"6/11/2007\",\"11:51am\",-0.07,39.67,40.18,39.43,1549580\r\n\"AIG\",71.62,\"6/11/2007\",\"11:51am\",+0.09,71.29,71.68,71.15,1587442\r\n\"AXP\",63.09,\"6/11/2007\",\"11:51am\",+0.05,62.79,63.19,62.42,1825430\r\n\"BA\",97.98,\"6/11/2007\",\"11:50am\",-0.21,98.25,98.79,97.74,1036600\r\n\"C\",53.52,\"6/11/2007\",\"11:50am\",+0.19,53.20,53.57,52.81,3665594\r\n\"CAT\",78.78,\"6/11/2007\",\"11:51am\",+0.26,78.32,78.99,78.06,1234052\r\n\"DD\",50.74,\"6/11/2007\",\"11:51am\",-0.39,51.13,51.21,50.59,1579200\r\n\"DIS\",34.23,\"6/11/2007\",\"11:51am\",+0.03,34.28,34.44,34.12,2335050\r\n\"GE\",37.469,\"6/11/2007\",\"11:51am\",+0.149,37.07,37.48,37.05,9637101\r\n\"GM\",31.31,\"6/11/2007\",\"11:50am\",+0.31,31.00,31.62,30.90,5900779\r\n\"HD\",37.77,\"6/11/2007\",\"11:51am\",-0.18,37.78,37.83,37.62,3574469\r\n\"HON\",56.99,\"6/11/2007\",\"11:50am\",-0.39,57.25,57.40,56.91,1469142\r\n\"HPQ\",46.0616,\"6/11/2007\",\"11:50am\",+0.3616,45.80,46.29,45.46,3886556\r\n\"IBM\",103.70,\"6/11/2007\",\"11:51am\",+0.63,102.87,103.71,102.50,1993700\r\n\"INTC\",22.01,\"6/11/2007\",\"11:56am\",+0.18,21.70,22.01,21.69,17848980\r\n\"JNJ\",62.719,\"6/11/2007\",\"11:51am\",+0.589,62.89,62.89,62.15,3194722\r\n\"JPM\",50.53,\"6/11/2007\",\"11:51am\",+0.12,50.41,50.55,50.05,2963700\r\n\"KO\",51.50,\"6/11/2007\",\"11:50am\",-0.17,51.67,51.82,51.32,5313142\r\n\"MCD\",51.52,\"6/11/2007\",\"11:51am\",+0.11,51.47,51.62,50.98,2105814\r\n\"MMM\",85.53,\"6/11/2007\",\"11:50am\",-0.41,85.94,85.98,85.39,879300\r\n\"MO\",70.43,\"6/11/2007\",\"11:50am\",+0.13,70.25,70.50,69.76,3873057\r\n\"MRK\",50.78,\"6/11/2007\",\"11:51am\",+0.64,50.30,50.87,50.04,4316300\r\n\"MSFT\",30.18,\"6/11/2007\",\"11:56am\",+0.13,30.05,30.25,29.93,18458900\r\n\"PFE\",26.50,\"6/11/2007\",\"11:51am\",-0.02,26.50,26.53,26.31,7882446\r\n\"PG\",63.04,\"6/11/2007\",\"11:50am\",-0.03,62.80,63.12,62.75,2384246\r\n\"T\",40.15,\"6/11/2007\",\"11:51am\",-0.11,40.20,40.25,39.89,5143600\r\n\"UTX\",69.85,\"6/11/2007\",\"11:50am\",-0.38,69.85,70.20,69.51,824600\r\n\"VZ\",43.29,\"6/11/2007\",\"11:51am\",+0.22,42.95,43.30,42.89,3253445\r\n\"WMT\",49.84,\"6/11/2007\",\"11:51am\",-0.24,49.90,50.00,49.55,3812233\r\n\"XOM\",83.26,\"6/11/2007\",\"11:51am\",+0.58,82.68,83.35,82.35,4722700\r\n\"AA\",39.62,\"6/11/2007\",\"11:56am\",-0.04,39.67,40.18,39.43,1573080\r\n\"AIG\",71.6254,\"6/11/2007\",\"11:56am\",+0.0954,71.29,71.68,71.15,1630242\r\n\"AXP\",63.14,\"6/11/2007\",\"11:56am\",+0.10,62.79,63.19,62.42,1839830\r\n\"BA\",97.97,\"6/11/2007\",\"11:55am\",-0.22,98.25,98.79,97.74,1056000\r\n\"C\",53.58,\"6/11/2007\",\"11:56am\",+0.25,53.20,53.58,52.81,3892094\r\n\"CAT\",78.76,\"6/11/2007\",\"11:55am\",+0.24,78.32,78.99,78.06,1252852\r\n\"DD\",50.73,\"6/11/2007\",\"11:56am\",-0.40,51.13,51.21,50.59,1632600\r\n\"DIS\",34.24,\"6/11/2007\",\"11:56am\",+0.04,34.28,34.44,34.12,2414850\r\n\"GE\",37.44,\"6/11/2007\",\"11:56am\",+0.12,37.07,37.49,37.05,9766201\r\n\"GM\",31.32,\"6/11/2007\",\"11:56am\",+0.32,31.00,31.62,30.90,5949301\r\n\"HD\",37.74,\"6/11/2007\",\"11:56am\",-0.21,37.78,37.83,37.62,3692669\r\n\"HON\",57.00,\"6/11/2007\",\"11:56am\",-0.38,57.25,57.40,56.91,1521042\r\n\"HPQ\",46.10,\"6/11/2007\",\"11:56am\",+0.40,45.80,46.29,45.46,4001456\r\n\"IBM\",103.62,\"6/11/2007\",\"11:56am\",+0.55,102.87,103.71,102.50,2026100\r\n\"INTC\",22.00,\"6/11/2007\",\"12:01pm\",+0.17,21.70,22.01,21.69,18388938\r\n\"JNJ\",62.66,\"6/11/2007\",\"11:55am\",+0.53,62.89,62.89,62.15,3236322\r\n\"JPM\",50.54,\"6/11/2007\",\"11:56am\",+0.13,50.41,50.56,50.05,3019600\r\n\"KO\",51.52,\"6/11/2007\",\"11:55am\",-0.15,51.67,51.82,51.32,5345042\r\n\"MCD\",51.53,\"6/11/2007\",\"11:56am\",+0.12,51.47,51.62,50.98,2134514\r\n\"MMM\",85.44,\"6/11/2007\",\"11:56am\",-0.50,85.94,85.98,85.39,905400\r\n\"MO\",70.42,\"6/11/2007\",\"11:56am\",+0.12,70.25,70.50,69.76,3926257\r\n\"MRK\",50.79,\"6/11/2007\",\"11:56am\",+0.65,50.30,50.87,50.04,4401700\r\n\"MSFT\",30.18,\"6/11/2007\",\"12:01pm\",+0.13,30.05,30.25,29.93,18749244\r\n\"PFE\",26.46,\"6/11/2007\",\"11:56am\",-0.06,26.50,26.53,26.31,8528842\r\n\"PG\",63.04,\"6/11/2007\",\"11:56am\",-0.03,62.80,63.12,62.75,2418246\r\n\"T\",40.16,\"6/11/2007\",\"11:56am\",-0.10,40.20,40.25,39.89,5294500\r\n\"UTX\",69.8227,\"6/11/2007\",\"11:56am\",-0.4073,69.85,70.20,69.51,840000\r\n\"VZ\",43.28,\"6/11/2007\",\"11:56am\",+0.21,42.95,43.31,42.89,3317345\r\n\"WMT\",49.87,\"6/11/2007\",\"11:56am\",-0.21,49.90,50.00,49.55,3928833\r\n\"XOM\",83.30,\"6/11/2007\",\"11:56am\",+0.62,82.68,83.39,82.35,4932400\r\n\"AA\",39.615,\"6/11/2007\",\"12:00pm\",-0.045,39.67,40.18,39.43,1587980\r\n\"AIG\",71.63,\"6/11/2007\",\"12:01pm\",+0.10,71.29,71.68,71.15,1669942\r\n\"AXP\",63.142,\"6/11/2007\",\"12:01pm\",+0.102,62.79,63.19,62.42,1852230\r\n\"BA\",98.00,\"6/11/2007\",\"12:01pm\",-0.19,98.25,98.79,97.74,1079000\r\n\"C\",53.53,\"6/11/2007\",\"12:01pm\",+0.20,53.20,53.58,52.81,3992194\r\n\"CAT\",78.76,\"6/11/2007\",\"12:01pm\",+0.24,78.32,78.99,78.06,1270152\r\n\"DD\",50.69,\"6/11/2007\",\"12:01pm\",-0.44,51.13,51.21,50.59,1671400\r\n\"DIS\",34.21,\"6/11/2007\",\"12:00pm\",+0.01,34.28,34.44,34.12,2450550\r\n\"GE\",37.425,\"6/11/2007\",\"12:01pm\",+0.105,37.07,37.49,37.05,9895501\r\n\"GM\",31.32,\"6/11/2007\",\"12:00pm\",+0.32,31.00,31.62,30.90,5989901\r\n\"HD\",37.75,\"6/11/2007\",\"12:01pm\",-0.20,37.78,37.83,37.62,3816469\r\n\"HON\",57.00,\"6/11/2007\",\"12:01pm\",-0.38,57.25,57.40,56.91,1543942\r\n\"HPQ\",46.12,\"6/11/2007\",\"12:01pm\",+0.42,45.80,46.29,45.46,4076156\r\n\"IBM\",103.63,\"6/11/2007\",\"12:00pm\",+0.56,102.87,103.71,102.50,2060500\r\n\"INTC\",21.99,\"6/11/2007\",\"12:06pm\",+0.16,21.70,22.02,21.69,18564224\r\n\"JNJ\",62.631,\"6/11/2007\",\"12:01pm\",+0.501,62.89,62.89,62.15,3282922\r\n\"JPM\",50.60,\"6/11/2007\",\"12:01pm\",+0.19,50.41,50.60,50.05,3118700\r\n\"KO\",51.53,\"6/11/2007\",\"12:00pm\",-0.14,51.67,51.82,51.32,5393742\r\n\"MCD\",51.50,\"6/11/2007\",\"12:01pm\",+0.09,51.47,51.62,50.98,2171014\r\n\"MMM\",85.43,\"6/11/2007\",\"12:00pm\",-0.51,85.94,85.98,85.39,922700\r\n\"MO\",70.40,\"6/11/2007\",\"12:01pm\",+0.10,70.25,70.50,69.76,3957957\r\n\"MRK\",50.7475,\"6/11/2007\",\"12:01pm\",+0.6075,50.30,50.87,50.04,4475600\r\n\"MSFT\",30.1597,\"6/11/2007\",\"12:06pm\",+0.1097,30.05,30.25,29.93,18960388\r\n\"PFE\",26.46,\"6/11/2007\",\"12:01pm\",-0.06,26.50,26.53,26.31,8624142\r\n\"PG\",63.03,\"6/11/2007\",\"12:01pm\",-0.04,62.80,63.12,62.75,2475246\r\n\"T\",40.20,\"6/11/2007\",\"12:01pm\",-0.06,40.20,40.25,39.89,5445500\r\n\"UTX\",69.83,\"6/11/2007\",\"12:00pm\",-0.40,69.85,70.20,69.51,860200\r\n\"VZ\",43.28,\"6/11/2007\",\"12:00pm\",+0.21,42.95,43.31,42.89,3361245\r\n\"WMT\",49.87,\"6/11/2007\",\"12:01pm\",-0.21,49.90,50.00,49.55,4143733\r\n\"XOM\",83.40,\"6/11/2007\",\"12:01pm\",+0.72,82.68,83.46,82.35,5119800\r\n\"AA\",39.61,\"6/11/2007\",\"12:06pm\",-0.05,39.67,40.18,39.43,1627080\r\n\"AIG\",71.68,\"6/11/2007\",\"12:06pm\",+0.15,71.29,71.68,71.15,1706542\r\n\"AXP\",63.0546,\"6/11/2007\",\"12:05pm\",+0.0146,62.79,63.19,62.42,1880930\r\n\"BA\",97.92,\"6/11/2007\",\"12:06pm\",-0.27,98.25,98.79,97.74,1090100\r\n\"C\",53.55,\"6/11/2007\",\"12:06pm\",+0.22,53.20,53.65,52.81,4319594\r\n\"CAT\",78.74,\"6/11/2007\",\"12:06pm\",+0.22,78.32,78.99,78.06,1288152\r\n\"DD\",50.70,\"6/11/2007\",\"12:06pm\",-0.43,51.13,51.21,50.59,1732500\r\n\"DIS\",34.22,\"6/11/2007\",\"12:06pm\",+0.02,34.28,34.44,34.12,2493950\r\n\"GE\",37.4227,\"6/11/2007\",\"12:06pm\",+0.1027,37.07,37.49,37.05,10023701\r\n\"GM\",31.32,\"6/11/2007\",\"12:06pm\",+0.32,31.00,31.62,30.90,6029201\r\n\"HD\",37.7401,\"6/11/2007\",\"12:06pm\",-0.2099,37.78,37.83,37.62,3862469\r\n\"HON\",57.00,\"6/11/2007\",\"12:06pm\",-0.38,57.25,57.40,56.91,1627642\r\n\"HPQ\",46.10,\"6/11/2007\",\"12:06pm\",+0.40,45.80,46.29,45.46,4113656\r\n\"IBM\",103.67,\"6/11/2007\",\"12:06pm\",+0.60,102.87,103.71,102.50,2085800\r\n\"INTC\",21.99,\"6/11/2007\",\"12:11pm\",+0.16,21.70,22.02,21.69,18898568\r\n\"JNJ\",62.62,\"6/11/2007\",\"12:06pm\",+0.49,62.89,62.89,62.15,3318522\r\n\"JPM\",50.60,\"6/11/2007\",\"12:06pm\",+0.19,50.41,50.62,50.05,3165700\r\n\"KO\",51.52,\"6/11/2007\",\"12:05pm\",-0.15,51.67,51.82,51.32,5416242\r\n\"MCD\",51.47,\"6/11/2007\",\"12:06pm\",+0.06,51.47,51.62,50.98,2192514\r\n\"MMM\",85.41,\"6/11/2007\",\"12:05pm\",-0.53,85.94,85.98,85.39,941800\r\n\"MO\",70.41,\"6/11/2007\",\"12:06pm\",+0.11,70.25,70.50,69.76,3992557\r\n\"MRK\",50.75,\"6/11/2007\",\"12:06pm\",+0.61,50.30,50.87,50.04,4518500\r\n\"MSFT\",30.13,\"6/11/2007\",\"12:11pm\",+0.08,30.05,30.25,29.93,19296222\r\n\"PFE\",26.43,\"6/11/2007\",\"12:06pm\",-0.09,26.50,26.53,26.31,8765142\r\n\"PG\",63.02,\"6/11/2007\",\"12:06pm\",-0.05,62.80,63.12,62.75,2498846\r\n\"T\",40.18,\"6/11/2007\",\"12:06pm\",-0.08,40.20,40.25,39.89,5544800\r\n\"UTX\",69.89,\"6/11/2007\",\"12:05pm\",-0.34,69.85,70.20,69.51,873700\r\n\"VZ\",43.32,\"6/11/2007\",\"12:06pm\",+0.25,42.95,43.34,42.89,3454720\r\n\"WMT\",49.84,\"6/11/2007\",\"12:06pm\",-0.24,49.90,50.00,49.55,4217433\r\n\"XOM\",83.40,\"6/11/2007\",\"12:06pm\",+0.72,82.68,83.48,82.35,5317100\r\n\"AA\",39.57,\"6/11/2007\",\"12:11pm\",-0.09,39.67,40.18,39.43,1657480\r\n\"AIG\",71.68,\"6/11/2007\",\"12:11pm\",+0.15,71.29,71.70,71.15,1754142\r\n\"AXP\",63.01,\"6/11/2007\",\"12:11pm\",-0.03,62.79,63.19,62.42,1905230\r\n\"BA\",97.856,\"6/11/2007\",\"12:11pm\",-0.334,98.25,98.79,97.74,1115200\r\n\"C\",53.57,\"6/11/2007\",\"12:11pm\",+0.24,53.20,53.65,52.81,4395794\r\n\"CAT\",78.82,\"6/11/2007\",\"12:10pm\",+0.30,78.32,78.99,78.06,1308952\r\n\"DD\",50.71,\"6/11/2007\",\"12:10pm\",-0.42,51.13,51.21,50.59,1760700\r\n\"DIS\",34.23,\"6/11/2007\",\"12:11pm\",+0.03,34.28,34.44,34.12,2537850\r\n\"GE\",37.4025,\"6/11/2007\",\"12:11pm\",+0.0825,37.07,37.49,37.05,10173701\r\n\"GM\",31.27,\"6/11/2007\",\"12:11pm\",+0.27,31.00,31.62,30.90,6100601\r\n\"HD\",37.75,\"6/11/2007\",\"12:11pm\",-0.20,37.78,37.83,37.62,4021569\r\n\"HON\",57.00,\"6/11/2007\",\"12:11pm\",-0.38,57.25,57.40,56.91,1666742\r\n\"HPQ\",46.08,\"6/11/2007\",\"12:10pm\",+0.38,45.80,46.29,45.46,4173956\r\n\"IBM\",103.56,\"6/11/2007\",\"12:11pm\",+0.49,102.87,103.71,102.50,2111700\r\n\"INTC\",21.98,\"6/11/2007\",\"12:16pm\",+0.15,21.70,22.02,21.69,18999740\r\n\"JNJ\",62.56,\"6/11/2007\",\"12:11pm\",+0.43,62.89,62.89,62.15,3392622\r\n\"JPM\",50.60,\"6/11/2007\",\"12:11pm\",+0.19,50.41,50.62,50.05,3283000\r\n\"KO\",51.53,\"6/11/2007\",\"12:11pm\",-0.14,51.67,51.82,51.32,5438042\r\n\"MCD\",51.455,\"6/11/2007\",\"12:11pm\",+0.045,51.47,51.62,50.98,2250714\r\n\"MMM\",85.32,\"6/11/2007\",\"12:11pm\",-0.62,85.94,85.98,85.32,975500\r\n\"MO\",70.38,\"6/11/2007\",\"12:11pm\",+0.08,70.25,70.50,69.76,4010457\r\n\"MRK\",50.75,\"6/11/2007\",\"12:10pm\",+0.61,50.30,50.87,50.04,4599200\r\n\"MSFT\",30.13,\"6/11/2007\",\"12:16pm\",+0.08,30.05,30.25,29.93,19453430\r\n\"PFE\",26.38,\"6/11/2007\",\"12:11pm\",-0.14,26.50,26.53,26.31,9435387\r\n\"PG\",63.01,\"6/11/2007\",\"12:11pm\",-0.06,62.80,63.12,62.75,2550946\r\n\"T\",40.13,\"6/11/2007\",\"12:11pm\",-0.13,40.20,40.25,39.89,5619200\r\n\"UTX\",69.89,\"6/11/2007\",\"12:11pm\",-0.34,69.85,70.20,69.51,893500\r\n\"VZ\",43.31,\"6/11/2007\",\"12:11pm\",+0.24,42.95,43.34,42.89,3525820\r\n\"WMT\",49.86,\"6/11/2007\",\"12:11pm\",-0.22,49.90,50.00,49.55,4320433\r\n\"XOM\",83.38,\"6/11/2007\",\"12:11pm\",+0.70,82.68,83.48,82.35,5384300\r\n\"AA\",39.51,\"6/11/2007\",\"12:15pm\",-0.15,39.67,40.18,39.43,1707780\r\n\"AIG\",71.68,\"6/11/2007\",\"12:16pm\",+0.15,71.29,71.70,71.15,1797642\r\n\"AXP\",62.98,\"6/11/2007\",\"12:15pm\",-0.06,62.79,63.19,62.42,1919830\r\n\"BA\",97.81,\"6/11/2007\",\"12:16pm\",-0.38,98.25,98.79,97.74,1189800\r\n\"C\",53.51,\"6/11/2007\",\"12:15pm\",+0.18,53.20,53.65,52.81,4582294\r\n\"CAT\",78.87,\"6/11/2007\",\"12:16pm\",+0.35,78.32,78.99,78.06,1321852\r\n\"DD\",50.69,\"6/11/2007\",\"12:16pm\",-0.44,51.13,51.21,50.59,1790800\r\n\"DIS\",34.21,\"6/11/2007\",\"12:16pm\",+0.01,34.28,34.44,34.12,2586450\r\n\"GE\",37.40,\"6/11/2007\",\"12:16pm\",+0.08,37.07,37.49,37.05,10295201\r\n\"GM\",31.27,\"6/11/2007\",\"12:16pm\",+0.27,31.00,31.62,30.90,6373201\r\n\"HD\",37.752,\"6/11/2007\",\"12:16pm\",-0.198,37.78,37.83,37.62,4072169\r\n\"HON\",57.00,\"6/11/2007\",\"12:16pm\",-0.38,57.25,57.40,56.91,1683042\r\n\"HPQ\",46.06,\"6/11/2007\",\"12:15pm\",+0.36,45.80,46.29,45.46,4225556\r\n\"IBM\",103.54,\"6/11/2007\",\"12:16pm\",+0.47,102.87,103.71,102.50,2128600\r\n\"INTC\",21.98,\"6/11/2007\",\"12:21pm\",+0.15,21.70,22.02,21.69,19143976\r\n\"JNJ\",62.57,\"6/11/2007\",\"12:16pm\",+0.44,62.89,62.89,62.15,3439222\r\n\"JPM\",50.59,\"6/11/2007\",\"12:16pm\",+0.18,50.41,50.62,50.05,3372000\r\n\"KO\",51.5418,\"6/11/2007\",\"12:16pm\",-0.1282,51.67,51.82,51.32,5475442\r\n\"MCD\",51.44,\"6/11/2007\",\"12:16pm\",+0.03,51.47,51.62,50.98,2321914\r\n\"MMM\",85.34,\"6/11/2007\",\"12:16pm\",-0.60,85.94,85.98,85.32,985300\r\n\"MO\",70.31,\"6/11/2007\",\"12:15pm\",+0.01,70.25,70.50,69.76,4067257\r\n\"MRK\",50.74,\"6/11/2007\",\"12:16pm\",+0.60,50.30,50.87,50.04,4655400\r\n\"MSFT\",30.10,\"6/11/2007\",\"12:21pm\",+0.05,30.05,30.25,29.93,19617530\r\n\"PFE\",26.38,\"6/11/2007\",\"12:16pm\",-0.14,26.50,26.53,26.31,9916032\r\n\"PG\",62.99,\"6/11/2007\",\"12:16pm\",-0.08,62.80,63.12,62.75,2626746\r\n\"T\",40.10,\"6/11/2007\",\"12:16pm\",-0.16,40.20,40.25,39.89,5730400\r\n\"UTX\",69.89,\"6/11/2007\",\"12:15pm\",-0.34,69.85,70.20,69.51,919000\r\n\"VZ\",43.252,\"6/11/2007\",\"12:16pm\",+0.182,42.95,43.34,42.89,3563720\r\n\"WMT\",49.882,\"6/11/2007\",\"12:16pm\",-0.198,49.90,50.00,49.55,4432033\r\n\"XOM\",83.37,\"6/11/2007\",\"12:16pm\",+0.69,82.68,83.48,82.35,5492200\r\n\"AA\",39.48,\"6/11/2007\",\"12:21pm\",-0.18,39.67,40.18,39.43,1815880\r\n\"AIG\",71.71,\"6/11/2007\",\"12:21pm\",+0.18,71.29,71.71,71.15,1835142\r\n\"AXP\",62.92,\"6/11/2007\",\"12:20pm\",-0.12,62.79,63.19,62.42,1934230\r\n\"BA\",97.79,\"6/11/2007\",\"12:21pm\",-0.40,98.25,98.79,97.74,1228800\r\n\"C\",53.53,\"6/11/2007\",\"12:21pm\",+0.20,53.20,53.65,52.81,4665694\r\n\"CAT\",78.84,\"6/11/2007\",\"12:20pm\",+0.32,78.32,78.99,78.06,1332852\r\n\"DD\",50.69,\"6/11/2007\",\"12:21pm\",-0.44,51.13,51.21,50.59,1803100\r\n\"DIS\",34.21,\"6/11/2007\",\"12:21pm\",+0.01,34.28,34.44,34.12,2632250\r\n\"GE\",37.41,\"6/11/2007\",\"12:21pm\",+0.09,37.07,37.49,37.05,10406001\r\n\"GM\",31.29,\"6/11/2007\",\"12:21pm\",+0.29,31.00,31.62,30.90,6432201\r\n\"HD\",37.75,\"6/11/2007\",\"12:20pm\",-0.20,37.78,37.83,37.62,5032769\r\n\"HON\",56.98,\"6/11/2007\",\"12:20pm\",-0.40,57.25,57.40,56.91,1693442\r\n\"HPQ\",46.03,\"6/11/2007\",\"12:21pm\",+0.33,45.80,46.29,45.46,4304056\r\n\"IBM\",103.56,\"6/11/2007\",\"12:20pm\",+0.49,102.87,103.71,102.50,2151600\r\n\"INTC\",21.963,\"6/11/2007\",\"12:26pm\",+0.133,21.70,22.02,21.69,19623448\r\n\"JNJ\",62.56,\"6/11/2007\",\"12:21pm\",+0.43,62.89,62.89,62.15,3517522\r\n\"JPM\",50.58,\"6/11/2007\",\"12:21pm\",+0.17,50.41,50.62,50.05,3435300\r\n\"KO\",51.52,\"6/11/2007\",\"12:21pm\",-0.15,51.67,51.82,51.32,5546242\r\n\"MCD\",51.40,\"6/11/2007\",\"12:20pm\",-0.01,51.47,51.62,50.98,2357014\r\n\"MMM\",85.33,\"6/11/2007\",\"12:20pm\",-0.61,85.94,85.98,85.32,996200\r\n\"MO\",70.27,\"6/11/2007\",\"12:21pm\",-0.03,70.25,70.50,69.76,4106257\r\n\"MRK\",50.72,\"6/11/2007\",\"12:21pm\",+0.58,50.30,50.87,50.04,4713700\r\n\"MSFT\",30.12,\"6/11/2007\",\"12:26pm\",+0.07,30.05,30.25,29.93,19901670\r\n\"PFE\",26.37,\"6/11/2007\",\"12:21pm\",-0.15,26.50,26.53,26.31,10036232\r\n\"PG\",63.01,\"6/11/2007\",\"12:20pm\",-0.06,62.80,63.12,62.75,2656246\r\n\"T\",40.09,\"6/11/2007\",\"12:21pm\",-0.17,40.20,40.25,39.89,5849000\r\n\"UTX\",69.80,\"6/11/2007\",\"12:20pm\",-0.43,69.85,70.20,69.51,935600\r\n\"VZ\",43.24,\"6/11/2007\",\"12:20pm\",+0.17,42.95,43.34,42.89,3608620\r\n\"WMT\",49.87,\"6/11/2007\",\"12:21pm\",-0.21,49.90,50.00,49.55,4485833\r\n\"XOM\",83.39,\"6/11/2007\",\"12:21pm\",+0.71,82.68,83.48,82.35,5559000\r\n\"AA\",39.49,\"6/11/2007\",\"12:26pm\",-0.17,39.67,40.18,39.43,1856980\r\n\"AIG\",71.73,\"6/11/2007\",\"12:26pm\",+0.20,71.29,71.74,71.15,1875942\r\n\"AXP\",62.90,\"6/11/2007\",\"12:26pm\",-0.14,62.79,63.19,62.42,1944730\r\n\"BA\",97.75,\"6/11/2007\",\"12:26pm\",-0.44,98.25,98.79,97.74,1241700\r\n\"C\",53.55,\"6/11/2007\",\"12:26pm\",+0.22,53.20,53.65,52.81,4736994\r\n\"CAT\",78.87,\"6/11/2007\",\"12:25pm\",+0.35,78.32,78.99,78.06,1347152\r\n\"DD\",50.70,\"6/11/2007\",\"12:26pm\",-0.43,51.13,51.21,50.59,1812800\r\n\"DIS\",34.20,\"6/11/2007\",\"12:26pm\",0.00,34.28,34.44,34.12,2667150\r\n\"GE\",37.41,\"6/11/2007\",\"12:26pm\",+0.09,37.07,37.49,37.05,10594701\r\n\"GM\",31.27,\"6/11/2007\",\"12:26pm\",+0.27,31.00,31.62,30.90,6554401\r\n\"HD\",37.73,\"6/11/2007\",\"12:26pm\",-0.22,37.78,37.83,37.62,5061669\r\n\"HON\",56.96,\"6/11/2007\",\"12:26pm\",-0.42,57.25,57.40,56.91,1723242\r\n\"HPQ\",46.02,\"6/11/2007\",\"12:26pm\",+0.32,45.80,46.29,45.46,4379056\r\n\"IBM\",103.64,\"6/11/2007\",\"12:26pm\",+0.57,102.87,103.71,102.50,2198300\r\n\"INTC\",21.97,\"6/11/2007\",\"12:31pm\",+0.14,21.70,22.02,21.69,19931936\r\n\"JNJ\",62.60,\"6/11/2007\",\"12:26pm\",+0.47,62.89,62.89,62.15,3566938\r\n\"JPM\",50.61,\"6/11/2007\",\"12:26pm\",+0.20,50.41,50.62,50.05,3481000\r\n\"KO\",51.55,\"6/11/2007\",\"12:26pm\",-0.12,51.67,51.82,51.32,5589842\r\n\"MCD\",51.37,\"6/11/2007\",\"12:26pm\",-0.04,51.47,51.62,50.98,2377914\r\n\"MMM\",85.33,\"6/11/2007\",\"12:25pm\",-0.61,85.94,85.98,85.32,1000600\r\n\"MO\",70.23,\"6/11/2007\",\"12:26pm\",-0.07,70.25,70.50,69.76,4146657\r\n\"MRK\",50.79,\"6/11/2007\",\"12:26pm\",+0.65,50.30,50.87,50.04,4803500\r\n\"MSFT\",30.145,\"6/11/2007\",\"12:31pm\",+0.095,30.05,30.25,29.93,20108456\r\n\"PFE\",26.38,\"6/11/2007\",\"12:26pm\",-0.14,26.50,26.53,26.31,10177632\r\n\"PG\",62.98,\"6/11/2007\",\"12:26pm\",-0.09,62.80,63.12,62.75,2697146\r\n\"T\",40.12,\"6/11/2007\",\"12:26pm\",-0.14,40.20,40.25,39.89,5959900\r\n\"UTX\",69.80,\"6/11/2007\",\"12:26pm\",-0.43,69.85,70.20,69.51,946900\r\n\"VZ\",43.23,\"6/11/2007\",\"12:26pm\",+0.16,42.95,43.34,42.89,3648320\r\n\"WMT\",49.90,\"6/11/2007\",\"12:26pm\",-0.18,49.90,50.00,49.55,4562633\r\n\"XOM\",83.43,\"6/11/2007\",\"12:26pm\",+0.75,82.68,83.48,82.35,5652500\r\n\"AA\",39.50,\"6/11/2007\",\"12:30pm\",-0.16,39.67,40.18,39.43,1898080\r\n\"AIG\",71.74,\"6/11/2007\",\"12:31pm\",+0.21,71.29,71.74,71.15,1905942\r\n\"AXP\",62.9008,\"6/11/2007\",\"12:31pm\",-0.1392,62.79,63.19,62.42,1968730\r\n\"BA\",97.67,\"6/11/2007\",\"12:30pm\",-0.52,98.25,98.79,97.63,1294900\r\n\"C\",53.57,\"6/11/2007\",\"12:31pm\",+0.24,53.20,53.65,52.81,4767094\r\n\"CAT\",78.89,\"6/11/2007\",\"12:31pm\",+0.37,78.32,78.99,78.06,1360152\r\n\"DD\",50.73,\"6/11/2007\",\"12:31pm\",-0.40,51.13,51.21,50.59,1833000\r\n\"DIS\",34.195,\"6/11/2007\",\"12:31pm\",-0.005,34.28,34.44,34.12,2687550\r\n\"GE\",37.41,\"6/11/2007\",\"12:31pm\",+0.09,37.07,37.49,37.05,10633601\r\n\"GM\",31.25,\"6/11/2007\",\"12:31pm\",+0.25,31.00,31.62,30.90,6609301\r\n\"HD\",37.74,\"6/11/2007\",\"12:30pm\",-0.21,37.78,37.83,37.62,5089969\r\n\"HON\",56.95,\"6/11/2007\",\"12:31pm\",-0.43,57.25,57.40,56.91,1741042\r\n\"HPQ\",46.02,\"6/11/2007\",\"12:30pm\",+0.32,45.80,46.29,45.46,4453056\r\n\"IBM\",103.67,\"6/11/2007\",\"12:31pm\",+0.60,102.87,103.71,102.50,2226700\r\n\"INTC\",21.96,\"6/11/2007\",\"12:36pm\",+0.13,21.70,22.02,21.69,20005174\r\n\"JNJ\",62.60,\"6/11/2007\",\"12:31pm\",+0.47,62.89,62.89,62.15,3584438\r\n\"JPM\",50.60,\"6/11/2007\",\"12:31pm\",+0.19,50.41,50.62,50.05,3513000\r\n\"KO\",51.56,\"6/11/2007\",\"12:30pm\",-0.11,51.67,51.82,51.32,5620842\r\n\"MCD\",51.34,\"6/11/2007\",\"12:31pm\",-0.07,51.47,51.62,50.98,2442514\r\n\"MMM\",85.28,\"6/11/2007\",\"12:30pm\",-0.66,85.94,85.98,85.28,1017900\r\n\"MO\",70.24,\"6/11/2007\",\"12:30pm\",-0.06,70.25,70.50,69.76,4185257\r\n\"MRK\",50.84,\"6/11/2007\",\"12:31pm\",+0.70,50.30,50.87,50.04,4848100\r\n\"MSFT\",30.14,\"6/11/2007\",\"12:36pm\",+0.09,30.05,30.25,29.93,20198736\r\n\"PFE\",26.39,\"6/11/2007\",\"12:31pm\",-0.13,26.50,26.53,26.31,10293432\r\n\"PG\",63.00,\"6/11/2007\",\"12:31pm\",-0.07,62.80,63.12,62.75,2745846\r\n\"T\",40.07,\"6/11/2007\",\"12:31pm\",-0.19,40.20,40.25,39.89,6112900\r\n\"UTX\",69.81,\"6/11/2007\",\"12:31pm\",-0.42,69.85,70.20,69.51,958400\r\n\"VZ\",43.27,\"6/11/2007\",\"12:31pm\",+0.20,42.95,43.34,42.89,3693720\r\n\"WMT\",49.92,\"6/11/2007\",\"12:31pm\",-0.16,49.90,50.00,49.55,4676833\r\n\"XOM\",83.46,\"6/11/2007\",\"12:31pm\",+0.78,82.68,83.48,82.35,5711300\r\n\"AA\",39.47,\"6/11/2007\",\"12:35pm\",-0.19,39.67,40.18,39.43,1938380\r\n\"AIG\",71.705,\"6/11/2007\",\"12:35pm\",+0.175,71.29,71.76,71.15,1958142\r\n\"AXP\",62.91,\"6/11/2007\",\"12:36pm\",-0.13,62.79,63.19,62.42,1982630\r\n\"BA\",97.65,\"6/11/2007\",\"12:35pm\",-0.54,98.25,98.79,97.61,1324100\r\n\"C\",53.56,\"6/11/2007\",\"12:36pm\",+0.23,53.20,53.65,52.81,4843994\r\n\"CAT\",78.82,\"6/11/2007\",\"12:36pm\",+0.30,78.32,78.99,78.06,1381852\r\n\"DD\",50.70,\"6/11/2007\",\"12:36pm\",-0.43,51.13,51.21,50.59,1854300\r\n\"DIS\",34.19,\"6/11/2007\",\"12:36pm\",-0.01,34.28,34.44,34.12,2871150\r\n\"GE\",37.41,\"6/11/2007\",\"12:36pm\",+0.09,37.07,37.49,37.05,10703601\r\n\"GM\",31.28,\"6/11/2007\",\"12:36pm\",+0.28,31.00,31.62,30.90,6667301\r\n\"HD\",37.74,\"6/11/2007\",\"12:35pm\",-0.21,37.78,37.83,37.62,5127869\r\n\"HON\",56.93,\"6/11/2007\",\"12:36pm\",-0.45,57.25,57.40,56.91,1772242\r\n\"HPQ\",46.01,\"6/11/2007\",\"12:35pm\",+0.31,45.80,46.29,45.46,4478756\r\n\"IBM\",103.70,\"6/11/2007\",\"12:36pm\",+0.63,102.87,103.73,102.50,2253600\r\n\"INTC\",21.96,\"6/11/2007\",\"12:41pm\",+0.13,21.70,22.02,21.69,20062818\r\n\"JNJ\",62.59,\"6/11/2007\",\"12:36pm\",+0.46,62.89,62.89,62.15,3609538\r\n\"JPM\",50.60,\"6/11/2007\",\"12:36pm\",+0.19,50.41,50.62,50.05,3631000\r\n\"KO\",51.58,\"6/11/2007\",\"12:36pm\",-0.09,51.67,51.82,51.32,5643442\r\n\"MCD\",51.37,\"6/11/2007\",\"12:36pm\",-0.04,51.47,51.62,50.98,2479114\r\n\"MMM\",85.34,\"6/11/2007\",\"12:36pm\",-0.60,85.94,85.98,85.28,1032100\r\n\"MO\",70.32,\"6/11/2007\",\"12:36pm\",+0.02,70.25,70.50,69.76,4255057\r\n\"MRK\",50.90,\"6/11/2007\",\"12:36pm\",+0.76,50.30,50.91,50.04,4953900\r\n\"MSFT\",30.13,\"6/11/2007\",\"12:41pm\",+0.08,30.05,30.25,29.93,20258588\r\n\"PFE\",26.41,\"6/11/2007\",\"12:36pm\",-0.11,26.50,26.53,26.31,10510182\r\n\"PG\",63.03,\"6/11/2007\",\"12:36pm\",-0.04,62.80,63.12,62.75,2776446\r\n\"T\",40.08,\"6/11/2007\",\"12:36pm\",-0.18,40.20,40.25,39.89,6259175\r\n\"UTX\",69.81,\"6/11/2007\",\"12:35pm\",-0.42,69.85,70.20,69.51,964300\r\n\"VZ\",43.28,\"6/11/2007\",\"12:36pm\",+0.21,42.95,43.34,42.89,3720320\r\n\"WMT\",49.94,\"6/11/2007\",\"12:36pm\",-0.14,49.90,50.00,49.55,4837533\r\n\"XOM\",83.45,\"6/11/2007\",\"12:36pm\",+0.77,82.68,83.50,82.35,5799000\r\n\"AA\",39.48,\"6/11/2007\",\"12:41pm\",-0.18,39.67,40.18,39.43,1988580\r\n\"AIG\",71.70,\"6/11/2007\",\"12:40pm\",+0.17,71.29,71.76,71.15,2020642\r\n\"AXP\",62.91,\"6/11/2007\",\"12:41pm\",-0.13,62.79,63.19,62.42,1993830\r\n\"BA\",97.73,\"6/11/2007\",\"12:40pm\",-0.46,98.25,98.79,97.59,1344400\r\n\"C\",53.52,\"6/11/2007\",\"12:41pm\",+0.19,53.20,53.65,52.81,5118094\r\n\"CAT\",78.88,\"6/11/2007\",\"12:41pm\",+0.36,78.32,78.99,78.06,1400252\r\n\"DD\",50.73,\"6/11/2007\",\"12:41pm\",-0.40,51.13,51.21,50.59,1866700\r\n\"DIS\",34.181,\"6/11/2007\",\"12:41pm\",-0.019,34.28,34.44,34.12,2913850\r\n\"GE\",37.42,\"6/11/2007\",\"12:41pm\",+0.10,37.07,37.49,37.05,10856501\r\n\"GM\",31.32,\"6/11/2007\",\"12:41pm\",+0.32,31.00,31.62,30.90,6784901\r\n\"HD\",37.75,\"6/11/2007\",\"12:41pm\",-0.20,37.78,37.83,37.62,5186969\r\n\"HON\",56.96,\"6/11/2007\",\"12:41pm\",-0.42,57.25,57.40,56.91,1802642\r\n\"HPQ\",46.01,\"6/11/2007\",\"12:41pm\",+0.31,45.80,46.29,45.46,4507656\r\n\"IBM\",103.74,\"6/11/2007\",\"12:40pm\",+0.67,102.87,103.76,102.50,2288300\r\n\"INTC\",21.96,\"6/11/2007\",\"12:46pm\",+0.13,21.70,22.02,21.69,20169300\r\n\"JNJ\",62.62,\"6/11/2007\",\"12:41pm\",+0.49,62.89,62.89,62.15,3651538\r\n\"JPM\",50.55,\"6/11/2007\",\"12:41pm\",+0.14,50.41,50.62,50.05,3814700\r\n\"KO\",51.56,\"6/11/2007\",\"12:40pm\",-0.11,51.67,51.82,51.32,5678742\r\n\"MCD\",51.40,\"6/11/2007\",\"12:41pm\",-0.01,51.47,51.62,50.98,2510514\r\n\"MMM\",85.33,\"6/11/2007\",\"12:40pm\",-0.61,85.94,85.98,85.28,1043700\r\n\"MO\",70.35,\"6/11/2007\",\"12:41pm\",+0.05,70.25,70.50,69.76,4300857\r\n\"MRK\",51.06,\"6/11/2007\",\"12:41pm\",+0.92,50.30,51.07,50.04,5195700\r\n\"MSFT\",30.13,\"6/11/2007\",\"12:46pm\",+0.08,30.05,30.25,29.93,20422172\r\n\"PFE\",26.41,\"6/11/2007\",\"12:41pm\",-0.11,26.50,26.53,26.31,10595782\r\n\"PG\",63.05,\"6/11/2007\",\"12:41pm\",-0.02,62.80,63.12,62.75,2806346\r\n\"T\",40.06,\"6/11/2007\",\"12:41pm\",-0.20,40.20,40.25,39.89,6338875\r\n\"UTX\",69.81,\"6/11/2007\",\"12:41pm\",-0.42,69.85,70.20,69.51,968100\r\n\"VZ\",43.28,\"6/11/2007\",\"12:41pm\",+0.21,42.95,43.34,42.89,3802620\r\n\"WMT\",49.92,\"6/11/2007\",\"12:41pm\",-0.16,49.90,50.00,49.55,4964533\r\n\"XOM\",83.55,\"6/11/2007\",\"12:41pm\",+0.87,82.68,83.57,82.35,5902800\r\n\"AA\",39.48,\"6/11/2007\",\"12:46pm\",-0.18,39.67,40.18,39.43,2011580\r\n\"AIG\",71.68,\"6/11/2007\",\"12:45pm\",+0.15,71.29,71.76,71.15,2066242\r\n\"AXP\",62.93,\"6/11/2007\",\"12:46pm\",-0.11,62.79,63.19,62.42,2001530\r\n\"BA\",97.68,\"6/11/2007\",\"12:46pm\",-0.51,98.25,98.79,97.59,1387800\r\n\"C\",53.54,\"6/11/2007\",\"12:46pm\",+0.21,53.20,53.65,52.81,5168294\r\n\"CAT\",78.87,\"6/11/2007\",\"12:46pm\",+0.35,78.32,78.99,78.06,1416552\r\n\"DD\",50.70,\"6/11/2007\",\"12:46pm\",-0.43,51.13,51.21,50.59,1884900\r\n\"DIS\",34.20,\"6/11/2007\",\"12:46pm\",0.00,34.28,34.44,34.12,2958150\r\n\"GE\",37.40,\"6/11/2007\",\"12:46pm\",+0.08,37.07,37.49,37.05,10931901\r\n\"GM\",31.29,\"6/11/2007\",\"12:46pm\",+0.29,31.00,31.62,30.90,6930401\r\n\"HD\",37.75,\"6/11/2007\",\"12:46pm\",-0.20,37.78,37.83,37.62,5233269\r\n\"HON\",56.99,\"6/11/2007\",\"12:46pm\",-0.39,57.25,57.40,56.91,1827642\r\n\"HPQ\",46.01,\"6/11/2007\",\"12:46pm\",+0.31,45.80,46.29,45.46,4557656\r\n\"IBM\",103.75,\"6/11/2007\",\"12:46pm\",+0.68,102.87,103.78,102.50,2310700\r\n\"INTC\",22.00,\"6/11/2007\",\"12:51pm\",+0.17,21.70,22.02,21.69,20568860\r\n\"JNJ\",62.59,\"6/11/2007\",\"12:46pm\",+0.46,62.89,62.89,62.15,3702738\r\n\"JPM\",50.57,\"6/11/2007\",\"12:46pm\",+0.16,50.41,50.62,50.05,3902300\r\n\"KO\",51.58,\"6/11/2007\",\"12:46pm\",-0.09,51.67,51.82,51.32,5707242\r\n\"MCD\",51.44,\"6/11/2007\",\"12:46pm\",+0.03,51.47,51.62,50.98,2534014\r\n\"MMM\",85.33,\"6/11/2007\",\"12:45pm\",-0.61,85.94,85.98,85.28,1051300\r\n\"MO\",70.32,\"6/11/2007\",\"12:46pm\",+0.02,70.25,70.50,69.76,4330057\r\n\"MRK\",50.97,\"6/11/2007\",\"12:46pm\",+0.83,50.30,51.07,50.04,5430200\r\n\"MSFT\",30.13,\"6/11/2007\",\"12:51pm\",+0.08,30.05,30.25,29.93,20730214\r\n\"PFE\",26.43,\"6/11/2007\",\"12:46pm\",-0.09,26.50,26.53,26.31,10698282\r\n\"PG\",63.05,\"6/11/2007\",\"12:46pm\",-0.02,62.80,63.12,62.75,2909046\r\n\"T\",40.06,\"6/11/2007\",\"12:46pm\",-0.20,40.20,40.25,39.89,6387575\r\n\"UTX\",69.81,\"6/11/2007\",\"12:46pm\",-0.42,69.85,70.20,69.51,976200\r\n\"VZ\",43.28,\"6/11/2007\",\"12:46pm\",+0.21,42.95,43.34,42.89,3826420\r\n\"WMT\",49.95,\"6/11/2007\",\"12:46pm\",-0.13,49.90,50.00,49.55,5329433\r\n\"XOM\",83.58,\"6/11/2007\",\"12:46pm\",+0.90,82.68,83.59,82.35,5965700\r\n\"AA\",39.48,\"6/11/2007\",\"12:51pm\",-0.18,39.67,40.18,39.43,2052980\r\n\"AIG\",71.67,\"6/11/2007\",\"12:51pm\",+0.14,71.29,71.76,71.15,2109942\r\n\"AXP\",62.90,\"6/11/2007\",\"12:50pm\",-0.14,62.79,63.19,62.42,2024830\r\n\"BA\",97.78,\"6/11/2007\",\"12:51pm\",-0.41,98.25,98.79,97.59,1430600\r\n\"C\",53.56,\"6/11/2007\",\"12:51pm\",+0.23,53.20,53.65,52.81,5509594\r\n\"CAT\",79.00,\"6/11/2007\",\"12:50pm\",+0.48,78.32,79.00,78.06,1448852\r\n\"DD\",50.71,\"6/11/2007\",\"12:51pm\",-0.42,51.13,51.21,50.59,1916300\r\n\"DIS\",34.192,\"6/11/2007\",\"12:50pm\",-0.008,34.28,34.44,34.12,3022750\r\n\"GE\",37.41,\"6/11/2007\",\"12:51pm\",+0.09,37.07,37.49,37.05,11159401\r\n\"GM\",31.38,\"6/11/2007\",\"12:51pm\",+0.38,31.00,31.62,30.90,7135301\r\n\"HD\",37.76,\"6/11/2007\",\"12:51pm\",-0.19,37.78,37.83,37.62,5362369\r\n\"HON\",57.05,\"6/11/2007\",\"12:51pm\",-0.33,57.25,57.40,56.91,1871542\r\n\"HPQ\",46.03,\"6/11/2007\",\"12:51pm\",+0.33,45.80,46.29,45.46,4684356\r\n\"IBM\",103.87,\"6/11/2007\",\"12:51pm\",+0.80,102.87,103.89,102.50,2362500\r\n\"INTC\",22.00,\"6/11/2007\",\"12:56pm\",+0.17,21.70,22.02,21.69,20977118\r\n\"JNJ\",62.58,\"6/11/2007\",\"12:51pm\",+0.45,62.89,62.89,62.15,3790288\r\n\"JPM\",50.60,\"6/11/2007\",\"12:51pm\",+0.19,50.41,50.63,50.05,3966000\r\n\"KO\",51.59,\"6/11/2007\",\"12:51pm\",-0.08,51.67,51.82,51.32,5755942\r\n\"MCD\",51.42,\"6/11/2007\",\"12:50pm\",+0.01,51.47,51.62,50.98,2580014\r\n\"MMM\",85.36,\"6/11/2007\",\"12:50pm\",-0.58,85.94,85.98,85.28,1063300\r\n\"MO\",70.37,\"6/11/2007\",\"12:51pm\",+0.07,70.25,70.50,69.76,4395657\r\n\"MRK\",50.97,\"6/11/2007\",\"12:51pm\",+0.83,50.30,51.07,50.04,5527700\r\n\"MSFT\",30.17,\"6/11/2007\",\"12:56pm\",+0.12,30.05,30.25,29.93,21243952\r\n\"PFE\",26.43,\"6/11/2007\",\"12:51pm\",-0.09,26.50,26.53,26.31,10870382\r\n\"PG\",63.04,\"6/11/2007\",\"12:51pm\",-0.03,62.80,63.12,62.75,3002346\r\n\"T\",40.01,\"6/11/2007\",\"12:51pm\",-0.25,40.20,40.25,39.89,6578675\r\n\"UTX\",69.88,\"6/11/2007\",\"12:50pm\",-0.35,69.85,70.20,69.51,986100\r\n\"VZ\",43.30,\"6/11/2007\",\"12:51pm\",+0.23,42.95,43.34,42.89,3880620\r\n\"WMT\",49.9786,\"6/11/2007\",\"12:51pm\",-0.1014,49.90,50.00,49.55,5399333\r\n\"XOM\",83.67,\"6/11/2007\",\"12:51pm\",+0.99,82.68,83.67,82.35,6065100\r\n\"AA\",39.50,\"6/11/2007\",\"12:56pm\",-0.16,39.67,40.18,39.43,2111680\r\n\"AIG\",71.68,\"6/11/2007\",\"12:56pm\",+0.15,71.29,71.76,71.15,2166942\r\n\"AXP\",63.00,\"6/11/2007\",\"12:56pm\",-0.04,62.79,63.19,62.42,2038530\r\n\"BA\",97.71,\"6/11/2007\",\"12:56pm\",-0.48,98.25,98.79,97.59,1475700\r\n\"C\",53.5614,\"6/11/2007\",\"12:56pm\",+0.2314,53.20,53.65,52.81,5665694\r\n\"CAT\",79.14,\"6/11/2007\",\"12:56pm\",+0.62,78.32,79.14,78.06,1510252\r\n\"DD\",50.79,\"6/11/2007\",\"12:56pm\",-0.34,51.13,51.21,50.59,1934600\r\n\"DIS\",34.20,\"6/11/2007\",\"12:56pm\",0.00,34.28,34.44,34.12,3112050\r\n\"GE\",37.47,\"6/11/2007\",\"12:56pm\",+0.15,37.07,37.49,37.05,11425501\r\n\"GM\",31.37,\"6/11/2007\",\"12:56pm\",+0.37,31.00,31.62,30.90,7225026\r\n\"HD\",37.77,\"6/11/2007\",\"12:56pm\",-0.18,37.78,37.83,37.62,5433169\r\n\"HON\",57.11,\"6/11/2007\",\"12:56pm\",-0.27,57.25,57.40,56.91,1911042\r\n\"HPQ\",46.07,\"6/11/2007\",\"12:55pm\",+0.37,45.80,46.29,45.46,4729356\r\n\"IBM\",103.97,\"6/11/2007\",\"12:56pm\",+0.90,102.87,104.00,102.50,2444600\r\n\"INTC\",22.00,\"6/11/2007\",\"1:01pm\",+0.17,21.70,22.02,21.69,21559788\r\n\"JNJ\",62.59,\"6/11/2007\",\"12:56pm\",+0.46,62.89,62.89,62.15,3837538\r\n\"JPM\",50.65,\"6/11/2007\",\"12:56pm\",+0.24,50.41,50.65,50.05,4042900\r\n\"KO\",51.67,\"6/11/2007\",\"12:56pm\",0.00,51.67,51.82,51.32,5793842\r\n\"MCD\",51.44,\"6/11/2007\",\"12:56pm\",+0.03,51.47,51.62,50.98,2635114\r\n\"MMM\",85.46,\"6/11/2007\",\"12:55pm\",-0.48,85.94,85.98,85.28,1082400\r\n\"MO\",70.35,\"6/11/2007\",\"12:56pm\",+0.05,70.25,70.50,69.76,4427157\r\n\"MRK\",50.96,\"6/11/2007\",\"12:56pm\",+0.82,50.30,51.07,50.04,5634400\r\n\"MSFT\",30.14,\"6/11/2007\",\"1:01pm\",+0.09,30.05,30.25,29.93,21696948\r\n\"PFE\",26.45,\"6/11/2007\",\"12:56pm\",-0.07,26.50,26.53,26.31,11036032\r\n\"PG\",63.08,\"6/11/2007\",\"12:56pm\",+0.01,62.80,63.12,62.75,3066446\r\n\"T\",40.04,\"6/11/2007\",\"12:56pm\",-0.22,40.20,40.25,39.89,6709275\r\n\"UTX\",69.92,\"6/11/2007\",\"12:56pm\",-0.31,69.85,70.20,69.51,1008800\r\n\"VZ\",43.32,\"6/11/2007\",\"12:56pm\",+0.25,42.95,43.34,42.89,3928820\r\n\"WMT\",50.01,\"6/11/2007\",\"12:56pm\",-0.07,49.90,50.04,49.55,5568433\r\n\"XOM\",83.66,\"6/11/2007\",\"12:56pm\",+0.98,82.68,83.72,82.35,6158700\r\n\"AA\",39.51,\"6/11/2007\",\"1:01pm\",-0.15,39.67,40.18,39.43,2207380\r\n\"AIG\",71.64,\"6/11/2007\",\"1:01pm\",+0.11,71.29,71.76,71.15,2223342\r\n\"AXP\",62.96,\"6/11/2007\",\"1:01pm\",-0.08,62.79,63.19,62.42,2056230\r\n\"BA\",97.65,\"6/11/2007\",\"1:01pm\",-0.54,98.25,98.79,97.59,1502900\r\n\"C\",53.58,\"6/11/2007\",\"1:01pm\",+0.25,53.20,53.65,52.81,5805794\r\n\"CAT\",79.07,\"6/11/2007\",\"1:00pm\",+0.55,78.32,79.14,78.06,1532652\r\n\"DD\",50.7784,\"6/11/2007\",\"1:01pm\",-0.3516,51.13,51.21,50.59,1957300\r\n\"DIS\",34.20,\"6/11/2007\",\"1:01pm\",0.00,34.28,34.44,34.12,3159250\r\n\"GE\",37.48,\"6/11/2007\",\"1:01pm\",+0.16,37.07,37.49,37.05,11769301\r\n\"GM\",31.34,\"6/11/2007\",\"1:01pm\",+0.34,31.00,31.62,30.90,7327251\r\n\"HD\",37.75,\"6/11/2007\",\"1:01pm\",-0.20,37.78,37.83,37.62,5482469\r\n\"HON\",56.98,\"6/11/2007\",\"1:01pm\",-0.40,57.25,57.40,56.91,1964142\r\n\"HPQ\",46.05,\"6/11/2007\",\"1:01pm\",+0.35,45.80,46.29,45.46,4778356\r\n\"IBM\",103.79,\"6/11/2007\",\"1:01pm\",+0.72,102.87,104.00,102.50,2477300\r\n\"INTC\",22.01,\"6/11/2007\",\"1:06pm\",+0.18,21.70,22.02,21.69,21974040\r\n\"JNJ\",62.55,\"6/11/2007\",\"1:01pm\",+0.42,62.89,62.89,62.15,4653538\r\n\"JPM\",50.64,\"6/11/2007\",\"1:01pm\",+0.23,50.41,50.66,50.05,4113700\r\n\"KO\",51.61,\"6/11/2007\",\"1:01pm\",-0.06,51.67,51.82,51.32,5829142\r\n\"MCD\",51.40,\"6/11/2007\",\"1:01pm\",-0.01,51.47,51.62,50.98,2668014\r\n\"MMM\",85.38,\"6/11/2007\",\"1:00pm\",-0.56,85.94,85.98,85.28,1091500\r\n\"MO\",70.29,\"6/11/2007\",\"1:01pm\",-0.01,70.25,70.50,69.76,4483057\r\n\"MRK\",50.95,\"6/11/2007\",\"1:01pm\",+0.81,50.30,51.07,50.04,5728600\r\n\"MSFT\",30.14,\"6/11/2007\",\"1:06pm\",+0.09,30.05,30.25,29.93,21878754\r\n\"PFE\",26.44,\"6/11/2007\",\"1:01pm\",-0.08,26.50,26.53,26.31,11232332\r\n\"PG\",63.01,\"6/11/2007\",\"1:01pm\",-0.06,62.80,63.12,62.75,3117846\r\n\"T\",40.03,\"6/11/2007\",\"1:01pm\",-0.23,40.20,40.25,39.89,6799375\r\n\"UTX\",69.90,\"6/11/2007\",\"1:00pm\",-0.33,69.85,70.20,69.51,1060200\r\n\"VZ\",43.25,\"6/11/2007\",\"1:01pm\",+0.18,42.95,43.34,42.89,4008920\r\n\"WMT\",50.01,\"6/11/2007\",\"1:01pm\",-0.07,49.90,50.04,49.55,5777993\r\n\"XOM\",83.57,\"6/11/2007\",\"1:01pm\",+0.89,82.68,83.72,82.35,6284800\r\n\"AA\",39.52,\"6/11/2007\",\"1:06pm\",-0.14,39.67,40.18,39.43,2284880\r\n\"AIG\",71.69,\"6/11/2007\",\"1:06pm\",+0.16,71.29,71.76,71.15,2274642\r\n\"AXP\",62.95,\"6/11/2007\",\"1:06pm\",-0.09,62.79,63.19,62.42,2065330\r\n\"BA\",97.65,\"6/11/2007\",\"1:06pm\",-0.54,98.25,98.79,97.59,1513500\r\n\"C\",53.57,\"6/11/2007\",\"1:06pm\",+0.24,53.20,53.65,52.81,5889294\r\n\"CAT\",79.07,\"6/11/2007\",\"1:05pm\",+0.55,78.32,79.14,78.06,1549052\r\n\"DD\",50.79,\"6/11/2007\",\"1:06pm\",-0.34,51.13,51.21,50.59,1978300\r\n\"DIS\",34.195,\"6/11/2007\",\"1:06pm\",-0.005,34.28,34.44,34.12,3179550\r\n\"GE\",37.48,\"6/11/2007\",\"1:06pm\",+0.16,37.07,37.49,37.05,11921001\r\n\"GM\",31.34,\"6/11/2007\",\"1:06pm\",+0.34,31.00,31.62,30.90,7418551\r\n\"HD\",37.75,\"6/11/2007\",\"1:06pm\",-0.20,37.78,37.83,37.62,5559869\r\n\"HON\",56.99,\"6/11/2007\",\"1:06pm\",-0.39,57.25,57.40,56.91,2007542\r\n\"HPQ\",46.0627,\"6/11/2007\",\"1:05pm\",+0.3627,45.80,46.29,45.46,4829456\r\n\"IBM\",103.76,\"6/11/2007\",\"1:05pm\",+0.69,102.87,104.00,102.50,2503900\r\n\"INTC\",22.04,\"6/11/2007\",\"1:11pm\",+0.21,21.70,22.05,21.69,22780160\r\n\"JNJ\",62.58,\"6/11/2007\",\"1:06pm\",+0.45,62.89,62.89,62.15,4682578\r\n\"JPM\",50.649,\"6/11/2007\",\"1:06pm\",+0.239,50.41,50.66,50.05,4156000\r\n\"KO\",51.68,\"6/11/2007\",\"1:06pm\",+0.01,51.67,51.82,51.32,5958742\r\n\"MCD\",51.38,\"6/11/2007\",\"1:06pm\",-0.03,51.47,51.62,50.98,2710414\r\n\"MMM\",85.40,\"6/11/2007\",\"1:05pm\",-0.54,85.94,85.98,85.28,1115800\r\n\"MO\",70.29,\"6/11/2007\",\"1:06pm\",-0.01,70.25,70.50,69.76,4510357\r\n\"MRK\",50.95,\"6/11/2007\",\"1:06pm\",+0.81,50.30,51.07,50.04,5784900\r\n\"MSFT\",30.16,\"6/11/2007\",\"1:11pm\",+0.11,30.05,30.25,29.93,22141974\r\n\"PFE\",26.50,\"6/11/2007\",\"1:06pm\",-0.02,26.50,26.53,26.31,11587539\r\n\"PG\",63.07,\"6/11/2007\",\"1:06pm\",0.00,62.80,63.12,62.75,3192946\r\n\"T\",40.13,\"6/11/2007\",\"1:06pm\",-0.13,40.20,40.25,39.89,6957775\r\n\"UTX\",69.91,\"6/11/2007\",\"1:06pm\",-0.32,69.85,70.20,69.51,1075500\r\n\"VZ\",43.2716,\"6/11/2007\",\"1:05pm\",+0.2016,42.95,43.34,42.89,4057020\r\n\"WMT\",50.07,\"6/11/2007\",\"1:06pm\",-0.01,49.90,50.08,49.55,5985207\r\n\"XOM\",83.53,\"6/11/2007\",\"1:06pm\",+0.85,82.68,83.72,82.35,6388600\r\n\"AA\",39.53,\"6/11/2007\",\"1:11pm\",-0.13,39.67,40.18,39.43,2319180\r\n\"AIG\",71.74,\"6/11/2007\",\"1:11pm\",+0.21,71.29,71.76,71.15,2359542\r\n\"AXP\",63.12,\"6/11/2007\",\"1:11pm\",+0.08,62.79,63.19,62.42,2086730\r\n\"BA\",97.75,\"6/11/2007\",\"1:11pm\",-0.44,98.25,98.79,97.59,1556900\r\n\"C\",53.67,\"6/11/2007\",\"1:11pm\",+0.34,53.20,53.69,52.81,6010594\r\n\"CAT\",79.17,\"6/11/2007\",\"1:11pm\",+0.65,78.32,79.19,78.06,1577652\r\n\"DD\",50.79,\"6/11/2007\",\"1:11pm\",-0.34,51.13,51.21,50.59,2054300\r\n\"DIS\",34.22,\"6/11/2007\",\"1:11pm\",+0.02,34.28,34.44,34.12,3245950\r\n\"GE\",37.53,\"6/11/2007\",\"1:11pm\",+0.21,37.07,37.53,37.05,12200301\r\n\"GM\",31.42,\"6/11/2007\",\"1:11pm\",+0.42,31.00,31.62,30.90,7518551\r\n\"HD\",37.78,\"6/11/2007\",\"1:11pm\",-0.17,37.78,37.83,37.62,5639569\r\n\"HON\",57.13,\"6/11/2007\",\"1:11pm\",-0.25,57.25,57.40,56.91,2042642\r\n\"HPQ\",46.12,\"6/11/2007\",\"1:11pm\",+0.42,45.80,46.29,45.46,5833149\r\n\"IBM\",103.86,\"6/11/2007\",\"1:11pm\",+0.79,102.87,104.00,102.50,2536300\r\n\"INTC\",22.05,\"6/11/2007\",\"1:16pm\",+0.22,21.70,22.07,21.69,23218578\r\n\"JNJ\",62.65,\"6/11/2007\",\"1:11pm\",+0.52,62.89,62.89,62.15,4727878\r\n\"JPM\",50.69,\"6/11/2007\",\"1:11pm\",+0.28,50.41,50.695,50.05,4239700\r\n\"KO\",51.74,\"6/11/2007\",\"1:11pm\",+0.07,51.67,51.82,51.32,6054742\r\n\"MCD\",51.44,\"6/11/2007\",\"1:11pm\",+0.03,51.47,51.62,50.98,2762114\r\n\"MMM\",85.53,\"6/11/2007\",\"1:11pm\",-0.41,85.94,85.98,85.28,1145200\r\n\"MO\",70.41,\"6/11/2007\",\"1:11pm\",+0.11,70.25,70.50,69.76,4563257\r\n\"MRK\",51.12,\"6/11/2007\",\"1:11pm\",+0.98,50.30,51.13,50.04,5899900\r\n\"MSFT\",30.17,\"6/11/2007\",\"1:16pm\",+0.12,30.05,30.25,29.93,22399100\r\n\"PFE\",26.5275,\"6/11/2007\",\"1:11pm\",+0.0075,26.50,26.53,26.31,12044278\r\n\"PG\",63.0995,\"6/11/2007\",\"1:11pm\",+0.0295,62.80,63.12,62.75,3229446\r\n\"T\",40.19,\"6/11/2007\",\"1:11pm\",-0.07,40.20,40.25,39.89,7073875\r\n\"UTX\",69.97,\"6/11/2007\",\"1:11pm\",-0.26,69.85,70.20,69.51,1092400\r\n\"VZ\",43.34,\"6/11/2007\",\"1:10pm\",+0.27,42.95,43.34,42.88,4847820\r\n\"WMT\",50.08,\"6/11/2007\",\"1:11pm\",0.00,49.90,50.12,49.55,6200407\r\n\"XOM\",83.66,\"6/11/2007\",\"1:11pm\",+0.98,82.68,83.72,82.35,6490400\r\n\"AA\",39.56,\"6/11/2007\",\"1:16pm\",-0.10,39.67,40.18,39.43,2433580\r\n\"AIG\",71.80,\"6/11/2007\",\"1:16pm\",+0.27,71.29,71.83,71.15,2440996\r\n\"AXP\",63.20,\"6/11/2007\",\"1:15pm\",+0.16,62.79,63.21,62.42,2110030\r\n\"BA\",97.85,\"6/11/2007\",\"1:16pm\",-0.34,98.25,98.79,97.59,1590400\r\n\"C\",53.679,\"6/11/2007\",\"1:16pm\",+0.349,53.20,53.71,52.81,6103294\r\n\"CAT\",79.35,\"6/11/2007\",\"1:16pm\",+0.83,78.32,79.39,78.06,1651052\r\n\"DD\",50.85,\"6/11/2007\",\"1:16pm\",-0.28,51.13,51.21,50.59,2111497\r\n\"DIS\",34.21,\"6/11/2007\",\"1:15pm\",+0.01,34.28,34.44,34.12,3343750\r\n\"GE\",37.535,\"6/11/2007\",\"1:16pm\",+0.215,37.07,37.54,37.05,12385801\r\n\"GM\",31.45,\"6/11/2007\",\"1:16pm\",+0.45,31.00,31.62,30.90,7588451\r\n\"HD\",37.77,\"6/11/2007\",\"1:16pm\",-0.18,37.78,37.83,37.62,5820967\r\n\"HON\",57.11,\"6/11/2007\",\"1:16pm\",-0.27,57.25,57.40,56.91,2057642\r\n\"HPQ\",46.12,\"6/11/2007\",\"1:16pm\",+0.42,45.80,46.29,45.46,5922449\r\n\"IBM\",103.83,\"6/11/2007\",\"1:16pm\",+0.76,102.87,104.00,102.50,2579600\r\n\"INTC\",22.06,\"6/11/2007\",\"1:21pm\",+0.23,21.70,22.08,21.69,23707494\r\n\"JNJ\",62.64,\"6/11/2007\",\"1:16pm\",+0.51,62.89,62.89,62.15,4771978\r\n\"JPM\",50.75,\"6/11/2007\",\"1:16pm\",+0.34,50.41,50.78,50.05,4349600\r\n\"KO\",51.73,\"6/11/2007\",\"1:16pm\",+0.06,51.67,51.82,51.32,6085542\r\n\"MCD\",51.47,\"6/11/2007\",\"1:16pm\",+0.06,51.47,51.62,50.98,2786414\r\n\"MMM\",85.56,\"6/11/2007\",\"1:16pm\",-0.38,85.94,85.98,85.28,1180100\r\n\"MO\",70.37,\"6/11/2007\",\"1:16pm\",+0.07,70.25,70.50,69.76,4636857\r\n\"MRK\",51.15,\"6/11/2007\",\"1:16pm\",+1.01,50.30,51.16,50.04,6009400\r\n\"MSFT\",30.14,\"6/11/2007\",\"1:21pm\",+0.09,30.05,30.25,29.93,23011740\r\n\"PFE\",26.51,\"6/11/2007\",\"1:16pm\",-0.01,26.50,26.54,26.31,12225128\r\n\"PG\",63.10,\"6/11/2007\",\"1:16pm\",+0.03,62.80,63.12,62.75,3329546\r\n\"T\",40.22,\"6/11/2007\",\"1:16pm\",-0.04,40.20,40.26,39.89,7235875\r\n\"UTX\",69.95,\"6/11/2007\",\"1:16pm\",-0.28,69.85,70.20,69.51,1122100\r\n\"VZ\",43.37,\"6/11/2007\",\"1:16pm\",+0.30,42.95,43.38,42.88,4953820\r\n\"WMT\",50.04,\"6/11/2007\",\"1:16pm\",-0.04,49.90,50.12,49.55,6345707\r\n\"XOM\",83.58,\"6/11/2007\",\"1:16pm\",+0.90,82.68,83.72,82.35,6598900\r\n\"AA\",39.58,\"6/11/2007\",\"1:21pm\",-0.08,39.67,40.18,39.43,2500980\r\n\"AIG\",71.90,\"6/11/2007\",\"1:21pm\",+0.37,71.29,71.89,71.15,2494796\r\n\"AXP\",63.22,\"6/11/2007\",\"1:21pm\",+0.18,62.79,63.25,62.42,2135530\r\n\"BA\",97.81,\"6/11/2007\",\"1:20pm\",-0.38,98.25,98.79,97.59,1608700\r\n\"C\",53.76,\"6/11/2007\",\"1:21pm\",+0.43,53.20,53.77,52.81,6249294\r\n\"CAT\",79.45,\"6/11/2007\",\"1:21pm\",+0.93,78.32,79.45,78.06,1689152\r\n\"DD\",50.89,\"6/11/2007\",\"1:21pm\",-0.24,51.13,51.21,50.59,2132297\r\n\"DIS\",34.23,\"6/11/2007\",\"1:20pm\",+0.03,34.28,34.44,34.12,3370250\r\n\"GE\",37.53,\"6/11/2007\",\"1:21pm\",+0.21,37.07,37.56,37.05,12700001\r\n\"GM\",31.43,\"6/11/2007\",\"1:21pm\",+0.43,31.00,31.62,30.90,7705751\r\n\"HD\",37.76,\"6/11/2007\",\"1:21pm\",-0.19,37.78,37.83,37.62,5879467\r\n\"HON\",57.17,\"6/11/2007\",\"1:21pm\",-0.21,57.25,57.40,56.91,2107242\r\n\"HPQ\",46.17,\"6/11/2007\",\"1:21pm\",+0.47,45.80,46.29,45.46,5961749\r\n\"IBM\",103.81,\"6/11/2007\",\"1:21pm\",+0.74,102.87,104.00,102.50,2608100\r\n\"INTC\",22.04,\"6/11/2007\",\"1:26pm\",+0.21,21.70,22.08,21.69,24314782\r\n\"JNJ\",62.66,\"6/11/2007\",\"1:21pm\",+0.53,62.89,62.89,62.15,4802578\r\n\"JPM\",50.83,\"6/11/2007\",\"1:21pm\",+0.42,50.41,50.84,50.05,4419800\r\n\"KO\",51.79,\"6/11/2007\",\"1:21pm\",+0.12,51.67,51.82,51.32,6135742\r\n\"MCD\",51.48,\"6/11/2007\",\"1:21pm\",+0.07,51.47,51.62,50.98,2853314\r\n\"MMM\",85.64,\"6/11/2007\",\"1:21pm\",-0.30,85.94,85.98,85.28,1258800\r\n\"MO\",70.41,\"6/11/2007\",\"1:21pm\",+0.11,70.25,70.50,69.76,4723085\r\n\"MRK\",51.22,\"6/11/2007\",\"1:21pm\",+1.08,50.30,51.23,50.04,6148300\r\n\"MSFT\",30.1301,\"6/11/2007\",\"1:25pm\",+0.0801,30.05,30.25,29.93,23292696\r\n\"PFE\",26.50,\"6/11/2007\",\"1:21pm\",-0.02,26.50,26.54,26.31,12653153\r\n\"PG\",63.10,\"6/11/2007\",\"1:21pm\",+0.03,62.80,63.12,62.75,3736946\r\n\"T\",40.28,\"6/11/2007\",\"1:21pm\",+0.02,40.20,40.29,39.89,7365200\r\n\"UTX\",70.02,\"6/11/2007\",\"1:21pm\",-0.21,69.85,70.20,69.51,1158800\r\n\"VZ\",43.43,\"6/11/2007\",\"1:21pm\",+0.36,42.95,43.44,42.88,5053620\r\n\"WMT\",50.02,\"6/11/2007\",\"1:21pm\",-0.06,49.90,50.12,49.55,6585307\r\n\"XOM\",83.66,\"6/11/2007\",\"1:21pm\",+0.98,82.68,83.72,82.35,6705800\r\n\"AA\",39.52,\"6/11/2007\",\"1:26pm\",-0.14,39.67,40.18,39.43,2540480\r\n\"AIG\",71.85,\"6/11/2007\",\"1:26pm\",+0.32,71.29,71.90,71.15,2549996\r\n\"AXP\",63.18,\"6/11/2007\",\"1:26pm\",+0.14,62.79,63.26,62.42,2157330\r\n\"BA\",97.71,\"6/11/2007\",\"1:26pm\",-0.48,98.25,98.79,97.59,1636600\r\n\"C\",53.72,\"6/11/2007\",\"1:26pm\",+0.39,53.20,53.77,52.81,6427294\r\n\"CAT\",79.20,\"6/11/2007\",\"1:26pm\",+0.68,78.32,79.46,78.06,1768752\r\n\"DD\",50.86,\"6/11/2007\",\"1:26pm\",-0.27,51.13,51.21,50.59,2150397\r\n\"DIS\",34.22,\"6/11/2007\",\"1:26pm\",+0.02,34.28,34.44,34.12,3496150\r\n\"GE\",37.50,\"6/11/2007\",\"1:26pm\",+0.18,37.07,37.56,37.05,12934301\r\n\"GM\",31.40,\"6/11/2007\",\"1:26pm\",+0.40,31.00,31.62,30.90,7805351\r\n\"HD\",37.75,\"6/11/2007\",\"1:26pm\",-0.20,37.78,37.83,37.62,5995967\r\n\"HON\",57.20,\"6/11/2007\",\"1:26pm\",-0.18,57.25,57.40,56.91,2149442\r\n\"HPQ\",46.14,\"6/11/2007\",\"1:26pm\",+0.44,45.80,46.29,45.46,6019149\r\n\"IBM\",103.70,\"6/11/2007\",\"1:26pm\",+0.63,102.87,104.00,102.50,2638000\r\n\"INTC\",22.04,\"6/11/2007\",\"1:31pm\",+0.21,21.70,22.08,21.69,24406300\r\n\"JNJ\",62.63,\"6/11/2007\",\"1:26pm\",+0.50,62.89,62.89,62.15,4835178\r\n\"JPM\",50.81,\"6/11/2007\",\"1:26pm\",+0.40,50.41,50.84,50.05,4480900\r\n\"KO\",51.78,\"6/11/2007\",\"1:26pm\",+0.11,51.67,51.85,51.32,6233852\r\n\"MCD\",51.44,\"6/11/2007\",\"1:26pm\",+0.03,51.47,51.62,50.98,2881414\r\n\"MMM\",85.62,\"6/11/2007\",\"1:26pm\",-0.32,85.94,85.98,85.28,1282500\r\n\"MO\",70.36,\"6/11/2007\",\"1:26pm\",+0.06,70.25,70.50,69.76,4767885\r\n\"MRK\",51.23,\"6/11/2007\",\"1:26pm\",+1.09,50.30,51.27,50.04,6334000\r\n\"MSFT\",30.12,\"6/11/2007\",\"1:31pm\",+0.07,30.05,30.25,29.93,23578844\r\n\"PFE\",26.50,\"6/11/2007\",\"1:26pm\",-0.02,26.50,26.54,26.31,14778927\r\n\"PG\",63.09,\"6/11/2007\",\"1:26pm\",+0.02,62.80,63.12,62.75,3928946\r\n\"T\",40.31,\"6/11/2007\",\"1:26pm\",+0.05,40.20,40.33,39.89,7505100\r\n\"UTX\",69.98,\"6/11/2007\",\"1:26pm\",-0.25,69.85,70.20,69.51,1210900\r\n\"VZ\",43.40,\"6/11/2007\",\"1:26pm\",+0.33,42.95,43.45,42.88,5123120\r\n\"WMT\",49.96,\"6/11/2007\",\"1:26pm\",-0.12,49.90,50.12,49.55,6687607\r\n\"XOM\",83.57,\"6/11/2007\",\"1:26pm\",+0.89,82.68,83.72,82.35,6797200\r\n\"AA\",39.53,\"6/11/2007\",\"1:31pm\",-0.13,39.67,40.18,39.43,2572580\r\n\"AIG\",71.85,\"6/11/2007\",\"1:31pm\",+0.32,71.29,71.90,71.15,2602596\r\n\"AXP\",63.26,\"6/11/2007\",\"1:31pm\",+0.22,62.79,63.26,62.42,2172130\r\n\"BA\",97.82,\"6/11/2007\",\"1:31pm\",-0.37,98.25,98.79,97.59,1659000\r\n\"C\",53.74,\"6/11/2007\",\"1:31pm\",+0.41,53.20,53.77,52.81,6462694\r\n\"CAT\",79.20,\"6/11/2007\",\"1:31pm\",+0.68,78.32,79.46,78.06,1829052\r\n\"DD\",50.85,\"6/11/2007\",\"1:31pm\",-0.28,51.13,51.21,50.59,2171097\r\n\"DIS\",34.24,\"6/11/2007\",\"1:31pm\",+0.04,34.28,34.44,34.12,3579950\r\n\"GE\",37.52,\"6/11/2007\",\"1:31pm\",+0.20,37.07,37.56,37.05,13012501\r\n\"GM\",31.42,\"6/11/2007\",\"1:31pm\",+0.42,31.00,31.62,30.90,7927151\r\n\"HD\",37.76,\"6/11/2007\",\"1:31pm\",-0.19,37.78,37.83,37.62,6189667\r\n\"HON\",57.21,\"6/11/2007\",\"1:31pm\",-0.17,57.25,57.40,56.91,2172142\r\n\"HPQ\",46.14,\"6/11/2007\",\"1:31pm\",+0.44,45.80,46.29,45.46,6057949\r\n\"IBM\",103.63,\"6/11/2007\",\"1:31pm\",+0.56,102.87,104.00,102.50,2660504\r\n\"INTC\",22.04,\"6/11/2007\",\"1:36pm\",+0.21,21.70,22.08,21.69,24689632\r\n\"JNJ\",62.61,\"6/11/2007\",\"1:31pm\",+0.48,62.89,62.89,62.15,4868578\r\n\"JPM\",50.80,\"6/11/2007\",\"1:31pm\",+0.39,50.41,50.84,50.05,4526800\r\n\"KO\",51.75,\"6/11/2007\",\"1:31pm\",+0.08,51.67,51.85,51.32,6294152\r\n\"MCD\",51.43,\"6/11/2007\",\"1:31pm\",+0.02,51.47,51.62,50.98,2901114\r\n\"MMM\",85.59,\"6/11/2007\",\"1:31pm\",-0.35,85.94,85.98,85.28,1297000\r\n\"MO\",70.34,\"6/11/2007\",\"1:31pm\",+0.04,70.25,70.50,69.76,4818885\r\n\"MRK\",51.24,\"6/11/2007\",\"1:31pm\",+1.10,50.30,51.27,50.04,6438900\r\n\"MSFT\",30.125,\"6/11/2007\",\"1:36pm\",+0.075,30.05,30.25,29.93,23983122\r\n\"PFE\",26.51,\"6/11/2007\",\"1:31pm\",-0.01,26.50,26.54,26.31,14886127\r\n\"PG\",63.08,\"6/11/2007\",\"1:31pm\",+0.01,62.80,63.13,62.75,3971046\r\n\"T\",40.31,\"6/11/2007\",\"1:31pm\",+0.05,40.20,40.33,39.89,7610100\r\n\"UTX\",70.05,\"6/11/2007\",\"1:31pm\",-0.18,69.85,70.20,69.51,1239200\r\n\"VZ\",43.39,\"6/11/2007\",\"1:31pm\",+0.32,42.95,43.45,42.88,5198120\r\n\"WMT\",50.00,\"6/11/2007\",\"1:31pm\",-0.08,49.90,50.12,49.55,6808307\r\n\"XOM\",83.60,\"6/11/2007\",\"1:31pm\",+0.92,82.68,83.72,82.35,6858500\r\n\"AA\",39.54,\"6/11/2007\",\"1:36pm\",-0.12,39.67,40.18,39.43,2615380\r\n\"AIG\",71.90,\"6/11/2007\",\"1:36pm\",+0.37,71.29,71.90,71.15,2648396\r\n\"AXP\",63.27,\"6/11/2007\",\"1:36pm\",+0.23,62.79,63.28,62.42,2187930\r\n\"BA\",97.88,\"6/11/2007\",\"1:36pm\",-0.31,98.25,98.79,97.59,1700700\r\n\"C\",53.73,\"6/11/2007\",\"1:36pm\",+0.40,53.20,53.77,52.81,6558794\r\n\"CAT\",79.20,\"6/11/2007\",\"1:36pm\",+0.68,78.32,79.46,78.06,1869352\r\n\"DD\",50.89,\"6/11/2007\",\"1:36pm\",-0.24,51.13,51.21,50.59,2184797\r\n\"DIS\",34.22,\"6/11/2007\",\"1:36pm\",+0.02,34.28,34.44,34.12,3654250\r\n\"GE\",37.51,\"6/11/2007\",\"1:36pm\",+0.19,37.07,37.56,37.05,13175301\r\n\"GM\",31.45,\"6/11/2007\",\"1:36pm\",+0.45,31.00,31.62,30.90,8227251\r\n\"HD\",37.76,\"6/11/2007\",\"1:36pm\",-0.19,37.78,37.83,37.62,6273567\r\n\"HON\",57.24,\"6/11/2007\",\"1:36pm\",-0.14,57.25,57.40,56.91,2203642\r\n\"HPQ\",46.17,\"6/11/2007\",\"1:36pm\",+0.47,45.80,46.29,45.46,6121849\r\n\"IBM\",103.59,\"6/11/2007\",\"1:36pm\",+0.52,102.87,104.00,102.50,2702104\r\n\"INTC\",22.02,\"6/11/2007\",\"1:41pm\",+0.19,21.70,22.08,21.69,25010946\r\n\"JNJ\",62.60,\"6/11/2007\",\"1:36pm\",+0.47,62.89,62.89,62.15,4923778\r\n\"JPM\",50.77,\"6/11/2007\",\"1:36pm\",+0.36,50.41,50.84,50.05,4660900\r\n\"KO\",51.79,\"6/11/2007\",\"1:36pm\",+0.12,51.67,51.85,51.32,6365852\r\n\"MCD\",51.42,\"6/11/2007\",\"1:36pm\",+0.01,51.47,51.62,50.98,2935214\r\n\"MMM\",85.57,\"6/11/2007\",\"1:36pm\",-0.37,85.94,85.98,85.28,1313500\r\n\"MO\",70.37,\"6/11/2007\",\"1:36pm\",+0.07,70.25,70.50,69.76,4877785\r\n\"MRK\",51.24,\"6/11/2007\",\"1:36pm\",+1.10,50.30,51.28,50.04,6515500\r\n\"MSFT\",30.12,\"6/11/2007\",\"1:41pm\",+0.07,30.05,30.25,29.93,24255316\r\n\"PFE\",26.491,\"6/11/2007\",\"1:36pm\",-0.029,26.50,26.54,26.31,15109927\r\n\"PG\",63.12,\"6/11/2007\",\"1:36pm\",+0.05,62.80,63.13,62.75,4002446\r\n\"T\",40.30,\"6/11/2007\",\"1:36pm\",+0.04,40.20,40.33,39.89,7712400\r\n\"UTX\",70.10,\"6/11/2007\",\"1:36pm\",-0.13,69.85,70.20,69.51,1247400\r\n\"VZ\",43.40,\"6/11/2007\",\"1:36pm\",+0.33,42.95,43.45,42.88,5341720\r\n\"WMT\",49.95,\"6/11/2007\",\"1:36pm\",-0.13,49.90,50.12,49.55,6914407\r\n\"XOM\",83.56,\"6/11/2007\",\"1:36pm\",+0.88,82.68,83.72,82.35,6917800\r\n\"AA\",39.53,\"6/11/2007\",\"1:41pm\",-0.13,39.67,40.18,39.43,2647380\r\n\"AIG\",71.87,\"6/11/2007\",\"1:41pm\",+0.34,71.29,71.90,71.15,2686796\r\n\"AXP\",63.28,\"6/11/2007\",\"1:41pm\",+0.24,62.79,63.32,62.42,2205330\r\n\"BA\",97.82,\"6/11/2007\",\"1:41pm\",-0.37,98.25,98.79,97.59,1725200\r\n\"C\",53.67,\"6/11/2007\",\"1:41pm\",+0.34,53.20,53.77,52.81,6740594\r\n\"CAT\",79.11,\"6/11/2007\",\"1:41pm\",+0.59,78.32,79.46,78.06,1880652\r\n\"DD\",50.861,\"6/11/2007\",\"1:41pm\",-0.269,51.13,51.21,50.59,2199797\r\n\"DIS\",34.21,\"6/11/2007\",\"1:41pm\",+0.01,34.28,34.44,34.12,3734350\r\n\"GE\",37.52,\"6/11/2007\",\"1:41pm\",+0.20,37.07,37.56,37.05,13566001\r\n\"GM\",31.47,\"6/11/2007\",\"1:41pm\",+0.47,31.00,31.62,30.90,8330251\r\n\"HD\",37.75,\"6/11/2007\",\"1:41pm\",-0.20,37.78,37.83,37.62,6627167\r\n\"HON\",57.17,\"6/11/2007\",\"1:41pm\",-0.21,57.25,57.40,56.91,2246142\r\n\"HPQ\",46.14,\"6/11/2007\",\"1:41pm\",+0.44,45.80,46.29,45.46,6190049\r\n\"IBM\",103.54,\"6/11/2007\",\"1:41pm\",+0.47,102.87,104.00,102.50,2715404\r\n\"INTC\",22.02,\"6/11/2007\",\"1:46pm\",+0.19,21.70,22.08,21.69,25285316\r\n\"JNJ\",62.54,\"6/11/2007\",\"1:41pm\",+0.41,62.89,62.89,62.15,5098378\r\n\"JPM\",50.77,\"6/11/2007\",\"1:41pm\",+0.36,50.41,50.84,50.05,5025900\r\n\"KO\",51.725,\"6/11/2007\",\"1:41pm\",+0.055,51.67,51.85,51.32,6390552\r\n\"MCD\",51.391,\"6/11/2007\",\"1:41pm\",-0.019,51.47,51.62,50.98,2972614\r\n\"MMM\",85.58,\"6/11/2007\",\"1:41pm\",-0.36,85.94,85.98,85.28,1337300\r\n\"MO\",70.29,\"6/11/2007\",\"1:41pm\",-0.01,70.25,70.50,69.76,4907285\r\n\"MRK\",51.21,\"6/11/2007\",\"1:41pm\",+1.07,50.30,51.28,50.04,6587800\r\n\"MSFT\",30.13,\"6/11/2007\",\"1:46pm\",+0.08,30.05,30.25,29.93,24497374\r\n\"PFE\",26.49,\"6/11/2007\",\"1:41pm\",-0.03,26.50,26.54,26.31,15538627\r\n\"PG\",63.13,\"6/11/2007\",\"1:41pm\",+0.06,62.80,63.14,62.75,4048446\r\n\"T\",40.31,\"6/11/2007\",\"1:41pm\",+0.05,40.20,40.34,39.89,7832500\r\n\"UTX\",70.07,\"6/11/2007\",\"1:41pm\",-0.16,69.85,70.20,69.51,1264700\r\n\"VZ\",43.38,\"6/11/2007\",\"1:41pm\",+0.31,42.95,43.45,42.88,5395220\r\n\"WMT\",49.90,\"6/11/2007\",\"1:41pm\",-0.18,49.90,50.12,49.55,7009107\r\n\"XOM\",83.53,\"6/11/2007\",\"1:41pm\",+0.85,82.68,83.72,82.35,7007700\r\n\"AA\",39.55,\"6/11/2007\",\"1:46pm\",-0.11,39.67,40.18,39.43,2672280\r\n\"AIG\",71.88,\"6/11/2007\",\"1:46pm\",+0.35,71.29,71.90,71.15,2724196\r\n\"AXP\",63.28,\"6/11/2007\",\"1:46pm\",+0.24,62.79,63.32,62.42,2214330\r\n\"BA\",97.98,\"6/11/2007\",\"1:46pm\",-0.21,98.25,98.79,97.59,1773500\r\n\"C\",53.61,\"6/11/2007\",\"1:46pm\",+0.28,53.20,53.77,52.81,6867594\r\n\"CAT\",79.07,\"6/11/2007\",\"1:46pm\",+0.55,78.32,79.46,78.06,1923652\r\n\"DD\",50.86,\"6/11/2007\",\"1:46pm\",-0.27,51.13,51.21,50.59,2221297\r\n\"DIS\",34.201,\"6/11/2007\",\"1:46pm\",+0.001,34.28,34.44,34.12,3795650\r\n\"GE\",37.52,\"6/11/2007\",\"1:46pm\",+0.20,37.07,37.56,37.05,13711301\r\n\"GM\",31.50,\"6/11/2007\",\"1:46pm\",+0.50,31.00,31.62,30.90,8678851\r\n\"HD\",37.77,\"6/11/2007\",\"1:46pm\",-0.18,37.78,37.83,37.62,6687867\r\n\"HON\",57.162,\"6/11/2007\",\"1:46pm\",-0.218,57.25,57.40,56.91,2293142\r\n\"HPQ\",46.13,\"6/11/2007\",\"1:46pm\",+0.43,45.80,46.29,45.46,6259749\r\n\"IBM\",103.57,\"6/11/2007\",\"1:46pm\",+0.50,102.87,104.00,102.50,2740104\r\n\"INTC\",22.02,\"6/11/2007\",\"1:51pm\",+0.19,21.70,22.08,21.69,25632092\r\n\"JNJ\",62.54,\"6/11/2007\",\"1:46pm\",+0.41,62.89,62.89,62.15,5147978\r\n\"JPM\",50.80,\"6/11/2007\",\"1:46pm\",+0.39,50.41,50.84,50.05,5114400\r\n\"KO\",51.75,\"6/11/2007\",\"1:46pm\",+0.08,51.67,51.85,51.32,6441352\r\n\"MCD\",51.40,\"6/11/2007\",\"1:46pm\",-0.01,51.47,51.62,50.98,3049814\r\n\"MMM\",85.59,\"6/11/2007\",\"1:46pm\",-0.35,85.94,85.98,85.28,1347600\r\n\"MO\",70.28,\"6/11/2007\",\"1:46pm\",-0.02,70.25,70.50,69.76,4959585\r\n\"MRK\",51.18,\"6/11/2007\",\"1:46pm\",+1.04,50.30,51.28,50.04,6651700\r\n\"MSFT\",30.11,\"6/11/2007\",\"1:51pm\",+0.06,30.05,30.25,29.93,24742128\r\n\"PFE\",26.44,\"6/11/2007\",\"1:46pm\",-0.08,26.50,26.54,26.31,15813727\r\n\"PG\",63.12,\"6/11/2007\",\"1:46pm\",+0.05,62.80,63.14,62.75,4099546\r\n\"T\",40.3073,\"6/11/2007\",\"1:46pm\",+0.0473,40.20,40.34,39.89,7901800\r\n\"UTX\",70.10,\"6/11/2007\",\"1:46pm\",-0.13,69.85,70.20,69.51,1284300\r\n\"VZ\",43.42,\"6/11/2007\",\"1:46pm\",+0.35,42.95,43.45,42.88,5473215\r\n\"WMT\",49.94,\"6/11/2007\",\"1:46pm\",-0.14,49.90,50.12,49.55,7155307\r\n\"XOM\",83.66,\"6/11/2007\",\"1:46pm\",+0.98,82.68,83.72,82.35,7157900\r\n\"AA\",39.55,\"6/11/2007\",\"1:51pm\",-0.11,39.67,40.18,39.43,2705680\r\n\"AIG\",71.84,\"6/11/2007\",\"1:51pm\",+0.31,71.29,71.90,71.15,2760896\r\n\"AXP\",63.245,\"6/11/2007\",\"1:51pm\",+0.205,62.79,63.32,62.42,2226930\r\n\"BA\",97.96,\"6/11/2007\",\"1:51pm\",-0.23,98.25,98.79,97.59,1793700\r\n\"C\",53.63,\"6/11/2007\",\"1:51pm\",+0.30,53.20,53.77,52.81,6926294\r\n\"CAT\",79.04,\"6/11/2007\",\"1:51pm\",+0.52,78.32,79.46,78.06,1958652\r\n\"DD\",50.83,\"6/11/2007\",\"1:51pm\",-0.30,51.13,51.21,50.59,2232897\r\n\"DIS\",34.20,\"6/11/2007\",\"1:50pm\",0.00,34.28,34.44,34.12,3823150\r\n\"GE\",37.52,\"6/11/2007\",\"1:51pm\",+0.20,37.07,37.56,37.05,13797801\r\n\"GM\",31.47,\"6/11/2007\",\"1:51pm\",+0.47,31.00,31.62,30.90,8801851\r\n\"HD\",37.77,\"6/11/2007\",\"1:51pm\",-0.18,37.78,37.83,37.62,6761167\r\n\"HON\",57.13,\"6/11/2007\",\"1:51pm\",-0.25,57.25,57.40,56.91,2327542\r\n\"HPQ\",46.12,\"6/11/2007\",\"1:51pm\",+0.42,45.80,46.29,45.46,6316649\r\n\"IBM\",103.55,\"6/11/2007\",\"1:51pm\",+0.48,102.87,104.00,102.50,2777904\r\n\"INTC\",22.03,\"6/11/2007\",\"1:56pm\",+0.20,21.70,22.08,21.69,26065954\r\n\"JNJ\",62.53,\"6/11/2007\",\"1:51pm\",+0.40,62.89,62.89,62.15,5210478\r\n\"JPM\",50.81,\"6/11/2007\",\"1:51pm\",+0.40,50.41,50.84,50.05,5222100\r\n\"KO\",51.75,\"6/11/2007\",\"1:51pm\",+0.08,51.67,51.85,51.32,6558652\r\n\"MCD\",51.39,\"6/11/2007\",\"1:51pm\",-0.02,51.47,51.62,50.98,3074814\r\n\"MMM\",85.548,\"6/11/2007\",\"1:51pm\",-0.392,85.94,85.98,85.28,1372000\r\n\"MO\",70.27,\"6/11/2007\",\"1:50pm\",-0.03,70.25,70.50,69.76,4986985\r\n\"MRK\",51.16,\"6/11/2007\",\"1:51pm\",+1.02,50.30,51.28,50.04,6821600\r\n\"MSFT\",30.13,\"6/11/2007\",\"1:56pm\",+0.08,30.05,30.25,29.93,24970772\r\n\"PFE\",26.448,\"6/11/2007\",\"1:51pm\",-0.072,26.50,26.54,26.31,16248877\r\n\"PG\",63.13,\"6/11/2007\",\"1:51pm\",+0.06,62.80,63.14,62.75,4157646\r\n\"T\",40.31,\"6/11/2007\",\"1:51pm\",+0.05,40.20,40.34,39.89,8018500\r\n\"UTX\",70.15,\"6/11/2007\",\"1:51pm\",-0.08,69.85,70.20,69.51,1321400\r\n\"VZ\",43.44,\"6/11/2007\",\"1:51pm\",+0.37,42.95,43.45,42.88,5538015\r\n\"WMT\",49.86,\"6/11/2007\",\"1:51pm\",-0.22,49.90,50.12,49.55,7285407\r\n\"XOM\",83.56,\"6/11/2007\",\"1:51pm\",+0.88,82.68,83.72,82.35,7364600\r\n\"AA\",39.53,\"6/11/2007\",\"1:56pm\",-0.13,39.67,40.18,39.43,2721380\r\n\"AIG\",71.7918,\"6/11/2007\",\"1:56pm\",+0.2618,71.29,71.90,71.15,2817696\r\n\"AXP\",63.20,\"6/11/2007\",\"1:56pm\",+0.16,62.79,63.32,62.42,2237230\r\n\"BA\",97.89,\"6/11/2007\",\"1:56pm\",-0.30,98.25,98.79,97.59,1808600\r\n\"C\",53.60,\"6/11/2007\",\"1:56pm\",+0.27,53.20,53.77,52.81,7078394\r\n\"CAT\",79.02,\"6/11/2007\",\"1:56pm\",+0.50,78.32,79.46,78.06,1980452\r\n\"DD\",50.83,\"6/11/2007\",\"1:56pm\",-0.30,51.13,51.21,50.59,2245697\r\n\"DIS\",34.195,\"6/11/2007\",\"1:56pm\",-0.005,34.28,34.44,34.12,3863950\r\n\"GE\",37.52,\"6/11/2007\",\"1:56pm\",+0.20,37.07,37.56,37.05,13986001\r\n\"GM\",31.49,\"6/11/2007\",\"1:56pm\",+0.49,31.00,31.62,30.90,8849851\r\n\"HD\",37.75,\"6/11/2007\",\"1:56pm\",-0.20,37.78,37.83,37.62,6819267\r\n\"HON\",57.11,\"6/11/2007\",\"1:56pm\",-0.27,57.25,57.40,56.91,2354542\r\n\"HPQ\",46.122,\"6/11/2007\",\"1:56pm\",+0.422,45.80,46.29,45.46,6380249\r\n\"IBM\",103.53,\"6/11/2007\",\"1:56pm\",+0.46,102.87,104.00,102.50,2802904\r\n\"INTC\",22.01,\"6/11/2007\",\"2:01pm\",+0.18,21.70,22.08,21.69,26445260\r\n\"JNJ\",62.52,\"6/11/2007\",\"1:56pm\",+0.39,62.89,62.89,62.15,5265678\r\n\"JPM\",50.77,\"6/11/2007\",\"1:56pm\",+0.36,50.41,50.84,50.05,5291300\r\n\"KO\",51.7619,\"6/11/2007\",\"1:56pm\",+0.0919,51.67,51.85,51.32,6606152\r\n\"MCD\",51.36,\"6/11/2007\",\"1:56pm\",-0.05,51.47,51.62,50.98,3110114\r\n\"MMM\",85.53,\"6/11/2007\",\"1:56pm\",-0.41,85.94,85.98,85.28,1402100\r\n\"MO\",70.27,\"6/11/2007\",\"1:56pm\",-0.03,70.25,70.50,69.76,5016285\r\n\"MRK\",51.11,\"6/11/2007\",\"1:56pm\",+0.97,50.30,51.28,50.04,6899000\r\n\"MSFT\",30.12,\"6/11/2007\",\"2:01pm\",+0.07,30.05,30.25,29.93,25277544\r\n\"PFE\",26.44,\"6/11/2007\",\"1:56pm\",-0.08,26.50,26.54,26.31,16455677\r\n\"PG\",63.13,\"6/11/2007\",\"1:56pm\",+0.06,62.80,63.15,62.75,4256946\r\n\"T\",40.28,\"6/11/2007\",\"1:56pm\",+0.02,40.20,40.34,39.89,8132800\r\n\"UTX\",70.154,\"6/11/2007\",\"1:56pm\",-0.076,69.85,70.20,69.51,1344300\r\n\"VZ\",43.47,\"6/11/2007\",\"1:56pm\",+0.40,42.95,43.47,42.88,5621815\r\n\"WMT\",49.88,\"6/11/2007\",\"1:56pm\",-0.20,49.90,50.12,49.55,7420907\r\n\"XOM\",83.52,\"6/11/2007\",\"1:56pm\",+0.84,82.68,83.72,82.35,7459300\r\n\"AA\",39.491,\"6/11/2007\",\"2:01pm\",-0.169,39.67,40.18,39.43,2763280\r\n\"AIG\",71.77,\"6/11/2007\",\"2:01pm\",+0.24,71.29,71.90,71.15,2879896\r\n\"AXP\",63.17,\"6/11/2007\",\"2:01pm\",+0.13,62.79,63.32,62.42,2261630\r\n\"BA\",97.83,\"6/11/2007\",\"2:01pm\",-0.36,98.25,98.79,97.59,1833100\r\n\"C\",53.55,\"6/11/2007\",\"2:01pm\",+0.22,53.20,53.77,52.81,7307094\r\n\"CAT\",79.00,\"6/11/2007\",\"2:01pm\",+0.48,78.32,79.46,78.06,2025552\r\n\"DD\",50.85,\"6/11/2007\",\"2:01pm\",-0.28,51.13,51.21,50.59,2277197\r\n\"DIS\",34.20,\"6/11/2007\",\"2:01pm\",0.00,34.28,34.44,34.12,3931450\r\n\"GE\",37.51,\"6/11/2007\",\"2:01pm\",+0.19,37.07,37.56,37.05,14260601\r\n\"GM\",31.50,\"6/11/2007\",\"2:01pm\",+0.50,31.00,31.62,30.90,8948751\r\n\"HD\",37.75,\"6/11/2007\",\"2:01pm\",-0.20,37.78,37.83,37.62,7227367\r\n\"HON\",57.13,\"6/11/2007\",\"2:01pm\",-0.25,57.25,57.40,56.91,2422142\r\n\"HPQ\",46.08,\"6/11/2007\",\"2:01pm\",+0.38,45.80,46.29,45.46,6426249\r\n\"IBM\",103.51,\"6/11/2007\",\"2:01pm\",+0.44,102.87,104.00,102.50,2839204\r\n\"INTC\",22.02,\"6/11/2007\",\"2:06pm\",+0.19,21.70,22.08,21.69,26751104\r\n\"JNJ\",62.50,\"6/11/2007\",\"2:01pm\",+0.37,62.89,62.89,62.15,5350858\r\n\"JPM\",50.70,\"6/11/2007\",\"2:01pm\",+0.29,50.41,50.84,50.05,5396400\r\n\"KO\",51.76,\"6/11/2007\",\"2:01pm\",+0.09,51.67,51.85,51.32,6660752\r\n\"MCD\",51.3727,\"6/11/2007\",\"2:01pm\",-0.0373,51.47,51.62,50.98,3168414\r\n\"MMM\",85.58,\"6/11/2007\",\"2:01pm\",-0.36,85.94,85.98,85.28,1440600\r\n\"MO\",70.24,\"6/11/2007\",\"2:01pm\",-0.06,70.25,70.50,69.76,5077085\r\n\"MRK\",51.09,\"6/11/2007\",\"2:01pm\",+0.95,50.30,51.28,50.04,6995800\r\n\"MSFT\",30.12,\"6/11/2007\",\"2:06pm\",+0.07,30.05,30.25,29.93,25466984\r\n\"PFE\",26.44,\"6/11/2007\",\"2:01pm\",-0.08,26.50,26.54,26.31,16750477\r\n\"PG\",63.12,\"6/11/2007\",\"2:01pm\",+0.05,62.80,63.15,62.75,4334646\r\n\"T\",40.27,\"6/11/2007\",\"2:01pm\",+0.01,40.20,40.34,39.89,8457200\r\n\"UTX\",70.11,\"6/11/2007\",\"2:01pm\",-0.12,69.85,70.20,69.51,1362600\r\n\"VZ\",43.46,\"6/11/2007\",\"2:01pm\",+0.39,42.95,43.47,42.88,5698015\r\n\"WMT\",49.79,\"6/11/2007\",\"2:01pm\",-0.29,49.90,50.12,49.55,7569507\r\n\"XOM\",83.43,\"6/11/2007\",\"2:01pm\",+0.75,82.68,83.72,82.35,7716900\r\n\"AA\",39.50,\"6/11/2007\",\"2:06pm\",-0.16,39.67,40.18,39.43,2841280\r\n\"AIG\",71.85,\"6/11/2007\",\"2:06pm\",+0.32,71.29,71.90,71.15,2945096\r\n\"AXP\",63.23,\"6/11/2007\",\"2:06pm\",+0.19,62.79,63.32,62.42,2280130\r\n\"BA\",97.92,\"6/11/2007\",\"2:06pm\",-0.27,98.25,98.79,97.59,1847000\r\n\"C\",53.56,\"6/11/2007\",\"2:06pm\",+0.23,53.20,53.77,52.81,7466394\r\n\"CAT\",79.11,\"6/11/2007\",\"2:06pm\",+0.59,78.32,79.46,78.06,2048252\r\n\"DD\",50.86,\"6/11/2007\",\"2:06pm\",-0.27,51.13,51.21,50.59,2309597\r\n\"DIS\",34.19,\"6/11/2007\",\"2:06pm\",-0.01,34.28,34.44,34.12,3986950\r\n\"GE\",37.51,\"6/11/2007\",\"2:06pm\",+0.19,37.07,37.56,37.05,14444801\r\n\"GM\",31.50,\"6/11/2007\",\"2:06pm\",+0.50,31.00,31.62,30.90,9108451\r\n\"HD\",37.75,\"6/11/2007\",\"2:06pm\",-0.20,37.78,37.83,37.62,7407867\r\n\"HON\",57.11,\"6/11/2007\",\"2:06pm\",-0.27,57.25,57.40,56.91,2462942\r\n\"HPQ\",46.08,\"6/11/2007\",\"2:06pm\",+0.38,45.80,46.29,45.46,6509849\r\n\"IBM\",103.54,\"6/11/2007\",\"2:06pm\",+0.47,102.87,104.00,102.50,2861904\r\n\"INTC\",22.03,\"6/11/2007\",\"2:11pm\",+0.20,21.70,22.08,21.69,26974348\r\n\"JNJ\",62.50,\"6/11/2007\",\"2:06pm\",+0.37,62.89,62.89,62.15,5513358\r\n\"JPM\",50.65,\"6/11/2007\",\"2:06pm\",+0.24,50.41,50.84,50.05,5565600\r\n\"KO\",51.77,\"6/11/2007\",\"2:06pm\",+0.10,51.67,51.85,51.32,6687652\r\n\"MCD\",51.41,\"6/11/2007\",\"2:06pm\",0.00,51.47,51.62,50.98,3209614\r\n\"MMM\",85.53,\"6/11/2007\",\"2:06pm\",-0.41,85.94,85.98,85.28,1470200\r\n\"MO\",70.29,\"6/11/2007\",\"2:06pm\",-0.01,70.25,70.50,69.76,5107185\r\n\"MRK\",51.05,\"6/11/2007\",\"2:06pm\",+0.91,50.30,51.28,50.04,7162100\r\n\"MSFT\",30.13,\"6/11/2007\",\"2:11pm\",+0.08,30.05,30.25,29.93,25965886\r\n\"PFE\",26.44,\"6/11/2007\",\"2:06pm\",-0.08,26.50,26.54,26.31,17179996\r\n\"PG\",63.14,\"6/11/2007\",\"2:06pm\",+0.07,62.80,63.15,62.75,4427046\r\n\"T\",40.26,\"6/11/2007\",\"2:06pm\",0.00,40.20,40.34,39.89,8620800\r\n\"UTX\",70.12,\"6/11/2007\",\"2:06pm\",-0.11,69.85,70.20,69.51,1386800\r\n\"VZ\",43.44,\"6/11/2007\",\"2:06pm\",+0.37,42.95,43.47,42.88,5829840\r\n\"WMT\",49.83,\"6/11/2007\",\"2:06pm\",-0.25,49.90,50.12,49.55,7695307\r\n\"XOM\",83.44,\"6/11/2007\",\"2:06pm\",+0.76,82.68,83.72,82.35,7916100\r\n\"AA\",39.53,\"6/11/2007\",\"2:11pm\",-0.13,39.67,40.18,39.43,2880880\r\n\"AIG\",71.98,\"6/11/2007\",\"2:11pm\",+0.45,71.29,71.99,71.15,3103496\r\n\"AXP\",63.38,\"6/11/2007\",\"2:11pm\",+0.34,62.79,63.39,62.42,2308630\r\n\"BA\",97.93,\"6/11/2007\",\"2:11pm\",-0.26,98.25,98.79,97.59,1881100\r\n\"C\",53.72,\"6/11/2007\",\"2:11pm\",+0.39,53.20,53.77,52.81,7588294\r\n\"CAT\",79.19,\"6/11/2007\",\"2:11pm\",+0.67,78.32,79.46,78.06,2131352\r\n\"DD\",50.90,\"6/11/2007\",\"2:11pm\",-0.23,51.13,51.21,50.59,2340097\r\n\"DIS\",34.21,\"6/11/2007\",\"2:11pm\",+0.01,34.28,34.44,34.12,4058750\r\n\"GE\",37.57,\"6/11/2007\",\"2:11pm\",+0.25,37.07,37.57,37.05,14734801\r\n\"GM\",31.57,\"6/11/2007\",\"2:11pm\",+0.57,31.00,31.62,30.90,9594251\r\n\"HD\",37.76,\"6/11/2007\",\"2:11pm\",-0.19,37.78,37.83,37.62,7474967\r\n\"HON\",57.19,\"6/11/2007\",\"2:11pm\",-0.19,57.25,57.40,56.91,2592542\r\n\"HPQ\",46.15,\"6/11/2007\",\"2:11pm\",+0.45,45.80,46.29,45.46,6633249\r\n\"IBM\",103.59,\"6/11/2007\",\"2:11pm\",+0.52,102.87,104.00,102.50,2884504\r\n\"INTC\",22.01,\"6/11/2007\",\"2:16pm\",+0.18,21.70,22.08,21.69,27482520\r\n\"JNJ\",62.53,\"6/11/2007\",\"2:11pm\",+0.40,62.89,62.89,62.15,5618158\r\n\"JPM\",50.74,\"6/11/2007\",\"2:11pm\",+0.33,50.41,50.84,50.05,5728500\r\n\"KO\",51.79,\"6/11/2007\",\"2:11pm\",+0.12,51.67,51.85,51.32,6761552\r\n\"MCD\",51.50,\"6/11/2007\",\"2:11pm\",+0.09,51.47,51.62,50.98,3260114\r\n\"MMM\",85.57,\"6/11/2007\",\"2:11pm\",-0.37,85.94,85.98,85.28,1508200\r\n\"MO\",70.34,\"6/11/2007\",\"2:11pm\",+0.04,70.25,70.50,69.76,5156485\r\n\"MRK\",51.21,\"6/11/2007\",\"2:11pm\",+1.07,50.30,51.28,50.04,7355400\r\n\"MSFT\",30.16,\"6/11/2007\",\"2:16pm\",+0.11,30.05,30.25,29.93,26411778\r\n\"PFE\",26.48,\"6/11/2007\",\"2:11pm\",-0.04,26.50,26.54,26.31,17302396\r\n\"PG\",63.1819,\"6/11/2007\",\"2:11pm\",+0.1119,62.80,63.19,62.75,4533846\r\n\"T\",40.25,\"6/11/2007\",\"2:11pm\",-0.01,40.20,40.34,39.89,8839900\r\n\"UTX\",70.20,\"6/11/2007\",\"2:11pm\",-0.03,69.85,70.20,69.51,1410200\r\n\"VZ\",43.49,\"6/11/2007\",\"2:11pm\",+0.42,42.95,43.49,42.88,5904936\r\n\"WMT\",49.882,\"6/11/2007\",\"2:11pm\",-0.198,49.90,50.12,49.55,7831607\r\n\"XOM\",83.51,\"6/11/2007\",\"2:11pm\",+0.83,82.68,83.72,82.35,8147200\r\n\"AA\",39.49,\"6/11/2007\",\"2:16pm\",-0.17,39.67,40.18,39.43,2953380\r\n\"AIG\",71.90,\"6/11/2007\",\"2:16pm\",+0.37,71.29,72.03,71.15,3208796\r\n\"AXP\",63.31,\"6/11/2007\",\"2:16pm\",+0.27,62.79,63.42,62.42,2337730\r\n\"BA\",97.92,\"6/11/2007\",\"2:16pm\",-0.27,98.25,98.79,97.59,1921100\r\n\"C\",53.65,\"6/11/2007\",\"2:16pm\",+0.32,53.20,53.77,52.81,7685194\r\n\"CAT\",79.08,\"6/11/2007\",\"2:16pm\",+0.56,78.32,79.46,78.06,2163952\r\n\"DD\",50.86,\"6/11/2007\",\"2:16pm\",-0.27,51.13,51.21,50.59,2355597\r\n\"DIS\",34.20,\"6/11/2007\",\"2:16pm\",0.00,34.28,34.44,34.12,4227650\r\n\"GE\",37.54,\"6/11/2007\",\"2:16pm\",+0.22,37.07,37.61,37.05,15329801\r\n\"GM\",31.63,\"6/11/2007\",\"2:16pm\",+0.63,31.00,31.64,30.90,9900251\r\n\"HD\",37.75,\"6/11/2007\",\"2:16pm\",-0.20,37.78,37.83,37.62,7614667\r\n\"HON\",57.16,\"6/11/2007\",\"2:16pm\",-0.22,57.25,57.40,56.91,2626742\r\n\"HPQ\",46.14,\"6/11/2007\",\"2:16pm\",+0.44,45.80,46.29,45.46,6705149\r\n\"IBM\",103.48,\"6/11/2007\",\"2:16pm\",+0.41,102.87,104.00,102.50,2932004\r\n\"INTC\",22.01,\"6/11/2007\",\"2:21pm\",+0.18,21.70,22.08,21.69,27617028\r\n\"JNJ\",62.49,\"6/11/2007\",\"2:16pm\",+0.36,62.89,62.89,62.15,5748158\r\n\"JPM\",50.71,\"6/11/2007\",\"2:16pm\",+0.30,50.41,50.84,50.05,5968400\r\n\"KO\",51.75,\"6/11/2007\",\"2:16pm\",+0.08,51.67,51.85,51.32,6798052\r\n\"MCD\",51.40,\"6/11/2007\",\"2:16pm\",-0.01,51.47,51.62,50.98,3310514\r\n\"MMM\",85.53,\"6/11/2007\",\"2:16pm\",-0.41,85.94,85.98,85.28,1531600\r\n\"MO\",70.33,\"6/11/2007\",\"2:16pm\",+0.03,70.25,70.50,69.76,5204085\r\n\"MRK\",51.17,\"6/11/2007\",\"2:16pm\",+1.03,50.30,51.28,50.04,7421000\r\n\"MSFT\",30.18,\"6/11/2007\",\"2:21pm\",+0.13,30.05,30.25,29.93,26830224\r\n\"PFE\",26.46,\"6/11/2007\",\"2:16pm\",-0.06,26.50,26.54,26.31,17549496\r\n\"PG\",63.18,\"6/11/2007\",\"2:16pm\",+0.11,62.80,63.20,62.75,4586346\r\n\"T\",40.30,\"6/11/2007\",\"2:16pm\",+0.04,40.20,40.34,39.89,9022910\r\n\"UTX\",70.192,\"6/11/2007\",\"2:16pm\",-0.038,69.85,70.25,69.51,1444200\r\n\"VZ\",43.52,\"6/11/2007\",\"2:16pm\",+0.45,42.95,43.535,42.88,6098036\r\n\"WMT\",49.85,\"6/11/2007\",\"2:16pm\",-0.23,49.90,50.12,49.55,7900607\r\n\"XOM\",83.37,\"6/11/2007\",\"2:16pm\",+0.69,82.68,83.85,82.35,8845100\r\n\"AA\",39.46,\"6/11/2007\",\"2:21pm\",-0.20,39.67,40.18,39.43,3029880\r\n\"AIG\",71.92,\"6/11/2007\",\"2:21pm\",+0.39,71.29,72.03,71.15,3245196\r\n\"AXP\",63.35,\"6/11/2007\",\"2:21pm\",+0.31,62.79,63.42,62.42,2353430\r\n\"BA\",97.97,\"6/11/2007\",\"2:21pm\",-0.22,98.25,98.79,97.59,1944500\r\n\"C\",53.69,\"6/11/2007\",\"2:21pm\",+0.36,53.20,53.77,52.81,7757194\r\n\"CAT\",79.12,\"6/11/2007\",\"2:21pm\",+0.60,78.32,79.46,78.06,2214452\r\n\"DD\",50.899,\"6/11/2007\",\"2:21pm\",-0.231,51.13,51.21,50.59,2373297\r\n\"DIS\",34.23,\"6/11/2007\",\"2:21pm\",+0.03,34.28,34.44,34.12,4262150\r\n\"GE\",37.52,\"6/11/2007\",\"2:21pm\",+0.20,37.07,37.61,37.05,15596301\r\n\"GM\",31.68,\"6/11/2007\",\"2:21pm\",+0.68,31.00,31.69,30.90,10286001\r\n\"HD\",37.7618,\"6/11/2007\",\"2:21pm\",-0.1882,37.78,37.83,37.62,7739967\r\n\"HON\",57.28,\"6/11/2007\",\"2:21pm\",-0.10,57.25,57.40,56.91,2699742\r\n\"HPQ\",46.16,\"6/11/2007\",\"2:21pm\",+0.46,45.80,46.29,45.46,6773849\r\n\"IBM\",103.47,\"6/11/2007\",\"2:21pm\",+0.40,102.87,104.00,102.50,2958304\r\n\"INTC\",22.008,\"6/11/2007\",\"2:26pm\",+0.178,21.70,22.08,21.69,28003456\r\n\"JNJ\",62.53,\"6/11/2007\",\"2:21pm\",+0.40,62.89,62.89,62.15,5814358\r\n\"JPM\",50.721,\"6/11/2007\",\"2:21pm\",+0.311,50.41,50.84,50.05,6057700\r\n\"KO\",51.77,\"6/11/2007\",\"2:21pm\",+0.10,51.67,51.85,51.32,6832552\r\n\"MCD\",51.37,\"6/11/2007\",\"2:21pm\",-0.04,51.47,51.62,50.98,3514114\r\n\"MMM\",85.58,\"6/11/2007\",\"2:21pm\",-0.36,85.94,85.98,85.28,1584600\r\n\"MO\",70.39,\"6/11/2007\",\"2:21pm\",+0.09,70.25,70.50,69.76,5237185\r\n\"MRK\",51.25,\"6/11/2007\",\"2:21pm\",+1.11,50.30,51.28,50.04,7541700\r\n\"MSFT\",30.18,\"6/11/2007\",\"2:26pm\",+0.13,30.05,30.25,29.93,27263748\r\n\"PFE\",26.45,\"6/11/2007\",\"2:21pm\",-0.07,26.50,26.54,26.31,17743896\r\n\"PG\",63.17,\"6/11/2007\",\"2:21pm\",+0.10,62.80,63.20,62.75,4649246\r\n\"T\",40.36,\"6/11/2007\",\"2:21pm\",+0.10,40.20,40.37,39.89,9204010\r\n\"UTX\",70.20,\"6/11/2007\",\"2:21pm\",-0.03,69.85,70.25,69.51,1518400\r\n\"VZ\",43.57,\"6/11/2007\",\"2:21pm\",+0.50,42.95,43.58,42.88,6405057\r\n\"WMT\",49.84,\"6/11/2007\",\"2:21pm\",-0.24,49.90,50.12,49.55,7979707\r\n\"XOM\",83.46,\"6/11/2007\",\"2:21pm\",+0.78,82.68,83.85,82.35,9131100\r\n\"AA\",39.46,\"6/11/2007\",\"2:26pm\",-0.20,39.67,40.18,39.43,3106580\r\n\"AIG\",71.92,\"6/11/2007\",\"2:26pm\",+0.39,71.29,72.03,71.15,3332596\r\n\"AXP\",63.34,\"6/11/2007\",\"2:26pm\",+0.30,62.79,63.42,62.42,2372530\r\n\"BA\",97.92,\"6/11/2007\",\"2:26pm\",-0.27,98.25,98.79,97.59,1972600\r\n\"C\",53.68,\"6/11/2007\",\"2:26pm\",+0.35,53.20,53.77,52.81,7862194\r\n\"CAT\",79.06,\"6/11/2007\",\"2:26pm\",+0.54,78.32,79.46,78.06,2621452\r\n\"DD\",50.85,\"6/11/2007\",\"2:26pm\",-0.28,51.13,51.21,50.59,2400597\r\n\"DIS\",34.21,\"6/11/2007\",\"2:26pm\",+0.01,34.28,34.44,34.12,4329650\r\n\"GE\",37.48,\"6/11/2007\",\"2:26pm\",+0.16,37.07,37.61,37.05,15908401\r\n\"GM\",31.76,\"6/11/2007\",\"2:26pm\",+0.76,31.00,31.77,30.90,10673201\r\n\"HD\",37.76,\"6/11/2007\",\"2:26pm\",-0.19,37.78,37.83,37.62,7797367\r\n\"HON\",57.23,\"6/11/2007\",\"2:26pm\",-0.15,57.25,57.40,56.91,2798642\r\n\"HPQ\",46.16,\"6/11/2007\",\"2:26pm\",+0.46,45.80,46.29,45.46,6850349\r\n\"IBM\",103.52,\"6/11/2007\",\"2:26pm\",+0.45,102.87,104.00,102.50,2994204\r\n\"INTC\",22.01,\"6/11/2007\",\"2:31pm\",+0.18,21.70,22.08,21.69,28166784\r\n\"JNJ\",62.52,\"6/11/2007\",\"2:26pm\",+0.39,62.89,62.89,62.15,5858958\r\n\"JPM\",50.72,\"6/11/2007\",\"2:26pm\",+0.31,50.41,50.84,50.05,6141900\r\n\"KO\",51.80,\"6/11/2007\",\"2:26pm\",+0.13,51.67,51.85,51.32,6873652\r\n\"MCD\",51.37,\"6/11/2007\",\"2:26pm\",-0.04,51.47,51.62,50.98,3549714\r\n\"MMM\",85.55,\"6/11/2007\",\"2:26pm\",-0.39,85.94,85.98,85.28,1616200\r\n\"MO\",70.3611,\"6/11/2007\",\"2:26pm\",+0.0611,70.25,70.50,69.76,5288885\r\n\"MRK\",51.26,\"6/11/2007\",\"2:26pm\",+1.12,50.30,51.28,50.04,7641900\r\n\"MSFT\",30.18,\"6/11/2007\",\"2:31pm\",+0.13,30.05,30.25,29.93,27477918\r\n\"PFE\",26.45,\"6/11/2007\",\"2:26pm\",-0.07,26.50,26.54,26.31,17889796\r\n\"PG\",63.13,\"6/11/2007\",\"2:26pm\",+0.06,62.80,63.20,62.75,4759446\r\n\"T\",40.43,\"6/11/2007\",\"2:26pm\",+0.17,40.20,40.47,39.89,9441710\r\n\"UTX\",70.15,\"6/11/2007\",\"2:26pm\",-0.08,69.85,70.25,69.51,1552400\r\n\"VZ\",43.58,\"6/11/2007\",\"2:26pm\",+0.51,42.95,43.61,42.88,6495057\r\n\"WMT\",49.82,\"6/11/2007\",\"2:26pm\",-0.26,49.90,50.12,49.55,8014807\r\n\"XOM\",83.40,\"6/11/2007\",\"2:26pm\",+0.72,82.68,83.85,82.35,9250000\r\n\"AA\",39.43,\"6/11/2007\",\"2:31pm\",-0.23,39.67,40.18,39.42,3146680\r\n\"AIG\",71.88,\"6/11/2007\",\"2:31pm\",+0.35,71.29,72.03,71.15,3377696\r\n\"AXP\",63.30,\"6/11/2007\",\"2:31pm\",+0.26,62.79,63.42,62.42,2396630\r\n\"BA\",97.83,\"6/11/2007\",\"2:31pm\",-0.36,98.25,98.79,97.59,1992300\r\n\"C\",53.64,\"6/11/2007\",\"2:31pm\",+0.31,53.20,53.77,52.81,7963894\r\n\"CAT\",79.00,\"6/11/2007\",\"2:31pm\",+0.48,78.32,79.46,78.06,2657252\r\n\"DD\",50.85,\"6/11/2007\",\"2:31pm\",-0.28,51.13,51.21,50.59,2419197\r\n\"DIS\",34.20,\"6/11/2007\",\"2:31pm\",0.00,34.28,34.44,34.12,4416950\r\n\"GE\",37.48,\"6/11/2007\",\"2:31pm\",+0.16,37.07,37.61,37.05,16180901\r\n\"GM\",31.74,\"6/11/2007\",\"2:31pm\",+0.74,31.00,31.77,30.90,10859101\r\n\"HD\",37.76,\"6/11/2007\",\"2:31pm\",-0.19,37.78,37.83,37.62,8074667\r\n\"HON\",57.20,\"6/11/2007\",\"2:31pm\",-0.18,57.25,57.40,56.91,2836642\r\n\"HPQ\",46.15,\"6/11/2007\",\"2:31pm\",+0.45,45.80,46.29,45.46,6906449\r\n\"IBM\",103.46,\"6/11/2007\",\"2:31pm\",+0.39,102.87,104.00,102.50,3014104\r\n\"INTC\",22.01,\"6/11/2007\",\"2:36pm\",+0.18,21.70,22.08,21.69,28623660\r\n\"JNJ\",62.48,\"6/11/2007\",\"2:31pm\",+0.35,62.89,62.89,62.15,5916458\r\n\"JPM\",50.68,\"6/11/2007\",\"2:31pm\",+0.27,50.41,50.84,50.05,6212400\r\n\"KO\",51.75,\"6/11/2007\",\"2:31pm\",+0.08,51.67,51.85,51.32,6921352\r\n\"MCD\",51.31,\"6/11/2007\",\"2:31pm\",-0.10,51.47,51.62,50.98,3570814\r\n\"MMM\",85.56,\"6/11/2007\",\"2:31pm\",-0.38,85.94,85.98,85.28,1632000\r\n\"MO\",70.34,\"6/11/2007\",\"2:31pm\",+0.04,70.25,70.50,69.76,5316285\r\n\"MRK\",51.25,\"6/11/2007\",\"2:31pm\",+1.11,50.30,51.35,50.04,7816300\r\n\"MSFT\",30.18,\"6/11/2007\",\"2:36pm\",+0.13,30.05,30.25,29.93,27776860\r\n\"PFE\",26.43,\"6/11/2007\",\"2:31pm\",-0.09,26.50,26.54,26.31,18072696\r\n\"PG\",63.09,\"6/11/2007\",\"2:31pm\",+0.02,62.80,63.20,62.75,4849346\r\n\"T\",40.37,\"6/11/2007\",\"2:31pm\",+0.11,40.20,40.47,39.89,9632910\r\n\"UTX\",70.15,\"6/11/2007\",\"2:31pm\",-0.08,69.85,70.25,69.51,1566500\r\n\"VZ\",43.53,\"6/11/2007\",\"2:31pm\",+0.46,42.95,43.61,42.88,6607457\r\n\"WMT\",49.77,\"6/11/2007\",\"2:31pm\",-0.31,49.90,50.12,49.55,8080507\r\n\"XOM\",83.35,\"6/11/2007\",\"2:31pm\",+0.67,82.68,83.85,82.35,9365400\r\n\"AA\",39.39,\"6/11/2007\",\"2:36pm\",-0.27,39.67,40.18,39.38,3233280\r\n\"AIG\",71.87,\"6/11/2007\",\"2:36pm\",+0.34,71.29,72.03,71.15,3455896\r\n\"AXP\",63.22,\"6/11/2007\",\"2:36pm\",+0.18,62.79,63.42,62.42,2427730\r\n\"BA\",97.77,\"6/11/2007\",\"2:36pm\",-0.42,98.25,98.79,97.59,2005500\r\n\"C\",53.63,\"6/11/2007\",\"2:36pm\",+0.30,53.20,53.77,52.81,8024794\r\n\"CAT\",79.01,\"6/11/2007\",\"2:36pm\",+0.49,78.32,79.46,78.06,2680952\r\n\"DD\",50.83,\"6/11/2007\",\"2:36pm\",-0.30,51.13,51.21,50.59,2435997\r\n\"DIS\",34.19,\"6/11/2007\",\"2:36pm\",-0.01,34.28,34.44,34.12,4466950\r\n\"GE\",37.44,\"6/11/2007\",\"2:36pm\",+0.12,37.07,37.61,37.05,16472401\r\n\"GM\",31.70,\"6/11/2007\",\"2:36pm\",+0.70,31.00,31.79,30.90,11121251\r\n\"HD\",37.75,\"6/11/2007\",\"2:36pm\",-0.20,37.78,37.83,37.62,8123767\r\n\"HON\",57.16,\"6/11/2007\",\"2:36pm\",-0.22,57.25,57.40,56.91,2882942\r\n\"HPQ\",46.15,\"6/11/2007\",\"2:36pm\",+0.45,45.80,46.29,45.46,6971049\r\n\"IBM\",103.42,\"6/11/2007\",\"2:36pm\",+0.35,102.87,104.00,102.50,3050204\r\n\"INTC\",22.0203,\"6/11/2007\",\"2:41pm\",+0.1903,21.70,22.08,21.69,28975326\r\n\"JNJ\",62.48,\"6/11/2007\",\"2:36pm\",+0.35,62.89,62.89,62.15,5985258\r\n\"JPM\",50.67,\"6/11/2007\",\"2:36pm\",+0.26,50.41,50.84,50.05,6275300\r\n\"KO\",51.75,\"6/11/2007\",\"2:36pm\",+0.08,51.67,51.85,51.32,7031052\r\n\"MCD\",51.33,\"6/11/2007\",\"2:36pm\",-0.08,51.47,51.62,50.98,3605014\r\n\"MMM\",85.47,\"6/11/2007\",\"2:36pm\",-0.47,85.94,85.98,85.28,1656600\r\n\"MO\",70.33,\"6/11/2007\",\"2:36pm\",+0.03,70.25,70.50,69.76,5350885\r\n\"MRK\",51.20,\"6/11/2007\",\"2:36pm\",+1.06,50.30,51.35,50.04,7984900\r\n\"MSFT\",30.18,\"6/11/2007\",\"2:41pm\",+0.13,30.05,30.25,29.93,30696744\r\n\"PFE\",26.44,\"6/11/2007\",\"2:36pm\",-0.08,26.50,26.54,26.31,18273796\r\n\"PG\",63.09,\"6/11/2007\",\"2:36pm\",+0.02,62.80,63.20,62.75,4902446\r\n\"T\",40.29,\"6/11/2007\",\"2:36pm\",+0.03,40.20,40.47,39.89,9901810\r\n\"UTX\",70.11,\"6/11/2007\",\"2:36pm\",-0.12,69.85,70.25,69.51,1589000\r\n\"VZ\",43.50,\"6/11/2007\",\"2:36pm\",+0.43,42.95,43.61,42.88,6714857\r\n\"WMT\",49.76,\"6/11/2007\",\"2:36pm\",-0.32,49.90,50.12,49.55,8135107\r\n\"XOM\",83.33,\"6/11/2007\",\"2:36pm\",+0.65,82.68,83.85,82.35,9480100\r\n\"AA\",39.39,\"6/11/2007\",\"2:41pm\",-0.27,39.67,40.18,39.34,3314380\r\n\"AIG\",71.89,\"6/11/2007\",\"2:41pm\",+0.36,71.29,72.03,71.15,3656596\r\n\"AXP\",63.23,\"6/11/2007\",\"2:41pm\",+0.19,62.79,63.42,62.42,2445430\r\n\"BA\",97.74,\"6/11/2007\",\"2:41pm\",-0.45,98.25,98.79,97.59,2031200\r\n\"C\",53.68,\"6/11/2007\",\"2:41pm\",+0.35,53.20,53.77,52.81,8087994\r\n\"CAT\",79.04,\"6/11/2007\",\"2:41pm\",+0.52,78.32,79.46,78.06,2712552\r\n\"DD\",50.81,\"6/11/2007\",\"2:41pm\",-0.32,51.13,51.21,50.59,2447897\r\n\"DIS\",34.19,\"6/11/2007\",\"2:41pm\",-0.01,34.28,34.44,34.12,4526150\r\n\"GE\",37.47,\"6/11/2007\",\"2:41pm\",+0.15,37.07,37.61,37.05,16819600\r\n\"GM\",31.65,\"6/11/2007\",\"2:41pm\",+0.65,31.00,31.79,30.90,11306951\r\n\"HD\",37.76,\"6/11/2007\",\"2:41pm\",-0.19,37.78,37.83,37.62,8189567\r\n\"HON\",57.13,\"6/11/2007\",\"2:41pm\",-0.25,57.25,57.40,56.91,2913842\r\n\"HPQ\",46.13,\"6/11/2007\",\"2:41pm\",+0.43,45.80,46.29,45.46,7057949\r\n\"IBM\",103.44,\"6/11/2007\",\"2:41pm\",+0.37,102.87,104.00,102.50,3087004\r\n\"INTC\",22.02,\"6/11/2007\",\"2:46pm\",+0.19,21.70,22.08,21.69,29252326\r\n\"JNJ\",62.48,\"6/11/2007\",\"2:41pm\",+0.35,62.89,62.89,62.15,6046058\r\n\"JPM\",50.71,\"6/11/2007\",\"2:41pm\",+0.30,50.41,50.84,50.05,6338800\r\n\"KO\",51.76,\"6/11/2007\",\"2:41pm\",+0.09,51.67,51.85,51.32,7064552\r\n\"MCD\",51.29,\"6/11/2007\",\"2:41pm\",-0.12,51.47,51.62,50.98,3648414\r\n\"MMM\",85.50,\"6/11/2007\",\"2:41pm\",-0.44,85.94,85.98,85.28,1687900\r\n\"MO\",70.34,\"6/11/2007\",\"2:41pm\",+0.04,70.25,70.50,69.76,5406985\r\n\"MRK\",51.18,\"6/11/2007\",\"2:41pm\",+1.04,50.30,51.35,50.04,8103700\r\n\"MSFT\",30.22,\"6/11/2007\",\"2:46pm\",+0.17,30.05,30.25,29.93,31209364\r\n\"PFE\",26.43,\"6/11/2007\",\"2:41pm\",-0.09,26.50,26.54,26.31,18438496\r\n\"PG\",63.11,\"6/11/2007\",\"2:41pm\",+0.04,62.80,63.20,62.75,4959446\r\n\"T\",40.27,\"6/11/2007\",\"2:41pm\",+0.01,40.20,40.47,39.89,10050710\r\n\"UTX\",70.11,\"6/11/2007\",\"2:41pm\",-0.12,69.85,70.25,69.51,1626700\r\n\"VZ\",43.50,\"6/11/2007\",\"2:41pm\",+0.43,42.95,43.61,42.88,6863457\r\n\"WMT\",49.79,\"6/11/2007\",\"2:41pm\",-0.29,49.90,50.12,49.55,8245307\r\n\"XOM\",83.37,\"6/11/2007\",\"2:41pm\",+0.69,82.68,83.85,82.35,9563400\r\n\"AA\",39.33,\"6/11/2007\",\"2:46pm\",-0.33,39.67,40.18,39.30,3399080\r\n\"AIG\",71.86,\"6/11/2007\",\"2:46pm\",+0.33,71.29,72.03,71.15,3740596\r\n\"AXP\",63.20,\"6/11/2007\",\"2:46pm\",+0.16,62.79,63.42,62.42,2463030\r\n\"BA\",97.78,\"6/11/2007\",\"2:46pm\",-0.41,98.25,98.79,97.59,2065200\r\n\"C\",53.67,\"6/11/2007\",\"2:46pm\",+0.34,53.20,53.77,52.81,8200694\r\n\"CAT\",79.00,\"6/11/2007\",\"2:46pm\",+0.48,78.32,79.46,78.06,2725452\r\n\"DD\",50.80,\"6/11/2007\",\"2:46pm\",-0.33,51.13,51.21,50.59,2457797\r\n\"DIS\",34.20,\"6/11/2007\",\"2:46pm\",0.00,34.28,34.44,34.12,4601150\r\n\"GE\",37.48,\"6/11/2007\",\"2:46pm\",+0.16,37.07,37.61,37.05,17056300\r\n\"GM\",31.65,\"6/11/2007\",\"2:46pm\",+0.65,31.00,31.79,30.90,11359851\r\n\"HD\",37.75,\"6/11/2007\",\"2:46pm\",-0.20,37.78,37.83,37.62,8424967\r\n\"HON\",57.18,\"6/11/2007\",\"2:46pm\",-0.20,57.25,57.40,56.91,2949842\r\n\"HPQ\",46.13,\"6/11/2007\",\"2:46pm\",+0.43,45.80,46.29,45.46,7195349\r\n\"IBM\",103.49,\"6/11/2007\",\"2:46pm\",+0.42,102.87,104.00,102.50,3126604\r\n\"INTC\",22.02,\"6/11/2007\",\"2:51pm\",+0.19,21.70,22.08,21.69,29404310\r\n\"JNJ\",62.485,\"6/11/2007\",\"2:46pm\",+0.355,62.89,62.89,62.15,6102358\r\n\"JPM\",50.70,\"6/11/2007\",\"2:46pm\",+0.29,50.41,50.84,50.05,6395400\r\n\"KO\",51.73,\"6/11/2007\",\"2:46pm\",+0.06,51.67,51.85,51.32,7104652\r\n\"MCD\",51.32,\"6/11/2007\",\"2:46pm\",-0.09,51.47,51.62,50.98,3708627\r\n\"MMM\",85.50,\"6/11/2007\",\"2:46pm\",-0.44,85.94,85.98,85.28,1710200\r\n\"MO\",70.28,\"6/11/2007\",\"2:46pm\",-0.02,70.25,70.50,69.76,5450985\r\n\"MRK\",51.19,\"6/11/2007\",\"2:46pm\",+1.05,50.30,51.35,50.04,8188500\r\n\"MSFT\",30.16,\"6/11/2007\",\"2:51pm\",+0.11,30.05,30.25,29.93,31914788\r\n\"PFE\",26.435,\"6/11/2007\",\"2:46pm\",-0.085,26.50,26.54,26.31,18623136\r\n\"PG\",63.11,\"6/11/2007\",\"2:46pm\",+0.04,62.80,63.20,62.75,5034546\r\n\"T\",40.29,\"6/11/2007\",\"2:46pm\",+0.03,40.20,40.47,39.89,10311910\r\n\"UTX\",70.19,\"6/11/2007\",\"2:46pm\",-0.04,69.85,70.25,69.51,1662900\r\n\"VZ\",43.52,\"6/11/2007\",\"2:46pm\",+0.45,42.95,43.61,42.88,6914257\r\n\"WMT\",49.76,\"6/11/2007\",\"2:46pm\",-0.32,49.90,50.12,49.55,8356278\r\n\"XOM\",83.30,\"6/11/2007\",\"2:46pm\",+0.62,82.68,83.85,82.35,9689200\r\n\"AA\",39.33,\"6/11/2007\",\"2:51pm\",-0.33,39.67,40.18,39.30,3443580\r\n\"AIG\",71.85,\"6/11/2007\",\"2:51pm\",+0.32,71.29,72.03,71.15,3833496\r\n\"AXP\",63.26,\"6/11/2007\",\"2:51pm\",+0.22,62.79,63.42,62.42,2483430\r\n\"BA\",97.66,\"6/11/2007\",\"2:51pm\",-0.53,98.25,98.79,97.59,2085200\r\n\"C\",53.68,\"6/11/2007\",\"2:51pm\",+0.35,53.20,53.77,52.81,8317394\r\n\"CAT\",78.99,\"6/11/2007\",\"2:51pm\",+0.47,78.32,79.46,78.06,2746952\r\n\"DD\",50.80,\"6/11/2007\",\"2:51pm\",-0.33,51.13,51.21,50.59,2480397\r\n\"DIS\",34.18,\"6/11/2007\",\"2:51pm\",-0.02,34.28,34.44,34.12,4641650\r\n\"GE\",37.48,\"6/11/2007\",\"2:51pm\",+0.16,37.07,37.61,37.05,17252900\r\n\"GM\",31.62,\"6/11/2007\",\"2:51pm\",+0.62,31.00,31.79,30.90,11456251\r\n\"HD\",37.741,\"6/11/2007\",\"2:51pm\",-0.209,37.78,37.83,37.62,8530067\r\n\"HON\",57.17,\"6/11/2007\",\"2:51pm\",-0.21,57.25,57.40,56.91,2989342\r\n\"HPQ\",46.08,\"6/11/2007\",\"2:51pm\",+0.38,45.80,46.29,45.46,7295183\r\n\"IBM\",103.49,\"6/11/2007\",\"2:51pm\",+0.42,102.87,104.00,102.50,3205604\r\n\"INTC\",22.03,\"6/11/2007\",\"2:56pm\",+0.20,21.70,22.08,21.69,29768528\r\n\"JNJ\",62.45,\"6/11/2007\",\"2:51pm\",+0.32,62.89,62.89,62.15,6158558\r\n\"JPM\",50.71,\"6/11/2007\",\"2:51pm\",+0.30,50.41,50.84,50.05,6486400\r\n\"KO\",51.76,\"6/11/2007\",\"2:51pm\",+0.09,51.67,51.85,51.32,7135652\r\n\"MCD\",51.30,\"6/11/2007\",\"2:51pm\",-0.11,51.47,51.62,50.98,3729927\r\n\"MMM\",85.48,\"6/11/2007\",\"2:51pm\",-0.46,85.94,85.98,85.28,1732900\r\n\"MO\",70.21,\"6/11/2007\",\"2:51pm\",-0.09,70.25,70.50,69.76,5501285\r\n\"MRK\",51.09,\"6/11/2007\",\"2:51pm\",+0.95,50.30,51.35,50.04,8318900\r\n\"MSFT\",30.16,\"6/11/2007\",\"2:56pm\",+0.11,30.05,30.25,29.93,32355638\r\n\"PFE\",26.43,\"6/11/2007\",\"2:51pm\",-0.09,26.50,26.54,26.31,18879636\r\n\"PG\",63.16,\"6/11/2007\",\"2:51pm\",+0.09,62.80,63.20,62.75,5127246\r\n\"T\",40.21,\"6/11/2007\",\"2:51pm\",-0.05,40.20,40.47,39.89,10570410\r\n\"UTX\",70.15,\"6/11/2007\",\"2:51pm\",-0.08,69.85,70.25,69.51,1681200\r\n\"VZ\",43.49,\"6/11/2007\",\"2:51pm\",+0.42,42.95,43.61,42.88,6963457\r\n\"WMT\",49.755,\"6/11/2007\",\"2:51pm\",-0.325,49.90,50.12,49.55,8428278\r\n\"XOM\",83.24,\"6/11/2007\",\"2:51pm\",+0.56,82.68,83.85,82.35,9787800\r\n\"AA\",39.32,\"6/11/2007\",\"2:56pm\",-0.34,39.67,40.18,39.30,3480480\r\n\"AIG\",71.86,\"6/11/2007\",\"2:56pm\",+0.33,71.29,72.03,71.15,3935896\r\n\"AXP\",63.31,\"6/11/2007\",\"2:56pm\",+0.27,62.79,63.42,62.42,2509230\r\n\"BA\",97.78,\"6/11/2007\",\"2:56pm\",-0.41,98.25,98.79,97.59,2114200\r\n\"C\",53.70,\"6/11/2007\",\"2:56pm\",+0.37,53.20,53.77,52.81,8402494\r\n\"CAT\",79.08,\"6/11/2007\",\"2:56pm\",+0.56,78.32,79.46,78.06,2766652\r\n\"DD\",50.84,\"6/11/2007\",\"2:56pm\",-0.29,51.13,51.21,50.59,2498497\r\n\"DIS\",34.20,\"6/11/2007\",\"2:56pm\",0.00,34.28,34.44,34.12,4680250\r\n\"GE\",37.53,\"6/11/2007\",\"2:56pm\",+0.21,37.07,37.61,37.05,17452700\r\n\"GM\",31.682,\"6/11/2007\",\"2:56pm\",+0.682,31.00,31.79,30.90,11603351\r\n\"HD\",37.76,\"6/11/2007\",\"2:56pm\",-0.19,37.78,37.83,37.62,8595367\r\n\"HON\",57.23,\"6/11/2007\",\"2:56pm\",-0.15,57.25,57.40,56.91,3114842\r\n\"HPQ\",46.091,\"6/11/2007\",\"2:56pm\",+0.391,45.80,46.29,45.46,7387583\r\n\"IBM\",103.53,\"6/11/2007\",\"2:56pm\",+0.46,102.87,104.00,102.50,3228704\r\n\"INTC\",22.02,\"6/11/2007\",\"3:01pm\",+0.19,21.70,22.08,21.69,30262880\r\n\"JNJ\",62.53,\"6/11/2007\",\"2:56pm\",+0.40,62.89,62.89,62.15,6280508\r\n\"JPM\",50.71,\"6/11/2007\",\"2:56pm\",+0.30,50.41,50.84,50.05,6635200\r\n\"KO\",51.82,\"6/11/2007\",\"2:56pm\",+0.15,51.67,51.85,51.32,7174352\r\n\"MCD\",51.35,\"6/11/2007\",\"2:56pm\",-0.06,51.47,51.62,50.98,3766527\r\n\"MMM\",85.51,\"6/11/2007\",\"2:56pm\",-0.43,85.94,85.98,85.28,1757600\r\n\"MO\",70.30,\"6/11/2007\",\"2:56pm\",0.00,70.25,70.50,69.76,5597885\r\n\"MRK\",51.15,\"6/11/2007\",\"2:56pm\",+1.01,50.30,51.35,50.04,8391000\r\n\"MSFT\",30.16,\"6/11/2007\",\"3:01pm\",+0.11,30.05,30.25,29.93,32950134\r\n\"PFE\",26.43,\"6/11/2007\",\"2:56pm\",-0.09,26.50,26.54,26.31,19031536\r\n\"PG\",63.12,\"6/11/2007\",\"2:56pm\",+0.05,62.80,63.21,62.75,5284846\r\n\"T\",40.26,\"6/11/2007\",\"2:56pm\",0.00,40.20,40.47,39.89,10708610\r\n\"UTX\",70.21,\"6/11/2007\",\"2:56pm\",-0.02,69.85,70.27,69.51,1705800\r\n\"VZ\",43.53,\"6/11/2007\",\"2:56pm\",+0.46,42.95,43.61,42.88,7015557\r\n\"WMT\",49.85,\"6/11/2007\",\"2:56pm\",-0.23,49.90,50.12,49.55,8513478\r\n\"XOM\",83.315,\"6/11/2007\",\"2:56pm\",+0.635,82.68,83.85,82.35,9892200\r\n\"AA\",39.308,\"6/11/2007\",\"3:01pm\",-0.352,39.67,40.18,39.30,3525080\r\n\"AIG\",71.88,\"6/11/2007\",\"3:01pm\",+0.35,71.29,72.03,71.15,4037796\r\n\"AXP\",63.33,\"6/11/2007\",\"3:01pm\",+0.29,62.79,63.42,62.42,2525030\r\n\"BA\",97.69,\"6/11/2007\",\"3:01pm\",-0.50,98.25,98.79,97.59,2139700\r\n\"C\",53.6917,\"6/11/2007\",\"3:01pm\",+0.3617,53.20,53.77,52.81,8489294\r\n\"CAT\",79.06,\"6/11/2007\",\"3:01pm\",+0.54,78.32,79.46,78.06,2812752\r\n\"DD\",50.82,\"6/11/2007\",\"3:01pm\",-0.31,51.13,51.21,50.59,2531197\r\n\"DIS\",34.20,\"6/11/2007\",\"3:01pm\",0.00,34.28,34.44,34.12,4838550\r\n\"GE\",37.56,\"6/11/2007\",\"3:01pm\",+0.24,37.07,37.61,37.05,17708200\r\n\"GM\",31.71,\"6/11/2007\",\"3:01pm\",+0.71,31.00,31.79,30.90,11810951\r\n\"HD\",37.75,\"6/11/2007\",\"3:01pm\",-0.20,37.78,37.83,37.62,8889067\r\n\"HON\",57.16,\"6/11/2007\",\"3:01pm\",-0.22,57.25,57.40,56.91,3138242\r\n\"HPQ\",46.09,\"6/11/2007\",\"3:01pm\",+0.39,45.80,46.29,45.46,7434383\r\n\"IBM\",103.49,\"6/11/2007\",\"3:01pm\",+0.42,102.87,104.00,102.50,3267604\r\n\"INTC\",22.01,\"6/11/2007\",\"3:06pm\",+0.18,21.70,22.08,21.69,30788464\r\n\"JNJ\",62.53,\"6/11/2007\",\"3:01pm\",+0.40,62.89,62.89,62.15,6335808\r\n\"JPM\",50.68,\"6/11/2007\",\"3:01pm\",+0.27,50.41,50.84,50.05,6741800\r\n\"KO\",51.81,\"6/11/2007\",\"3:01pm\",+0.14,51.67,51.85,51.32,7223752\r\n\"MCD\",51.28,\"6/11/2007\",\"3:01pm\",-0.13,51.47,51.62,50.98,3825627\r\n\"MMM\",85.47,\"6/11/2007\",\"3:01pm\",-0.47,85.94,85.98,85.28,1780900\r\n\"MO\",70.26,\"6/11/2007\",\"3:01pm\",-0.04,70.25,70.50,69.76,5686085\r\n\"MRK\",51.14,\"6/11/2007\",\"3:01pm\",+1.00,50.30,51.35,50.04,8454600\r\n\"MSFT\",30.16,\"6/11/2007\",\"3:06pm\",+0.11,30.05,30.25,29.93,33439226\r\n\"PFE\",26.42,\"6/11/2007\",\"3:01pm\",-0.10,26.50,26.54,26.31,19178536\r\n\"PG\",63.09,\"6/11/2007\",\"3:01pm\",+0.02,62.80,63.21,62.75,5381146\r\n\"T\",40.26,\"6/11/2007\",\"3:01pm\",0.00,40.20,40.47,39.89,10923510\r\n\"UTX\",70.21,\"6/11/2007\",\"3:01pm\",-0.02,69.85,70.27,69.51,1741900\r\n\"VZ\",43.54,\"6/11/2007\",\"3:01pm\",+0.47,42.95,43.61,42.88,7071957\r\n\"WMT\",49.7901,\"6/11/2007\",\"3:01pm\",-0.2899,49.90,50.12,49.55,8630878\r\n\"XOM\",83.29,\"6/11/2007\",\"3:01pm\",+0.61,82.68,83.85,82.35,9962400\r\n\"AA\",39.33,\"6/11/2007\",\"3:06pm\",-0.33,39.67,40.18,39.25,3559780\r\n\"AIG\",71.87,\"6/11/2007\",\"3:06pm\",+0.34,71.29,72.03,71.15,4096696\r\n\"AXP\",63.30,\"6/11/2007\",\"3:06pm\",+0.26,62.79,63.42,62.42,2545430\r\n\"BA\",97.70,\"6/11/2007\",\"3:06pm\",-0.49,98.25,98.79,97.58,2170600\r\n\"C\",53.66,\"6/11/2007\",\"3:06pm\",+0.33,53.20,53.77,52.81,8668794\r\n\"CAT\",79.0221,\"6/11/2007\",\"3:06pm\",+0.5021,78.32,79.46,78.06,2871952\r\n\"DD\",50.84,\"6/11/2007\",\"3:06pm\",-0.29,51.13,51.21,50.59,2564597\r\n\"DIS\",34.20,\"6/11/2007\",\"3:06pm\",0.00,34.28,34.44,34.12,4903050\r\n\"GE\",37.54,\"6/11/2007\",\"3:06pm\",+0.22,37.07,37.61,37.05,17946600\r\n\"GM\",31.76,\"6/11/2007\",\"3:06pm\",+0.76,31.00,31.79,30.90,11923751\r\n\"HD\",37.76,\"6/11/2007\",\"3:06pm\",-0.19,37.78,37.83,37.62,9002967\r\n\"HON\",57.17,\"6/11/2007\",\"3:06pm\",-0.21,57.25,57.40,56.91,3177742\r\n\"HPQ\",46.10,\"6/11/2007\",\"3:06pm\",+0.40,45.80,46.29,45.46,7517183\r\n\"IBM\",103.49,\"6/11/2007\",\"3:06pm\",+0.42,102.87,104.00,102.50,3306904\r\n\"INTC\",22.03,\"6/11/2007\",\"3:11pm\",+0.20,21.70,22.08,21.69,31162120\r\n\"JNJ\",62.50,\"6/11/2007\",\"3:06pm\",+0.37,62.89,62.89,62.15,6420308\r\n\"JPM\",50.65,\"6/11/2007\",\"3:06pm\",+0.24,50.41,50.84,50.05,6851800\r\n\"KO\",51.80,\"6/11/2007\",\"3:06pm\",+0.13,51.67,51.85,51.32,7265152\r\n\"MCD\",51.29,\"6/11/2007\",\"3:06pm\",-0.12,51.47,51.62,50.98,3892827\r\n\"MMM\",85.55,\"6/11/2007\",\"3:06pm\",-0.39,85.94,85.98,85.28,1819600\r\n\"MO\",70.23,\"6/11/2007\",\"3:06pm\",-0.07,70.25,70.50,69.76,5764585\r\n\"MRK\",51.12,\"6/11/2007\",\"3:06pm\",+0.98,50.30,51.35,50.04,8577100\r\n\"MSFT\",30.16,\"6/11/2007\",\"3:11pm\",+0.11,30.05,30.25,29.93,34219296\r\n\"PFE\",26.41,\"6/11/2007\",\"3:06pm\",-0.11,26.50,26.54,26.31,19446136\r\n\"PG\",63.07,\"6/11/2007\",\"3:06pm\",0.00,62.80,63.21,62.75,5533446\r\n\"T\",40.23,\"6/11/2007\",\"3:06pm\",-0.03,40.20,40.47,39.89,11134010\r\n\"UTX\",70.22,\"6/11/2007\",\"3:06pm\",-0.01,69.85,70.27,69.51,1781800\r\n\"VZ\",43.54,\"6/11/2007\",\"3:06pm\",+0.47,42.95,43.61,42.88,7163857\r\n\"WMT\",49.795,\"6/11/2007\",\"3:06pm\",-0.285,49.90,50.12,49.55,8740778\r\n\"XOM\",83.31,\"6/11/2007\",\"3:06pm\",+0.63,82.68,83.85,82.35,10075100\r\n\"AA\",39.37,\"6/11/2007\",\"3:11pm\",-0.29,39.67,40.18,39.25,3601080\r\n\"AIG\",71.86,\"6/11/2007\",\"3:11pm\",+0.33,71.29,72.03,71.15,4171396\r\n\"AXP\",63.28,\"6/11/2007\",\"3:11pm\",+0.24,62.79,63.42,62.42,2572030\r\n\"BA\",97.70,\"6/11/2007\",\"3:11pm\",-0.49,98.25,98.79,97.58,2202300\r\n\"C\",53.60,\"6/11/2007\",\"3:11pm\",+0.27,53.20,53.77,52.81,8910394\r\n\"CAT\",79.10,\"6/11/2007\",\"3:11pm\",+0.58,78.32,79.46,78.06,2926952\r\n\"DD\",50.84,\"6/11/2007\",\"3:11pm\",-0.29,51.13,51.21,50.59,2592497\r\n\"DIS\",34.195,\"6/11/2007\",\"3:11pm\",-0.005,34.28,34.44,34.12,5006550\r\n\"GE\",37.56,\"6/11/2007\",\"3:11pm\",+0.24,37.07,37.61,37.05,18148100\r\n\"GM\",31.81,\"6/11/2007\",\"3:11pm\",+0.81,31.00,31.82,30.90,12211151\r\n\"HD\",37.74,\"6/11/2007\",\"3:11pm\",-0.21,37.78,37.83,37.62,9075667\r\n\"HON\",57.24,\"6/11/2007\",\"3:11pm\",-0.14,57.25,57.40,56.91,3219242\r\n\"HPQ\",46.12,\"6/11/2007\",\"3:11pm\",+0.42,45.80,46.29,45.46,7596783\r\n\"IBM\",103.52,\"6/11/2007\",\"3:11pm\",+0.45,102.87,104.00,102.50,3347004\r\n\"INTC\",22.03,\"6/11/2007\",\"3:16pm\",+0.20,21.70,22.08,21.69,31499736\r\n\"JNJ\",62.48,\"6/11/2007\",\"3:11pm\",+0.35,62.89,62.89,62.15,6626708\r\n\"JPM\",50.64,\"6/11/2007\",\"3:11pm\",+0.23,50.41,50.84,50.05,6957100\r\n\"KO\",51.80,\"6/11/2007\",\"3:11pm\",+0.13,51.67,51.85,51.32,7305952\r\n\"MCD\",51.28,\"6/11/2007\",\"3:11pm\",-0.13,51.47,51.62,50.98,3951227\r\n\"MMM\",85.52,\"6/11/2007\",\"3:11pm\",-0.42,85.94,85.98,85.28,1844000\r\n\"MO\",70.26,\"6/11/2007\",\"3:11pm\",-0.04,70.25,70.50,69.76,5802685\r\n\"MRK\",51.19,\"6/11/2007\",\"3:11pm\",+1.05,50.30,51.35,50.04,8710500\r\n\"MSFT\",30.14,\"6/11/2007\",\"3:16pm\",+0.09,30.05,30.25,29.93,35084560\r\n\"PFE\",26.42,\"6/11/2007\",\"3:11pm\",-0.10,26.50,26.54,26.31,19904950\r\n\"PG\",63.08,\"6/11/2007\",\"3:11pm\",+0.01,62.80,63.21,62.75,5640146\r\n\"T\",40.25,\"6/11/2007\",\"3:11pm\",-0.01,40.20,40.47,39.89,11272135\r\n\"UTX\",70.21,\"6/11/2007\",\"3:11pm\",-0.02,69.85,70.27,69.51,1802900\r\n\"VZ\",43.56,\"6/11/2007\",\"3:11pm\",+0.49,42.95,43.61,42.88,7294957\r\n\"WMT\",49.83,\"6/11/2007\",\"3:11pm\",-0.25,49.90,50.12,49.55,8885778\r\n\"XOM\",83.3328,\"6/11/2007\",\"3:11pm\",+0.6528,82.68,83.85,82.35,10226000\r\n\"AA\",39.36,\"6/11/2007\",\"3:16pm\",-0.30,39.67,40.18,39.25,3628080\r\n\"AIG\",71.86,\"6/11/2007\",\"3:16pm\",+0.33,71.29,72.03,71.15,4253196\r\n\"AXP\",63.27,\"6/11/2007\",\"3:16pm\",+0.23,62.79,63.42,62.42,2599530\r\n\"BA\",97.62,\"6/11/2007\",\"3:16pm\",-0.57,98.25,98.79,97.58,2240000\r\n\"C\",53.63,\"6/11/2007\",\"3:16pm\",+0.30,53.20,53.77,52.81,9064594\r\n\"CAT\",79.1263,\"6/11/2007\",\"3:16pm\",+0.6063,78.32,79.46,78.06,2961952\r\n\"DD\",50.84,\"6/11/2007\",\"3:16pm\",-0.29,51.13,51.21,50.59,2614197\r\n\"DIS\",34.19,\"6/11/2007\",\"3:16pm\",-0.01,34.28,34.44,34.12,5055750\r\n\"GE\",37.58,\"6/11/2007\",\"3:16pm\",+0.26,37.07,37.61,37.05,18410300\r\n\"GM\",31.79,\"6/11/2007\",\"3:16pm\",+0.79,31.00,31.82,30.90,12429251\r\n\"HD\",37.73,\"6/11/2007\",\"3:16pm\",-0.22,37.78,37.83,37.62,9252867\r\n\"HON\",57.22,\"6/11/2007\",\"3:16pm\",-0.16,57.25,57.40,56.91,3267342\r\n\"HPQ\",46.13,\"6/11/2007\",\"3:16pm\",+0.43,45.80,46.29,45.46,7732283\r\n\"IBM\",103.52,\"6/11/2007\",\"3:16pm\",+0.45,102.87,104.00,102.50,3390804\r\n\"INTC\",22.03,\"6/11/2007\",\"3:21pm\",+0.20,21.70,22.08,21.69,31747072\r\n\"JNJ\",62.46,\"6/11/2007\",\"3:16pm\",+0.33,62.89,62.89,62.15,6718808\r\n\"JPM\",50.66,\"6/11/2007\",\"3:16pm\",+0.25,50.41,50.84,50.05,7077000\r\n\"KO\",51.81,\"6/11/2007\",\"3:16pm\",+0.14,51.67,51.85,51.32,7356152\r\n\"MCD\",51.28,\"6/11/2007\",\"3:16pm\",-0.13,51.47,51.62,50.98,4004827\r\n\"MMM\",85.50,\"6/11/2007\",\"3:16pm\",-0.44,85.94,85.98,85.28,1863700\r\n\"MO\",70.24,\"6/11/2007\",\"3:16pm\",-0.06,70.25,70.50,69.76,5895585\r\n\"MRK\",51.17,\"6/11/2007\",\"3:16pm\",+1.03,50.30,51.35,50.04,8885200\r\n\"MSFT\",30.18,\"6/11/2007\",\"3:21pm\",+0.13,30.05,30.25,29.93,35482676\r\n\"PFE\",26.41,\"6/11/2007\",\"3:16pm\",-0.11,26.50,26.54,26.31,20145150\r\n\"PG\",63.07,\"6/11/2007\",\"3:16pm\",0.00,62.80,63.21,62.75,5824046\r\n\"T\",40.24,\"6/11/2007\",\"3:16pm\",-0.02,40.20,40.47,39.89,11393435\r\n\"UTX\",70.20,\"6/11/2007\",\"3:16pm\",-0.03,69.85,70.27,69.51,1819800\r\n\"VZ\",43.56,\"6/11/2007\",\"3:16pm\",+0.49,42.95,43.61,42.88,7465357\r\n\"WMT\",49.79,\"6/11/2007\",\"3:16pm\",-0.29,49.90,50.12,49.55,9009578\r\n\"XOM\",83.29,\"6/11/2007\",\"3:16pm\",+0.61,82.68,83.85,82.35,10323500\r\n\"AA\",39.41,\"6/11/2007\",\"3:21pm\",-0.25,39.67,40.18,39.25,3683980\r\n\"AIG\",71.87,\"6/11/2007\",\"3:21pm\",+0.34,71.29,72.03,71.15,4342596\r\n\"AXP\",63.28,\"6/11/2007\",\"3:21pm\",+0.24,62.79,63.42,62.42,2631030\r\n\"BA\",97.79,\"6/11/2007\",\"3:21pm\",-0.40,98.25,98.79,97.58,2283500\r\n\"C\",53.67,\"6/11/2007\",\"3:21pm\",+0.34,53.20,53.77,52.81,9345794\r\n\"CAT\",79.18,\"6/11/2007\",\"3:21pm\",+0.66,78.32,79.46,78.06,3018652\r\n\"DD\",50.85,\"6/11/2007\",\"3:21pm\",-0.28,51.13,51.21,50.59,2681597\r\n\"DIS\",34.189,\"6/11/2007\",\"3:21pm\",-0.011,34.28,34.44,34.12,5129050\r\n\"GE\",37.59,\"6/11/2007\",\"3:21pm\",+0.27,37.07,37.61,37.05,18759500\r\n\"GM\",31.83,\"6/11/2007\",\"3:21pm\",+0.83,31.00,31.84,30.90,12621451\r\n\"HD\",37.74,\"6/11/2007\",\"3:21pm\",-0.21,37.78,37.83,37.62,9337467\r\n\"HON\",57.25,\"6/11/2007\",\"3:21pm\",-0.13,57.25,57.40,56.91,3305486\r\n\"HPQ\",46.15,\"6/11/2007\",\"3:21pm\",+0.45,45.80,46.29,45.46,7875783\r\n\"IBM\",103.58,\"6/11/2007\",\"3:21pm\",+0.51,102.87,104.00,102.50,3430404\r\n\"INTC\",22.03,\"6/11/2007\",\"3:26pm\",+0.20,21.70,22.08,21.69,32604206\r\n\"JNJ\",62.44,\"6/11/2007\",\"3:21pm\",+0.31,62.89,62.89,62.15,6819808\r\n\"JPM\",50.68,\"6/11/2007\",\"3:21pm\",+0.27,50.41,50.84,50.05,7182200\r\n\"KO\",51.80,\"6/11/2007\",\"3:21pm\",+0.13,51.67,51.85,51.32,7430852\r\n\"MCD\",51.288,\"6/11/2007\",\"3:21pm\",-0.122,51.47,51.62,50.98,4044427\r\n\"MMM\",85.54,\"6/11/2007\",\"3:21pm\",-0.40,85.94,85.98,85.28,1903500\r\n\"MO\",70.28,\"6/11/2007\",\"3:21pm\",-0.02,70.25,70.50,69.76,5949185\r\n\"MRK\",51.13,\"6/11/2007\",\"3:21pm\",+0.99,50.30,51.35,50.04,9078900\r\n\"MSFT\",30.15,\"6/11/2007\",\"3:26pm\",+0.10,30.05,30.25,29.93,36214532\r\n\"PFE\",26.44,\"6/11/2007\",\"3:21pm\",-0.08,26.50,26.54,26.31,20502150\r\n\"PG\",63.10,\"6/11/2007\",\"3:21pm\",+0.03,62.80,63.21,62.75,5930246\r\n\"T\",40.26,\"6/11/2007\",\"3:21pm\",0.00,40.20,40.47,39.89,11546235\r\n\"UTX\",70.28,\"6/11/2007\",\"3:21pm\",+0.05,69.85,70.27,69.51,1852300\r\n\"VZ\",43.58,\"6/11/2007\",\"3:21pm\",+0.51,42.95,43.61,42.88,7563557\r\n\"WMT\",49.82,\"6/11/2007\",\"3:21pm\",-0.26,49.90,50.12,49.55,9134278\r\n\"XOM\",83.37,\"6/11/2007\",\"3:21pm\",+0.69,82.68,83.85,82.35,10394600\r\n\"AA\",39.36,\"6/11/2007\",\"3:26pm\",-0.30,39.67,40.18,39.25,3734180\r\n\"AIG\",71.85,\"6/11/2007\",\"3:26pm\",+0.32,71.29,72.03,71.15,4412096\r\n\"AXP\",63.26,\"6/11/2007\",\"3:26pm\",+0.22,62.79,63.42,62.42,2656530\r\n\"BA\",97.75,\"6/11/2007\",\"3:26pm\",-0.44,98.25,98.79,97.58,2318700\r\n\"C\",53.65,\"6/11/2007\",\"3:26pm\",+0.32,53.20,53.77,52.81,9469794\r\n\"CAT\",79.14,\"6/11/2007\",\"3:26pm\",+0.62,78.32,79.46,78.06,3063152\r\n\"DD\",50.85,\"6/11/2007\",\"3:26pm\",-0.28,51.13,51.21,50.59,2734497\r\n\"DIS\",34.20,\"6/11/2007\",\"3:26pm\",0.00,34.28,34.44,34.12,5218750\r\n\"GE\",37.61,\"6/11/2007\",\"3:26pm\",+0.29,37.07,37.61,37.05,19343000\r\n\"GM\",31.88,\"6/11/2007\",\"3:26pm\",+0.88,31.00,31.90,30.90,12954222\r\n\"HD\",37.7382,\"6/11/2007\",\"3:26pm\",-0.2118,37.78,37.83,37.62,9712367\r\n\"HON\",57.23,\"6/11/2007\",\"3:26pm\",-0.15,57.25,57.40,56.91,3354186\r\n\"HPQ\",46.15,\"6/11/2007\",\"3:26pm\",+0.45,45.80,46.29,45.46,7964983\r\n\"IBM\",103.48,\"6/11/2007\",\"3:26pm\",+0.41,102.87,104.00,102.50,3487304\r\n\"INTC\",22.05,\"6/11/2007\",\"3:31pm\",+0.22,21.70,22.08,21.69,33024848\r\n\"JNJ\",62.38,\"6/11/2007\",\"3:26pm\",+0.25,62.89,62.89,62.15,6981631\r\n\"JPM\",50.65,\"6/11/2007\",\"3:26pm\",+0.24,50.41,50.84,50.05,7366500\r\n\"KO\",51.78,\"6/11/2007\",\"3:26pm\",+0.11,51.67,51.85,51.32,7479252\r\n\"MCD\",51.21,\"6/11/2007\",\"3:26pm\",-0.20,51.47,51.62,50.98,4117427\r\n\"MMM\",85.55,\"6/11/2007\",\"3:26pm\",-0.39,85.94,85.98,85.28,1925800\r\n\"MO\",70.2525,\"6/11/2007\",\"3:26pm\",-0.0475,70.25,70.50,69.76,6009485\r\n\"MRK\",51.09,\"6/11/2007\",\"3:26pm\",+0.95,50.30,51.35,50.04,9266600\r\n\"MSFT\",30.165,\"6/11/2007\",\"3:31pm\",+0.115,30.05,30.25,29.93,36431612\r\n\"PFE\",26.40,\"6/11/2007\",\"3:26pm\",-0.12,26.50,26.54,26.31,20767150\r\n\"PG\",63.08,\"6/11/2007\",\"3:26pm\",+0.01,62.80,63.21,62.75,5987546\r\n\"T\",40.25,\"6/11/2007\",\"3:26pm\",-0.01,40.20,40.47,39.88,13610435\r\n\"UTX\",70.25,\"6/11/2007\",\"3:26pm\",+0.02,69.85,70.30,69.51,1883100\r\n\"VZ\",43.58,\"6/11/2007\",\"3:26pm\",+0.51,42.95,43.61,42.88,7711757\r\n\"WMT\",49.80,\"6/11/2007\",\"3:26pm\",-0.28,49.90,50.12,49.55,9419178\r\n\"XOM\",83.2854,\"6/11/2007\",\"3:26pm\",+0.6054,82.68,83.85,82.35,10509700\r\n\"AA\",39.37,\"6/11/2007\",\"3:31pm\",-0.29,39.67,40.18,39.25,3767480\r\n\"AIG\",71.84,\"6/11/2007\",\"3:31pm\",+0.31,71.29,72.03,71.15,4469996\r\n\"AXP\",63.27,\"6/11/2007\",\"3:31pm\",+0.23,62.79,63.42,62.42,2681730\r\n\"BA\",97.66,\"6/11/2007\",\"3:31pm\",-0.53,98.25,98.79,97.58,2347600\r\n\"C\",53.63,\"6/11/2007\",\"3:31pm\",+0.30,53.20,53.77,52.81,9570094\r\n\"CAT\",79.15,\"6/11/2007\",\"3:31pm\",+0.63,78.32,79.46,78.06,3108752\r\n\"DD\",50.83,\"6/11/2007\",\"3:31pm\",-0.30,51.13,51.21,50.59,2758297\r\n\"DIS\",34.20,\"6/11/2007\",\"3:31pm\",0.00,34.28,34.44,34.12,5359050\r\n\"GE\",37.61,\"6/11/2007\",\"3:31pm\",+0.29,37.07,37.61,37.05,19495200\r\n\"GM\",31.83,\"6/11/2007\",\"3:31pm\",+0.83,31.00,31.90,30.90,13338922\r\n\"HD\",37.7275,\"6/11/2007\",\"3:31pm\",-0.2225,37.78,37.83,37.62,9833219\r\n\"HON\",57.23,\"6/11/2007\",\"3:31pm\",-0.15,57.25,57.40,56.91,3411886\r\n\"HPQ\",46.16,\"6/11/2007\",\"3:31pm\",+0.46,45.80,46.29,45.46,8108883\r\n\"IBM\",103.48,\"6/11/2007\",\"3:31pm\",+0.41,102.87,104.00,102.50,3582104\r\n\"INTC\",22.02,\"6/11/2007\",\"3:36pm\",+0.19,21.70,22.08,21.69,33811596\r\n\"JNJ\",62.38,\"6/11/2007\",\"3:31pm\",+0.25,62.89,62.89,62.15,7072531\r\n\"JPM\",50.6225,\"6/11/2007\",\"3:31pm\",+0.2125,50.41,50.84,50.05,7423100\r\n\"KO\",51.78,\"6/11/2007\",\"3:31pm\",+0.11,51.67,51.85,51.32,7527052\r\n\"MCD\",51.17,\"6/11/2007\",\"3:31pm\",-0.24,51.47,51.62,50.98,4207427\r\n\"MMM\",85.50,\"6/11/2007\",\"3:31pm\",-0.44,85.94,85.98,85.28,1954000\r\n\"MO\",70.26,\"6/11/2007\",\"3:31pm\",-0.04,70.25,70.50,69.76,6068285\r\n\"MRK\",51.15,\"6/11/2007\",\"3:31pm\",+1.01,50.30,51.35,50.04,9411500\r\n\"MSFT\",30.14,\"6/11/2007\",\"3:36pm\",+0.09,30.05,30.25,29.93,37331996\r\n\"PFE\",26.40,\"6/11/2007\",\"3:31pm\",-0.12,26.50,26.54,26.31,21355550\r\n\"PG\",63.11,\"6/11/2007\",\"3:31pm\",+0.04,62.80,63.21,62.75,6055546\r\n\"T\",40.24,\"6/11/2007\",\"3:31pm\",-0.02,40.20,40.47,39.88,13730535\r\n\"UTX\",70.26,\"6/11/2007\",\"3:31pm\",+0.03,69.85,70.30,69.51,1985400\r\n\"VZ\",43.58,\"6/11/2007\",\"3:31pm\",+0.51,42.95,43.61,42.88,7833157\r\n\"WMT\",49.80,\"6/11/2007\",\"3:31pm\",-0.28,49.90,50.12,49.55,9490578\r\n\"XOM\",83.275,\"6/11/2007\",\"3:31pm\",+0.595,82.68,83.85,82.35,10612200\r\n\"AA\",39.30,\"6/11/2007\",\"3:36pm\",-0.36,39.67,40.18,39.25,3811480\r\n\"AIG\",71.77,\"6/11/2007\",\"3:36pm\",+0.24,71.29,72.03,71.15,4581496\r\n\"AXP\",63.155,\"6/11/2007\",\"3:36pm\",+0.115,62.79,63.42,62.42,2721130\r\n\"BA\",97.56,\"6/11/2007\",\"3:36pm\",-0.63,98.25,98.79,97.55,2383000\r\n\"C\",53.54,\"6/11/2007\",\"3:36pm\",+0.21,53.20,53.77,52.81,11390094\r\n\"CAT\",79.04,\"6/11/2007\",\"3:36pm\",+0.52,78.32,79.46,78.06,3145152\r\n\"DD\",50.76,\"6/11/2007\",\"3:36pm\",-0.37,51.13,51.21,50.59,2813297\r\n\"DIS\",34.18,\"6/11/2007\",\"3:36pm\",-0.02,34.28,34.44,34.12,5431250\r\n\"GE\",37.58,\"6/11/2007\",\"3:36pm\",+0.26,37.07,37.61,37.05,19977600\r\n\"GM\",31.79,\"6/11/2007\",\"3:36pm\",+0.79,31.00,31.90,30.90,13573622\r\n\"HD\",37.72,\"6/11/2007\",\"3:36pm\",-0.23,37.78,37.83,37.62,10373519\r\n\"HON\",57.18,\"6/11/2007\",\"3:36pm\",-0.20,57.25,57.40,56.91,3490736\r\n\"HPQ\",46.10,\"6/11/2007\",\"3:36pm\",+0.40,45.80,46.29,45.46,8224783\r\n\"IBM\",103.36,\"6/11/2007\",\"3:36pm\",+0.29,102.87,104.00,102.50,3667004\r\n\"INTC\",22.02,\"6/11/2007\",\"3:41pm\",+0.19,21.70,22.08,21.69,34379072\r\n\"JNJ\",62.33,\"6/11/2007\",\"3:36pm\",+0.20,62.89,62.89,62.15,7231131\r\n\"JPM\",50.58,\"6/11/2007\",\"3:36pm\",+0.17,50.41,50.84,50.05,7580900\r\n\"KO\",51.73,\"6/11/2007\",\"3:36pm\",+0.06,51.67,51.85,51.32,7588352\r\n\"MCD\",51.17,\"6/11/2007\",\"3:36pm\",-0.24,51.47,51.62,50.98,4327427\r\n\"MMM\",85.42,\"6/11/2007\",\"3:36pm\",-0.52,85.94,85.98,85.28,1997700\r\n\"MO\",70.24,\"6/11/2007\",\"3:36pm\",-0.06,70.25,70.50,69.76,6182685\r\n\"MRK\",51.15,\"6/11/2007\",\"3:36pm\",+1.01,50.30,51.35,50.04,9552400\r\n\"MSFT\",30.11,\"6/11/2007\",\"3:41pm\",+0.06,30.05,30.25,29.93,37726620\r\n\"PFE\",26.41,\"6/11/2007\",\"3:36pm\",-0.11,26.50,26.54,26.31,21924450\r\n\"PG\",63.0627,\"6/11/2007\",\"3:36pm\",-0.0073,62.80,63.21,62.75,6121146\r\n\"T\",40.23,\"6/11/2007\",\"3:36pm\",-0.03,40.20,40.47,39.88,13995835\r\n\"UTX\",70.17,\"6/11/2007\",\"3:36pm\",-0.06,69.85,70.30,69.51,2031100\r\n\"VZ\",43.55,\"6/11/2007\",\"3:36pm\",+0.48,42.95,43.61,42.88,7990757\r\n\"WMT\",49.811,\"6/11/2007\",\"3:36pm\",-0.269,49.90,50.12,49.55,9655678\r\n\"XOM\",83.17,\"6/11/2007\",\"3:36pm\",+0.49,82.68,83.85,82.35,10797200\r\n\"AA\",39.28,\"6/11/2007\",\"3:41pm\",-0.38,39.67,40.18,39.25,3906080\r\n\"AIG\",71.73,\"6/11/2007\",\"3:41pm\",+0.20,71.29,72.03,71.15,4691496\r\n\"AXP\",63.15,\"6/11/2007\",\"3:41pm\",+0.11,62.79,63.42,62.42,2766030\r\n\"BA\",97.50,\"6/11/2007\",\"3:41pm\",-0.69,98.25,98.79,97.48,2453500\r\n\"C\",53.51,\"6/11/2007\",\"3:41pm\",+0.18,53.20,53.77,52.81,11535394\r\n\"CAT\",78.98,\"6/11/2007\",\"3:41pm\",+0.46,78.32,79.46,78.06,3172952\r\n\"DD\",50.73,\"6/11/2007\",\"3:41pm\",-0.40,51.13,51.21,50.59,2869897\r\n\"DIS\",34.18,\"6/11/2007\",\"3:41pm\",-0.02,34.28,34.44,34.12,5648050\r\n\"GE\",37.58,\"6/11/2007\",\"3:41pm\",+0.26,37.07,37.61,37.05,20658300\r\n\"GM\",31.73,\"6/11/2007\",\"3:41pm\",+0.73,31.00,31.90,30.90,13983822\r\n\"HD\",37.72,\"6/11/2007\",\"3:41pm\",-0.23,37.78,37.83,37.62,10596819\r\n\"HON\",57.16,\"6/11/2007\",\"3:41pm\",-0.22,57.25,57.40,56.91,3548036\r\n\"HPQ\",46.072,\"6/11/2007\",\"3:41pm\",+0.372,45.80,46.29,45.46,8398683\r\n\"IBM\",103.38,\"6/11/2007\",\"3:41pm\",+0.31,102.87,104.00,102.50,3741504\r\n\"INTC\",21.992,\"6/11/2007\",\"3:46pm\",+0.162,21.70,22.08,21.69,35566072\r\n\"JNJ\",62.29,\"6/11/2007\",\"3:41pm\",+0.16,62.89,62.89,62.15,7444481\r\n\"JPM\",50.59,\"6/11/2007\",\"3:41pm\",+0.18,50.41,50.84,50.05,7794800\r\n\"KO\",51.74,\"6/11/2007\",\"3:41pm\",+0.07,51.67,51.85,51.32,7758670\r\n\"MCD\",51.25,\"6/11/2007\",\"3:41pm\",-0.16,51.47,51.62,50.98,4427195\r\n\"MMM\",85.50,\"6/11/2007\",\"3:41pm\",-0.44,85.94,85.98,85.28,2066900\r\n\"MO\",70.25,\"6/11/2007\",\"3:41pm\",-0.05,70.25,70.50,69.76,6272385\r\n\"MRK\",51.14,\"6/11/2007\",\"3:41pm\",+1.00,50.30,51.35,50.04,9785300\r\n\"MSFT\",30.08,\"6/11/2007\",\"3:46pm\",+0.03,30.05,30.25,29.93,38740488\r\n\"PFE\",26.43,\"6/11/2007\",\"3:41pm\",-0.09,26.50,26.54,26.31,22573150\r\n\"PG\",63.06,\"6/11/2007\",\"3:41pm\",-0.01,62.80,63.21,62.75,6204746\r\n\"T\",40.25,\"6/11/2007\",\"3:41pm\",-0.01,40.20,40.47,39.88,14297535\r\n\"UTX\",70.12,\"6/11/2007\",\"3:41pm\",-0.11,69.85,70.30,69.51,2068500\r\n\"VZ\",43.56,\"6/11/2007\",\"3:41pm\",+0.49,42.95,43.61,42.88,8283057\r\n\"WMT\",49.85,\"6/11/2007\",\"3:41pm\",-0.23,49.90,50.12,49.55,9905878\r\n\"XOM\",83.1801,\"6/11/2007\",\"3:41pm\",+0.5001,82.68,83.85,82.35,10980200\r\n\"AA\",39.28,\"6/11/2007\",\"3:46pm\",-0.38,39.67,40.18,39.21,4013480\r\n\"AIG\",71.74,\"6/11/2007\",\"3:46pm\",+0.21,71.29,72.03,71.15,4893496\r\n\"AXP\",63.13,\"6/11/2007\",\"3:46pm\",+0.09,62.79,63.42,62.42,2811230\r\n\"BA\",97.50,\"6/11/2007\",\"3:46pm\",-0.69,98.25,98.79,97.44,2545100\r\n\"C\",53.47,\"6/11/2007\",\"3:46pm\",+0.14,53.20,53.77,52.81,11812294\r\n\"CAT\",78.98,\"6/11/2007\",\"3:46pm\",+0.46,78.32,79.46,78.06,3228752\r\n\"DD\",50.78,\"6/11/2007\",\"3:46pm\",-0.35,51.13,51.21,50.59,2971197\r\n\"DIS\",34.17,\"6/11/2007\",\"3:46pm\",-0.03,34.28,34.44,34.12,5764350\r\n\"GE\",37.53,\"6/11/2007\",\"3:46pm\",+0.21,37.07,37.6202,37.05,21261200\r\n\"GM\",31.74,\"6/11/2007\",\"3:46pm\",+0.74,31.00,31.90,30.90,14221022\r\n\"HD\",37.69,\"6/11/2007\",\"3:46pm\",-0.26,37.78,37.83,37.62,10829419\r\n\"HON\",57.13,\"6/11/2007\",\"3:46pm\",-0.25,57.25,57.40,56.91,3612436\r\n\"HPQ\",46.06,\"6/11/2007\",\"3:46pm\",+0.36,45.80,46.29,45.46,8528683\r\n\"IBM\",103.37,\"6/11/2007\",\"3:46pm\",+0.30,102.87,104.00,102.50,3846604\r\n\"INTC\",21.97,\"6/11/2007\",\"3:51pm\",+0.14,21.70,22.08,21.69,37733764\r\n\"JNJ\",62.28,\"6/11/2007\",\"3:46pm\",+0.15,62.89,62.89,62.15,7592281\r\n\"JPM\",50.57,\"6/11/2007\",\"3:46pm\",+0.16,50.41,50.84,50.05,7987300\r\n\"KO\",51.71,\"6/11/2007\",\"3:46pm\",+0.04,51.67,51.85,51.32,7854570\r\n\"MCD\",51.31,\"6/11/2007\",\"3:46pm\",-0.10,51.47,51.62,50.98,4569195\r\n\"MMM\",85.41,\"6/11/2007\",\"3:46pm\",-0.53,85.94,85.98,85.28,2120500\r\n\"MO\",70.24,\"6/11/2007\",\"3:46pm\",-0.06,70.25,70.50,69.76,6364785\r\n\"MRK\",51.11,\"6/11/2007\",\"3:46pm\",+0.97,50.30,51.35,50.04,9941300\r\n\"MSFT\",30.04,\"6/11/2007\",\"3:51pm\",-0.01,30.05,30.25,29.93,41124800\r\n\"PFE\",26.41,\"6/11/2007\",\"3:46pm\",-0.11,26.50,26.54,26.31,23036750\r\n\"PG\",63.05,\"6/11/2007\",\"3:46pm\",-0.02,62.80,63.21,62.75,6374346\r\n\"T\",40.21,\"6/11/2007\",\"3:46pm\",-0.05,40.20,40.47,39.88,15620735\r\n\"UTX\",70.19,\"6/11/2007\",\"3:46pm\",-0.04,69.85,70.30,69.51,2171000\r\n\"VZ\",43.569,\"6/11/2007\",\"3:46pm\",+0.499,42.95,43.61,42.88,8442057\r\n\"WMT\",49.85,\"6/11/2007\",\"3:46pm\",-0.23,49.90,50.12,49.55,10081678\r\n\"XOM\",83.14,\"6/11/2007\",\"3:46pm\",+0.46,82.68,83.85,82.35,11246800\r\n\"AA\",39.29,\"6/11/2007\",\"3:51pm\",-0.37,39.67,40.18,39.21,4132380\r\n\"AIG\",71.62,\"6/11/2007\",\"3:51pm\",+0.09,71.29,72.03,71.15,5038729\r\n\"AXP\",63.09,\"6/11/2007\",\"3:51pm\",+0.05,62.79,63.42,62.42,2861530\r\n\"BA\",97.49,\"6/11/2007\",\"3:51pm\",-0.70,98.25,98.79,97.43,3063600\r\n\"C\",53.42,\"6/11/2007\",\"3:51pm\",+0.09,53.20,53.77,52.81,12267394\r\n\"CAT\",78.78,\"6/11/2007\",\"3:51pm\",+0.26,78.32,79.46,78.06,3287352\r\n\"DD\",50.73,\"6/11/2007\",\"3:51pm\",-0.40,51.13,51.21,50.59,3033697\r\n\"DIS\",34.135,\"6/11/2007\",\"3:51pm\",-0.065,34.28,34.44,34.12,5919350\r\n\"GE\",37.49,\"6/11/2007\",\"3:51pm\",+0.17,37.07,37.6202,37.05,21532000\r\n\"GM\",31.72,\"6/11/2007\",\"3:51pm\",+0.72,31.00,31.90,30.90,14377693\r\n\"HD\",37.67,\"6/11/2007\",\"3:51pm\",-0.28,37.78,37.83,37.62,11229419\r\n\"HON\",57.04,\"6/11/2007\",\"3:51pm\",-0.34,57.25,57.40,56.91,3682536\r\n\"HPQ\",46.00,\"6/11/2007\",\"3:51pm\",+0.30,45.80,46.29,45.46,8652383\r\n\"IBM\",103.20,\"6/11/2007\",\"3:51pm\",+0.13,102.87,104.00,102.50,3934104\r\n\"INTC\",21.97,\"6/11/2007\",\"3:56pm\",+0.14,21.70,22.08,21.69,38481016\r\n\"JNJ\",62.22,\"6/11/2007\",\"3:51pm\",+0.09,62.89,62.89,62.15,7738581\r\n\"JPM\",50.48,\"6/11/2007\",\"3:51pm\",+0.07,50.41,50.84,50.05,8466180\r\n\"KO\",51.65,\"6/11/2007\",\"3:51pm\",-0.02,51.67,51.85,51.32,7921870\r\n\"MCD\",51.26,\"6/11/2007\",\"3:51pm\",-0.15,51.47,51.62,50.98,5379392\r\n\"MMM\",85.26,\"6/11/2007\",\"3:51pm\",-0.68,85.94,85.98,85.28,2179200\r\n\"MO\",70.22,\"6/11/2007\",\"3:51pm\",-0.08,70.25,70.50,69.76,6468485\r\n\"MRK\",51.09,\"6/11/2007\",\"3:51pm\",+0.95,50.30,51.35,50.04,10126700\r\n\"MSFT\",30.04,\"6/11/2007\",\"3:56pm\",-0.01,30.05,30.25,29.93,45401260\r\n\"PFE\",26.36,\"6/11/2007\",\"3:51pm\",-0.16,26.50,26.54,26.31,23619450\r\n\"PG\",63.01,\"6/11/2007\",\"3:51pm\",-0.06,62.80,63.21,62.75,6447846\r\n\"T\",40.14,\"6/11/2007\",\"3:51pm\",-0.12,40.20,40.47,39.88,15842235\r\n\"UTX\",70.19,\"6/11/2007\",\"3:51pm\",-0.04,69.85,70.30,69.51,2300500\r\n\"VZ\",43.51,\"6/11/2007\",\"3:51pm\",+0.44,42.95,43.61,42.88,8663757\r\n\"WMT\",49.81,\"6/11/2007\",\"3:51pm\",-0.27,49.90,50.12,49.55,10280178\r\n\"XOM\",82.99,\"6/11/2007\",\"3:51pm\",+0.31,82.68,83.85,82.35,11476800\r\n\"AA\",39.29,\"6/11/2007\",\"3:56pm\",-0.37,39.67,40.18,39.21,4279480\r\n\"AIG\",71.63,\"6/11/2007\",\"3:56pm\",+0.10,71.29,72.03,71.15,5259629\r\n\"AXP\",63.06,\"6/11/2007\",\"3:56pm\",+0.02,62.79,63.42,62.42,2932030\r\n\"BA\",97.48,\"6/11/2007\",\"3:56pm\",-0.71,98.25,98.79,97.43,3134100\r\n\"C\",53.46,\"6/11/2007\",\"3:56pm\",+0.13,53.20,53.77,52.81,12689394\r\n\"CAT\",78.76,\"6/11/2007\",\"3:56pm\",+0.24,78.32,79.46,78.06,3364652\r\n\"DD\",50.70,\"6/11/2007\",\"3:56pm\",-0.43,51.13,51.21,50.59,3120297\r\n\"DIS\",34.16,\"6/11/2007\",\"3:56pm\",-0.04,34.28,34.44,34.12,6061350\r\n\"GE\",37.47,\"6/11/2007\",\"3:56pm\",+0.15,37.07,37.6202,37.05,22045900\r\n\"GM\",31.72,\"6/11/2007\",\"3:56pm\",+0.72,31.00,31.90,30.90,14702993\r\n\"HD\",37.67,\"6/11/2007\",\"3:56pm\",-0.28,37.78,37.83,37.62,11654819\r\n\"HON\",56.95,\"6/11/2007\",\"3:56pm\",-0.43,57.25,57.40,56.91,3802936\r\n\"HPQ\",45.93,\"6/11/2007\",\"3:56pm\",+0.23,45.80,46.29,45.46,11355083\r\n\"IBM\",103.01,\"6/11/2007\",\"3:56pm\",-0.06,102.87,104.00,102.50,4096804\r\n\"INTC\",21.93,\"6/11/2007\",\"4:01pm\",+0.10,21.70,22.08,21.69,40860336\r\n\"JNJ\",62.29,\"6/11/2007\",\"3:56pm\",+0.16,62.89,62.89,62.15,7933681\r\n\"JPM\",50.43,\"6/11/2007\",\"3:56pm\",+0.02,50.41,50.84,50.05,8654880\r\n\"KO\",51.66,\"6/11/2007\",\"3:56pm\",-0.01,51.67,51.85,51.32,7987870\r\n\"MCD\",51.30,\"6/11/2007\",\"3:56pm\",-0.11,51.47,51.62,50.98,5548192\r\n\"MMM\",85.18,\"6/11/2007\",\"3:56pm\",-0.76,85.94,85.98,85.17,2274500\r\n\"MO\",70.22,\"6/11/2007\",\"3:56pm\",-0.08,70.25,70.50,69.76,6635085\r\n\"MRK\",51.06,\"6/11/2007\",\"3:56pm\",+0.92,50.30,51.35,50.04,10461500\r\n\"MSFT\",30.02,\"6/11/2007\",\"4:00pm\",-0.03,30.05,30.25,29.93,46709236\r\n\"PFE\",26.37,\"6/11/2007\",\"3:56pm\",-0.15,26.50,26.54,26.31,24178850\r\n\"PG\",63.02,\"6/11/2007\",\"3:56pm\",-0.05,62.80,63.21,62.75,6576946\r\n\"T\",40.0918,\"6/11/2007\",\"3:56pm\",-0.1682,40.20,40.47,39.88,16125935\r\n\"UTX\",70.17,\"6/11/2007\",\"3:56pm\",-0.06,69.85,70.30,69.51,2406200\r\n\"VZ\",43.48,\"6/11/2007\",\"3:56pm\",+0.41,42.95,43.61,42.88,9009057\r\n\"WMT\",49.79,\"6/11/2007\",\"3:56pm\",-0.29,49.90,50.12,49.55,10441378\r\n\"XOM\",82.96,\"6/11/2007\",\"3:56pm\",+0.28,82.68,83.85,82.35,11744600\r\n\"AA\",39.30,\"6/11/2007\",\"4:01pm\",-0.36,39.67,40.18,39.14,4516480\r\n\"AIG\",71.65,\"6/11/2007\",\"4:00pm\",+0.12,71.29,72.03,71.15,5942029\r\n\"AXP\",63.06,\"6/11/2007\",\"4:00pm\",+0.02,62.79,63.42,62.42,3050830\r\n\"BA\",97.55,\"6/11/2007\",\"4:00pm\",-0.64,98.25,98.79,97.42,3300500\r\n\"C\",53.47,\"6/11/2007\",\"4:01pm\",+0.14,53.20,53.77,52.81,13457894\r\n\"CAT\",78.75,\"6/11/2007\",\"4:00pm\",+0.23,78.32,79.46,78.06,3456552\r\n\"DD\",50.73,\"6/11/2007\",\"3:59pm\",-0.40,51.13,51.21,50.59,3162997\r\n\"DIS\",34.15,\"6/11/2007\",\"3:59pm\",-0.05,34.28,34.44,34.12,6162650\r\n\"GE\",37.46,\"6/11/2007\",\"4:01pm\",+0.14,37.07,37.6202,37.05,23163100\r\n\"GM\",31.77,\"6/11/2007\",\"4:00pm\",+0.77,31.00,31.90,30.90,15223093\r\n\"HD\",37.71,\"6/11/2007\",\"4:00pm\",-0.24,37.78,37.83,37.62,12074119\r\n\"HON\",57.02,\"6/11/2007\",\"3:59pm\",-0.36,57.25,57.40,56.91,3911336\r\n\"HPQ\",45.89,\"6/11/2007\",\"4:00pm\",+0.19,45.80,46.29,45.46,11960883\r\n\"IBM\",103.22,\"6/11/2007\",\"4:01pm\",+0.15,102.87,104.00,102.50,4668204\r\n\"INTC\",21.93,\"6/11/2007\",\"4:01pm\",+0.10,21.70,22.08,21.69,41134284\r\n\"JNJ\",62.27,\"6/11/2007\",\"4:00pm\",+0.14,62.89,62.89,62.15,8452146\r\n\"JPM\",50.45,\"6/11/2007\",\"4:00pm\",+0.04,50.41,50.84,50.05,8869280\r\n\"KO\",51.65,\"6/11/2007\",\"3:59pm\",-0.02,51.67,51.85,51.32,8049770\r\n\"MCD\",51.25,\"6/11/2007\",\"4:01pm\",-0.16,51.47,51.62,50.98,5969292\r\n\"MMM\",85.30,\"6/11/2007\",\"4:01pm\",-0.64,85.94,85.98,85.17,2454400\r\n\"MO\",70.22,\"6/11/2007\",\"4:00pm\",-0.08,70.25,70.50,69.76,6887785\r\n\"MRK\",51.09,\"6/11/2007\",\"3:59pm\",+0.95,50.30,51.35,50.04,10623065\r\n\"MSFT\",30.02,\"6/11/2007\",\"4:00pm\",-0.03,30.05,30.25,29.93,46924636\r\n\"PFE\",26.37,\"6/11/2007\",\"4:00pm\",-0.15,26.50,26.54,26.31,25287450\r\n\"PG\",63.05,\"6/11/2007\",\"4:00pm\",-0.02,62.80,63.21,62.75,6920246\r\n\"T\",40.12,\"6/11/2007\",\"4:00pm\",-0.14,40.20,40.47,39.88,16712535\r\n\"UTX\",70.18,\"6/11/2007\",\"4:00pm\",-0.05,69.85,70.30,69.51,2660900\r\n\"VZ\",43.47,\"6/11/2007\",\"3:59pm\",+0.40,42.95,43.61,42.88,9156827\r\n\"WMT\",49.81,\"6/11/2007\",\"4:00pm\",-0.27,49.90,50.12,49.55,10924878\r\n\"XOM\",83.06,\"6/11/2007\",\"4:00pm\",+0.38,82.68,83.85,82.35,12427710"
  },
  {
    "path": "Data/missing.csv",
    "content": "name,shares,price\n\"AA\",15,39.48\n\"AXP\",10,62.58\n\"BA\",5,98.31\n\"C\",,53.08\n\"CAT\",15,78.29\n\"DD\",10,50.75\n\"DIS\",50,N/A\n\"GE\",,37.23\n\"GM\",15,31.44\n\"HD\",20,37.67\n\"HPQ\",5,45.81\n\"IBM\",10,102.86\n\"INTC\",,21.84\n\"JNJ\",20,62.25\n\"JPM\",10,50.35\n\"KO\",5,51.65\n\"MCD\",,51.11\n\"MMM\",10,85.60\n\"MO\",,70.09\n\"MRK\",5,50.21\n\"MSFT\",20,30.08\n\"PFE\",,26.40\n\"PG\",5,62.79\n\"T\",10,40.03\n\"UTX\",8,69.81\n\"VZ\",,42.92\n\"WMT\",10,49.78\n\"XOM\",15,82.50\n"
  },
  {
    "path": "Data/portfolio.csv",
    "content": "name,shares,price\n\"AA\",100,32.20\n\"IBM\",50,91.10\n\"CAT\",150,83.44\n\"MSFT\",200,51.23\n\"GE\",95,40.37\n\"MSFT\",50,65.10\n\"IBM\",100,70.44\n"
  },
  {
    "path": "Data/portfolio2.csv",
    "content": "name,shares,price\n\"AA\",50,27.10\n\"HPQ\",250,43.15\n\"MSFT\",25,50.15\n\"GE\",125,52.10\n"
  },
  {
    "path": "Data/portfolio3.csv",
    "content": "\"AA\",15,39.48\n\"AXP\",10,62.58\n\"BA\",5,98.31\n\"C\",-,53.08\n\"CAT\",15,78.29\n\"DD\",10,50.75\n\"DIS\",-,N/A\n\"GE\",-,37.23\n\"GM\",15,31.44\n\"HD\",20,37.67\n\"HPQ\",5,45.81\n\"IBM\",10,102.86\n\"INTC\",-,21.84\n\"JNJ\",20,62.25\n\"JPM\",10,50.35\n\"KO\",5,51.65\n\"MCD\",-,51.11\n\"MMM\",10,85.60\n\"MO\",-,70.09\n\"MRK\",5,50.21\n\"MSFT\",20,30.08\n\"PFE\",-,26.40\n\"PG\",5,62.79\n\"T\",10,40.03\n\"UTX\",8,69.81\n\"VZ\",-,42.92\n\"WMT\",10,49.78\n\"XOM\",15,82.50\n"
  },
  {
    "path": "Data/portfolio_noheader.csv",
    "content": "\"AA\",100,32.20\n\"IBM\",50,91.10\n\"CAT\",150,83.44\n\"MSFT\",200,51.23\n\"GE\",95,40.37\n\"MSFT\",50,65.10\n\"IBM\",100,70.44\n"
  },
  {
    "path": "Data/prices.csv",
    "content": "\"AA\",9.22\r\n\"AXP\",24.85\r\n\"BA\",44.85\r\n\"BAC\",11.27\r\n\"C\",3.72\r\n\"CAT\",35.46\r\n\"CVX\",66.67\r\n\"DD\",28.47\r\n\"DIS\",24.22\r\n\"GE\",13.48\r\n\"GM\",0.75\r\n\"HD\",23.16\r\n\"HPQ\",34.35\r\n\"IBM\",106.28\r\n\"INTC\",15.72\r\n\"JNJ\",55.16\r\n\"JPM\",36.90\r\n\"KFT\",26.11\r\n\"KO\",49.16\r\n\"MCD\",58.99\r\n\"MMM\",57.10\r\n\"MRK\",27.58\r\n\"MSFT\",20.89\r\n\"PFE\",15.19\r\n\"PG\",51.94\r\n\"T\",24.79\r\n\"UTX\",52.61\r\n\"VZ\",29.26\r\n\"WMT\",49.74\r\n\"XOM\",69.35\r\n"
  },
  {
    "path": "Data/stocksim.py",
    "content": "#!/usr/bin/env python\n# stocksim.py\n#\n# Stock market simulator.  This simulator creates stock market\n# data and provides it in several different ways:\n#\n#    1. Makes periodic updates to a log file stocklog.dat\n#\n# The purpose of this module is to provide data to the user\n# in different ways in order to write interesting Python examples\n\nimport math\nimport time\nimport threading\ntry:\n    import queue\nexcept ImportError:\n    import Queue as queue\n\nhistory_file = \"dowstocks.csv\"\n\n# Convert a time string such as \"4:00pm\" to minutes past midnight\ndef minutes(tm):\n    am_pm = tm[-2:]\n    fields = tm[:-2].split(\":\")\n    hour = int(fields[0])\n    minute = int(fields[1])\n    if hour == 12:\n       hour = 0\n    if am_pm == 'pm':\n       hour += 12\n    return hour*60 + minute\n\n# Convert time in minutes to a format string\ndef minutes_to_str(m):\n    frac,m = math.modf(m)\n    hours = m//60\n    minutes = m % 60\n    seconds = frac * 60\n    return \"%02d:%02d.%02.f\" % (hours,minutes,seconds)\n\n# Read the stock history file as a list of lists\ndef read_history(filename):\n    result = []\n    for line in open(filename):\n        str_fields = line.strip().split(\",\")\n        fields = [eval(x) for x in str_fields]\n        fields[3] = minutes(fields[3])\n        result.append(fields)\n    return result\n\n# Format CSV record\ndef csv_record(fields):\n    s = '\"%s\",%0.2f,\"%s\",\"%s\",%0.2f,%0.2f,%0.2f,%0.2f,%d' % tuple(fields)\n    return s\n\nclass StockTrack(object):\n    def __init__(self,name):\n        self.name    = name\n        self.history = []\n        self.price   = 0\n        self.time    = 0\n        self.index   = 0\n        self.open    = 0\n        self.low     = 0\n        self.high    = 0\n        self.volume  = 0\n        self.initial = 0\n        self.change  = 0\n        self.date    = \"\"\n    def add_data(self,record):\n        self.history.append(record)\n    def reset(self,time):\n        self.time = time\n        # Sort the history by time\n        self.history.sort(key=lambda t:t[3])\n        # Find the first entry who's time is behind the given time\n        self.index = 0\n        while self.index < len(self.history):\n            if self.history[self.index][3] > time:\n                break\n            self.index += 1\n        self.open = self.history[0][5]\n        self.initial = self.history[0][1] - self.history[0][4]\n        self.date = self.history[0][2]\n        self.update()\n        self.low = self.price\n        self.high = self.price\n\n    # Calculate interpolated value of a given field based on\n    # current time\n    def interpolate(self,field):\n        first = self.history[self.index][field]\n        next  = self.history[self.index+1][field]\n        first_t = self.history[self.index][3]\n        next_t = self.history[self.index+1][3]\n        try:\n            slope = (next - first)/(next_t-first_t)\n            return first + slope*(self.time - first_t)\n        except ZeroDivisionError:\n            return first\n\n    # Update all computed values\n    def update(self):\n        self.price = round(self.interpolate(1),2)\n        self.volume = int(self.interpolate(-1))\n        if self.price < self.low:\n            self.low = self.price\n        if self.price >= self.high:\n            self.high = self.price\n        self.change = self.price - self.initial\n        \n    # Increment the time by a delta\n    def incr(self,dt):\n        self.time += dt\n        if self.index < (len(self.history) - 2):\n            while self.index < (len(self.history) - 2) and self.time >= self.history[self.index+1][3]:\n                self.index += 1\n        self.update()\n\n    def make_record(self):\n        return [self.name,round(self.price,2),self.date,minutes_to_str(self.time),round(self.change,2),self.open,round(self.high,2),\n                round(self.low,2),self.volume]\n\nclass MarketSimulator(object):\n    def __init__(self):\n        self.stocks = { }\n        self.prices = { }\n        self.time = 0\n        self.observers = []\n    def register(self,observer):\n        self.observers.append(observer)\n\n    def publish(self,record):\n        for obj in self.observers:\n            obj.update(record)\n    def add_history(self,filename):\n        hist = read_history(filename)\n        for record in hist:\n            if record[0] not in self.stocks:\n                self.stocks[record[0]] = StockTrack(record[0])\n            self.stocks[record[0]].add_data(record) \n\n    def reset(self,time):\n        self.time = time\n        for s in list(self.stocks.values()):\n            s.reset(time)\n\n    # Run forever.  Dt is in seconds\n    def run(self,dt):\n        for s in self.stocks:\n            self.prices[s] = self.stocks[s].price\n            self.publish(self.stocks[s].make_record())\n        while self.time < 1000:\n            for s in self.stocks:\n                self.stocks[s].incr(dt/60.0)    # Increment is in minutes\n                if self.stocks[s].price != self.prices[s]:\n                    self.prices[s] = self.stocks[s].price\n                    self.publish(self.stocks[s].make_record())\n            time.sleep(dt)\n            self.time += (dt/60.0)\n\n\nclass BasicPrinter(object):\n    def update(self,record):\n        print(csv_record(record))\n\nclass LogPrinter(object):\n    def __init__(self,filename):\n        self.f = open(filename,\"w\")\n    def update(self,record):\n        self.f.write(csv_record(record)+\"\\n\")\n        self.f.flush()\n\nm = MarketSimulator()\nm.add_history(history_file)\nm.reset(minutes(\"9:30am\"))\nm.register(BasicPrinter())\nm.register(LogPrinter(\"stocklog.csv\"))\nm.run(1)\n\n\n   \n"
  },
  {
    "path": "Data/words.txt",
    "content": "look into my eyes\nlook into my eyes\nthe eyes the eyes the eyes\nnot around the eyes \ndon't look around the eyes\nlook into my eyes you're under\n"
  },
  {
    "path": "Exercises/README.md",
    "content": "# Advanced Python Mastery\n\nCopyright (C) 2007-2023 \nDavid Beazley (dave@dabeaz.com) \nhttp://www.dabeaz.com\n\nWelcome to the Python Mastery course. This\ndirectory, `pythonmaster` is where you find support files\nrelated to the class exercises.  It is also where you will be doing\nyour work.\n\nThis course requires the use of Python 3.6 or newer.  If you are\nusing Python 2, most of the material still applies, but you will\nhave to make minor code modifications here and there. \n\n- [`PythonMastery.pdf`](../PythonMastery.pdf) is a PDF that contains\nall of the presentation slides.\n\n- The [`Exercises/`](index.md) folder is where you\nfind all the class exercises.\n\n- The [`Data/`](../Data/) folder is where you find data files, scripts, and\nother files used by the exercises.\n\n- The [`Solutions/`](../Solutions/) folder contains complete solution code for\nvarious exercises.  Each problem has its own directory.  For example,\nthe solution to exercise 3.2 can be found in the [`Solutions/3_2/`](../Solutions/3_2/) directory.\n\nEvery attempt has been made to make sure exercises work.  However, it's \npossible that you will find typos or minor mistakes. If you find any\nerrors, please let me know so that I can fix them for future editions\nof the course.\n\n\n\n\n\n"
  },
  {
    "path": "Exercises/ex1_1.md",
    "content": "\\[ [Index](index.md) | []() | [Exercise 1.2](ex1_2.md) \\]\n\n# Exercise 1.1\n\n*Objectives:*\n\n- Make sure Python is installed correctly on your machine\n- Start the interactive interpreter\n- Edit and run a small program\n\n*Files Created:* `art.py`\n\n## (a) Launch Python\n\nStart Python3 on your machine.  Make sure you can type simple\nstatements such as the \"hello world\" program:\n\n```python\n>>> print('Hello World')\nHello World\n>>>\n```\n\nIn much of this course, you'll want to make sure you can work from\nthe interactive REPL like this.   If you're working from a different\nenvironment such as IPython or Jupyter Notebooks, that's fine.\n\n## (b) Some Generative Art\n\nCreate the following program and put it in a file called `art.py`:\n\n```python\n# art.py\n\nimport sys\nimport random\n\nchars = '\\|/'\n\ndef draw(rows, columns):\n    for r in rows:\n        print(''.join(random.choice(chars) for _ in range(columns)))\n\nif __name__ == '__main__':\n    if len(sys.argv) != 3:\n        raise SystemExit(\"Usage: art.py rows columns\")\n    draw(int(sys.argv[1]), int(sys.argv[2]))\n```\n\nMake sure you can run this program from the command line or a terminal.\n\n```\nbash % python3 art.py 10 20\n```\n\nIf you run the above command, you'll get a crash and traceback message.\nGo fix the problem and run the program again.  You should get output like\nthis:\n\n```\nbash % python3 art.py 10 20\n||||/\\||//\\//\\|||\\|\\\n///||\\/||\\//|\\\\|\\\\/\\\n|\\////|//|||\\//|/\\||\n|//\\||\\/|\\///|\\|\\|/|\n|/|//|/|/|\\\\/\\/\\||//\n|\\/\\|\\//\\\\//\\|\\||\\\\/\n|||\\\\\\\\/\\\\\\|/||||\\/|\n\\\\||\\\\\\|\\||||////\\\\|\n//\\//|/|\\\\|\\//\\|||\\/\n\\\\\\|/\\\\|/|\\\\\\|/|/\\/|\nbash %\n```\n\n### Important Note\n\nIt is absolutely essential that you are able to edit, run, and debug\nordinary Python programs for the rest of this course.  The choice\nof editor, IDE, or operating system doesn't matter as long as you\nare able to experiment interactively and create normal Python source\nfiles that can execute from the command line.\n\n\n\\[ [Solution](soln1_1.md) | [Index](index.md) | [Exercise 1.2](ex1_2.md) \\]\n\n\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex1_2.md",
    "content": "\\[ [Index](index.md) | [Exercise 1.1](ex1_1.md) | [Exercise 1.3](ex1_3.md) \\]\n\n\n# Exercise 1.2\n\n*Objectives:*\n\n- Manipulate various built-in Python objects\n\n*Files Created:* None\n\n## Part 1 : Numbers\n\nNumerical calculations work about like you would expect in Python.\nFor example:\n\n```python\n>>> 3 + 4*5\n23\n>>> 23.45 / 1e-02\n2345.0\n>>>\n```\n\nBe aware that integer division is different in Python 2 and Python 3. \n\n```python\n>>> 7 / 4      # In python 2, this truncates to 1\n1.75           \n>>> 7 // 4     # Truncating division\n1 \n>>> \n```\n\nIf you want Python 3 behavior in Python 2, do this:\n\n```python\n>>> from __future__ import division\n>>> 7 / 4\n1.75\n>>> 7 // 4      # Truncating division\n1\n>>>\n```\n\nNumbers have a small set of methods, many of which are actually quite\nrecent and overlooked by even experienced Python programmers.  Try some of them.\n\n```python\n>>> x = 1172.5\n>>> x.as_integer_ratio()\n(2345, 2)\n>>> x.is_integer()\nFalse\n>>> y = 12345\n>>> y.numerator\n12345\n>>> y.denominator\n1\n>>> y.bit_length()\n14\n>>> \n```\n\n## Part 2 : String Manipulation\n\nDefine a string containing a series of stock ticker symbols like this:\n\n```python\n>>> symbols = 'AAPL IBM MSFT YHOO SCO'\n```\n\nNow, let's experiment with different string operations:\n\n### (a) Extracting individual characters and substrings\n\nStrings are arrays of characters.  Try extracting a few characters:\n\n```python\n>>> symbols[0]\n'A'\n>>> symbols[1]\n'A'\n>>> symbols[2]\n'P'\n>>> symbols[-1]        # Last character\n'O'\n>>> symbols[-2]        # 2nd from last character\n'C'\n>>>\n```\n\nTry taking a few slices:\n\n```python\n>>> symbols[:4]\n'AAPL'\n>>> symbols[-3:]\n'SCO'\n>>> symbols[5:8]\n'IBM'\n>>>\n```\n\n### (b) Strings as read-only objects\n\nStrings are read-only.   Verify this by trying to change the first character of `symbols` to a lower-case 'a'. \n\n```python\n>>> symbols[0] = 'a'\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: 'str' object does not support item assignment\n>>> \n```\n\n### (c) String concatenation\n\nAlthough string data is read-only, you can always reassign a variable to a newly created string.   \nTry the following statement which concatenates a new symbol \"GOOG\" to the end of `symbols`:\n\n```python\n>>> symbols += ' GOOG'             \n>>> symbols\n... look at the result ...\n```\n\nNow, try adding \"HPQ\" to the beginning of `symbols` like this:\n\n```python\n>>> symbols = 'HPQ ' + symbols     \n>>> symbols\n... look at the result ...\n```\n\nIt should be noted in both of these examples, the original string `symbols` is _NOT_\nbeing modified \"in place.\"  Instead, a completely new string is created.  The variable name `symbols` is\njust bound to the result.  Afterwards, the old string is destroyed since it's not being used anymore.\n\n\n### (d) Membership testing (substring testing)\n\nExperiment with the `in` operator to check for substrings.  At\nthe interactive prompt, try these operations:\n\n```python\n>>> 'IBM' in symbols\nTrue\n>>> 'AA' in symbols\nTrue\n>>> 'CAT' in symbols\nFalse\n>>>\n```\n\nMake sure you understand why the check for \"AA\" returned `True`.\n\n### (e) String Methods\n\nAt the Python interactive prompt, try experimenting with some of the\nstring methods. \n\n```python\n>>> symbols.lower()\n'hpq aapl ibm msft yhoo sco goog'\n>>> symbols       \n'HPQ AAPL IBM MSFT YHOO SCO GOOG'\n```\n\nRemember, strings are always read-only.  If you want to save the result of an operation, you\nneed to place it in a variable:\n\n```python\n>>> lowersyms = symbols.lower()\n>>> lowersyms\n'hpq aapl ibm msft yhoo sco goog'\n>>>\n```\n\nTry some more operations:\n\n```python\n>>> symbols.find('MSFT')\n13\n>>> symbols[13:17]\n'MSFT'\n>>> symbols = symbols.replace('SCO','')\n>>> symbols\n'HPQ AAPL IBM MSFT YHOO  GOOG'\n>>>\n```\n\n## Part 3 : List Manipulation\n\nIn the first part, you worked with strings containing stock symbols.  For example:\n\n```python\n>>> symbols = 'HPQ AAPL IBM MSFT YHOO  GOOG'\n>>>\n```\n\nDefine the above variable and split it into a list of names using the `split()` operation of strings:\n\n```python\n>>> symlist = symbols.split()\n>>> symlist\n['HPQ', 'AAPL', 'IBM', 'MSFT', 'YHOO', 'GOOG' ]\n>>>\n```\n\n### (a) Extracting and reassigning list elements\n\nLists work like arrays where you can look up and\nmodify elements by numerical index.   Try a few lookups:\n\n```python\n>>> symlist[0]\n'HPQ'\n>>> symlist[1]\n'AAPL'\n>>> symlist[-1]\n'GOOG'\n>>> symlist[-2]\n'YHOO'\n>>>\n```\n\nTry reassigning one of the items:\n\n```python\n>>> symlist[2] = 'AIG'\n>>> symlist\n['HPQ', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'GOOG' ]\n>>>\n```\n\n### (b) Looping over list items\n\nThe `for` loop works by looping over data in a sequence such as a list.   Check this out\nby typing the following loop and watching what happens:\n\n```python\n>>> for s in symlist:\n        print('s =', s)\n\n... look at the output ...\n```\n\n### (c) Membership tests\n\nUse the `in` operator to check if `'AIG'`,`'AA'`, and `'CAT'` are in the list of symbols.\n\n```python\n>>> 'AIG' in symlist\nTrue\n>>> 'AA' in symlist\nFalse\n>>>\n```\n\n### (d) Appending, inserting, and deleting items\n\nUse the `append()` method to add the symbol `'RHT'` to end of `symlist`.  \n\n```python\n>>> symlist.append('RHT')\n>>> symlist\n['HPQ', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'GOOG', 'RHT']\n>>> \n```\n\nUse the `insert()` method to\ninsert the symbol `'AA'` as the second item in the list.\n\n```python\n>>> symlist.insert(1,'AA')\n>>> symlist\n['HPQ', 'AA', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'GOOG', 'RHT']\n>>>\n```\n\nUse the `remove()` method to remove `'MSFT'` from the list. \n\n```python\n>>> symlist.remove('MSFT')\n>>> symlist\n['HPQ', 'AA', 'AAPL', 'AIG', 'YHOO', 'GOOG', 'RHT']\n```\n\nTry calling `remove()` again to see what happens if the item can't be found.\n\n```python\n>>> symlist.remove('MSFT')\n... watch what happens ...\n>>>\n```\n\nUse the `index()` method to find the position of `'YHOO'` in the list.\n\n```python\n>>> symlist.index('YHOO')\n4\n>>> symlist[4]\n'YHOO'\n>>>\n```\n\n### (e) List sorting\n\nWant to sort a list?  Use the `sort()` method.  Try it out:\n\n```python\n>>> symlist.sort()\n>>> symlist\n['AA', 'AAPL', 'AIG', 'GOOG', 'HPQ', 'RHT', 'YHOO']\n>>>\n```\n\nWant to sort in reverse?  Try this:\n\n```python\n>>> symlist.sort(reverse=True)\n>>> symlist\n['YHOO', 'RHT', 'HPQ', 'GOOG', 'AIG', 'AAPL', 'AA']\n>>>\n```\n\nNote: Sorting a list modifies its contents \"in-place.\"  That is, the\nelements of the list are shuffled around, but no new list is created\nas a result.\n\n\n### (f) Lists of anything\n\nLists can contain any kind of object, including other lists (e.g., nested\nlists).  Try this out:\n\n```python\n>>> nums = [101,102,103]\n>>> items = [symlist, nums]\n>>> items\n[['YHOO', 'RHT', 'HPQ', 'GOOG', 'AIG', 'AAPL', 'AA'], [101, 102, 103]]\n```\n\nPay close attention to the above output.  `items` is a list\nwith two elements. Each element is list.\n\nTry some nested list lookups:\n\n```python\n>>> items[0]\n['YHOO', 'RHT', 'HPQ', 'GOOG', 'AIG', 'AAPL', 'AA']\n>>> items[0][1]\n'RHT'\n>>> items[0][1][2]\n'T'\n>>> items[1]\n[101, 102, 103]\n>>> items[1][1]\n102\n>>>\n```\n\n## Part 4 : Dictionaries\n\nIn last few parts, you've simply worked with stock symbols.   However,\nsuppose you wanted to map stock symbols to other data such as the\nprice?  Use a dictionary:\n\n```python\n>>> prices = { 'IBM': 91.1, 'GOOG': 490.1, 'AAPL':312.23 }\n>>>\n```\n\nA dictionary maps keys to values.  Here's how to access:\n\n```python\n>>> prices['IBM']\n91.1\n>>> prices['IBM'] = 123.45\n>>> prices['HPQ'] = 26.15\n>>> prices\n{'GOOG': 490.1, 'AAPL': 312.23, 'IBM': 123.45, 'HPQ': 26.15}\n>>>\n```\n\nTo get a list of keys, use this:\n\n```python\n>>> list(prices)\n['GOOG', 'AAPL', 'IBM', 'HPQ']\n>>>\n```\n\nTo delete a value, use `del`\n\n```python\n>>> del prices['AAPL']\n>>> prices\n{'GOOG': 490.1, 'IBM': 123.45, 'HPQ': 26.15}\n>>>\n```\n\n\n\\[ [Solution](soln1_2.md) | [Index](index.md) | [Exercise 1.1](ex1_1.md) | [Exercise 1.3](ex1_3.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex1_3.md",
    "content": "\\[ [Index](index.md) | [Exercise 1.2](ex1_2.md) | [Exercise 1.4](ex1_4.md) \\]\n\n# Exercise 1.3\n\n*Objectives:*\n\n- Review basic file I/O\n\n*Files Created:* `pcost.py`\n\n## (a) Working with files\n\nThe file `Data/portfolio.dat` contains a list of lines with information\non a portfolio of stocks.  The file looks like this:\n\n```\nAA 100 32.20\nIBM 50 91.10\nCAT 150 83.44\nMSFT 200 51.23\nGE 95 40.37\nMSFT 50 65.10\nIBM 100 70.44\n```\n\nThe first column is the stock name, the second column is the number of\nshares, and the third column is the purchase price of a single share. \n\nWrite a program called `pcost.py` that opens this file, reads\nall lines, and calculates how much it cost to purchase all of the shares\nin the portfolio. To do this, compute the sum of the second column\nmultiplied by the third column.\n\n\\[ [Solution](soln1_3.md) | [Index](index.md) | [Exercise 1.2](ex1_2.md) | [Exercise 1.4](ex1_4.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex1_4.md",
    "content": "\\[ [Index](index.md) | [Exercise 1.3](ex1_3.md) | [Exercise 1.5](ex1_5.md) \\]\n\n# Exercise 1.4\n\n*Objectives:*\n\n- Review of how to define simple functions\n- Exception handling\n\n*Files Created:* None\n\n*Files Modified:* `pcost.py`\n\n## (a) Defining a function\n\nTake the program `pcost.py` that you wrote in the last exercise and\nconvert it into a function `portfolio_cost(filename)` that takes a\nfilename as input, reads the portfolio data in that file, and returns\nthe total cost of the portfolio as a floating point number. Once you\nwritten the function, have your program call the function by simply\nadding this statement at the end:\n\n```python\nprint(portfolio_cost('Data/portfolio.dat'))\n```\n\nRun your program and make sure it produces the same output as\nbefore.\n\n\n## (b) Adding Error Handling\n\nWhen writing programs that process data, it is common to encounter\nerrors related to bad data (malformed, missing fields, etc.).  Modify\nyour `pcost.py` program to read the data file `Data/portfolio3.dat`\nand run it (hint: it should crash).\n\nModify your function slightly so that it is able to recover from lines\nwith bad data.  For example, the conversion functions `int()` and\n`float()` raise a `ValueError` exception if they can't convert the\ninput.  Use `try` and `except` to catch and print a warning message\nabout lines that can't be parsed.  For example:\n\n```\nCouldn't parse: 'C - 53.08\\n'\nReason: invalid literal for int() with base 10: '-'\nCouldn't parse: 'DIS - 34.20\\n'\nReason: invalid literal for int() with base 10: '-'\n...\n```\n\nTry running your program on the `Data/portfolio3.dat` file\nagain.   It should run successfully despite printed warning messages.\n\n## (c) Interactive Experimentation\n\nRun your `pcost.py` program and call the\n`portfolio_cost()` function directly from the interactive\ninterpreter.\n\n```python\n>>> portfolio_cost('Data/portfolio.dat')\n44671.15\n>>> portfolio_cost('Data/portfolio2.dat')\n19908.75\n>>>\n```\n\nNote: To do this, you might have to run python using the `-i`\noption.  For example:\n\n```\nbash % python3 -i pcost.py\n```\n\nWe are going to be writing a lot of programs where you define\nfunctions and experiment interactively.  Make sure you know how to do\nthis.\n\n\\[ [Solution](soln1_4.md) | [Index](index.md) | [Exercise 1.3](ex1_3.md) | [Exercise 1.5](ex1_5.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex1_5.md",
    "content": "\\[ [Index](index.md) | [Exercise 1.4](ex1_4.md) | [Exercise 1.6](ex1_6.md) \\]\n\n# Exercise 1.5\n\n*Objectives:*\n\n- Review of how to define a simple object\n\n*Files Created:* `stock.py`\n\n## (a) Defining a simple object\n\nCreate a file `stock.py` and define the following class:\n\n```python\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n    def cost(self):\n        return self.shares * self.price\n```\n\nOnce you have done this, run your program and experiment with your new\n`Stock` object:\n\n```python\n>>> s = Stock('GOOG',100,490.10)\n>>> s.name\n'GOOG'\n>>> s.shares\n100\n>>> s.price\n490.1\n>>> s.cost()\n49010.0\n>>> print('%10s %10d %10.2f' % (s.name, s.shares, s.price))\n      GOOG        100     490.10\n>>> t = Stock('IBM', 50, 91.5)\n>>> t.cost()\n4575.0\n>>> \n```\n\n\\[ [Solution](soln1_5.md) | [Index](index.md) | [Exercise 1.4](ex1_4.md) | [Exercise 1.6](ex1_6.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex1_6.md",
    "content": "\\[ [Index](index.md) | [Exercise 1.5](ex1_5.md) | [Exercise 2.1](ex2_1.md) \\]\n\n# Exercise 1.6\n\n*Objectives:*\n\n- Defining modules\n- Using the import statement\n\n*Files Created:* None\n\n**Note:**\nFor this exercise involving modules, it is\ncritically important to make sure you are running Python in a proper\nenvironment.  You may need to check the value of `sys.path` if you \ncan't get import statements to work.  Ask for assistance if everything\nseems broken.\n\nBefore starting this exercise, first restart your Python interpreter session.  If using IDLE, click on\nthe shell window and look for a menu option \"Shell > Restart Shell\".   You should get a message like this:\n\n```python\n>>> ##################== RESTART ##################==\n>>> \n```\n\nIf you are using Unix, simply exit Python and restart the interpreter.\n\n## (a) Using the import statement\n\nIn previous exercises, you wrote two programs `pcost.py` and\n`stock.py`.   Use the `import` statement to load these\nprograms and use their functionality:\n\n```python\n>>> import pcost\n44671.15\n>>> pcost.portfolio_cost('Data/portfolio2.dat')\n19908.75\n>>> from stock import Stock\n>>> s = Stock('GOOG', 100, 490.10)\n>>> s.name\n'GOOG'\n>>> s.cost()\n49010.0\n>>>\n```\n\nIf you can't get the above statements to work, you might have placed\nyour programs in a funny directory.  Make sure you are running Python\nin the same directory as your files or that the directory is included\non `sys.path`.\n\n\n## (b) Main Module\n\nIn your `pcost.py` program, the last statement called a\nfunction and printed out the result.  Modify the program so that this\nstep only occurs if the program is run as the main program.  Now,\ntry running the program two ways:\n\nFirst, run the program as main:\n\n```\nbash % python3 pcost.py\n44671.25\nbash %\n```\n\nNext, run the program as a library import.  You should not see any\noutput.\n\n```python\n>>> import pcost\n>>>\n```\n\n\\[ [Solution](soln1_6.md) | [Index](index.md) | [Exercise 1.5](ex1_5.md) | [Exercise 2.1](ex2_1.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex2_1.md",
    "content": "\\[ [Index](index.md) | [Exercise 1.6](ex1_6.md) | [Exercise 2.2](ex2_2.md) \\]\n\n# Exercise 2.1\n\n*Objectives:*\n\n- Figure out the most memory-efficient way to store a lot of data.\n- Learn about different ways of representing records including tuples,\ndictionaries, classes, and named tuples.\n\nIn this exercise, we look at different choices for representing data\nstructures with an eye towards memory use and efficiency.  A lot of\npeople use Python to perform various kinds of data analysis so knowing\nabout different options and their tradeoffs is useful information.\n\n## (a) Stuck on the bus\n\nThe file `Data/ctabus.csv` is a CSV file containing\ndaily ridership data for the Chicago Transit Authority (CTA) bus\nsystem from January 1, 2001 to August 31, 2013.  It contains\napproximately 577000 rows of data.  Use Python to view a few lines\nof data to see what it looks like:\n\n```python\n>>> f = open('Data/ctabus.csv')\n>>> next(f)\n'route,date,daytype,rides\\n'\n>>> next(f)\n'3,01/01/2001,U,7354\\n'\n>>> next(f)\n'4,01/01/2001,U,9288\\n'\n>>>\n```\n\nThere are 4 columns of data.\n\n- route: Column 0.  The bus route name.\n- date: Column 1.  A date string of the form MM/DD/YYYY.\n- daytype: Column 2. A day type code (U=Sunday/Holiday, A=Saturday, W=Weekday)\n- rides: Column 3. Total number of riders (integer)\n\nThe `rides` column records the total number of people who boarded a\nbus on that route on a given day. Thus, from the example, 7354 people\nrode the number 3 bus on January 1, 2001.\n\n## (b) Basic memory use of text\n\nLet's get a baseline of the memory required to work with this\ndatafile.  First, restart Python and try a very simple experiment of\nsimply grabbing the file and storing its data in a single string:\n\n```python\n>>> # --- RESTART \n>>> import tracemalloc\n>>> f = open('Data/ctabus.csv')\n>>> tracemalloc.start()\n>>> data = f.read()\n>>> len(data)\n12361039\n>>> current, peak = tracemalloc.get_traced_memory()\n>>> current\n12369664\n>>> peak\n24730766\n>>> \n```\n\nYour results might vary somewhat, but you should see current\nmemory use in the range of 12MB with a peak of 24MB.\n\nWhat happens if you read the entire file into a list of strings\ninstead?  Restart Python and try this:\n\n```python\n>>> # --- RESTART\n>>> import tracemalloc\n>>> f = open('Data/ctabus.csv')\n>>> tracemalloc.start()\n>>> lines = f.readlines()\n>>> len(lines)\n577564\n>>> current, peak = tracemalloc.get_traced_memory()\n>>> current\n45828030\n>>> peak\n45867371\n>>> \n```\n\nYou should see the memory use go up significantly into the range of 40-50MB.\nPoint to ponder: what might be the source of that extra overhead?\n\n## (c) A List of Tuples\n\nIn practice, you might read the data into a list and convert each line\ninto some other data structure.  Here is a program `readrides.py` that\nreads the entire file into a list of tuples using the `csv` module:\n\n```python\n# readrides.py\n\nimport csv\n\ndef read_rides_as_tuples(filename):\n    '''\n    Read the bus ride data as a list of tuples\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = (route, date, daytype, rides)\n            records.append(record)\n    return records\n\nif __name__ == '__main__':\n    import tracemalloc\n    tracemalloc.start()\n    rows = read_rides_as_tuples('Data/ctabus.csv')\n    print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory())\n```\n\nRun this program using `python3 -i readrides.py` and look at the\nresulting contents of `rows`. You should get a list of tuples like\nthis:\n\n```python\n>>> len(rows)\n577563\n>>> rows[0]\n('3', '01/01/2001', 'U', 7354)\n>>> rows[1]\n('4', '01/01/2001', 'U', 9288)\n```\n\nLook at the resulting memory use. It should be substantially higher\nthan in part (b).\n\n## (d) Memory Use of Other Data Structures\n\nPython has many different choices for representing data structures.\nFor example:\n\n```python\n# A tuple\nrow = (route, date, daytype, rides)\n\n# A dictionary\nrow = {\n    'route': route,\n    'date': date,\n    'daytype': daytype,\n    'rides': rides,\n}\n\n# A class\nclass Row:\n    def __init__(self, route, date, daytype, rides):\n        self.route = route\n        self.date = date\n        self.daytype = daytype\n        self.rides = rides\n\n# A named tuple\nfrom collections import namedtuple\nRow = namedtuple('Row', ['route', 'date', 'daytype', 'rides'])\n\n# A class with __slots__\nclass Row:\n    __slots__ = ['route', 'date', 'daytype', 'rides']\n    def __init__(self, route, date, daytype, rides):\n        self.route = route\n        self.date = date\n        self.daytype = daytype\n        self.rides = rides\n```\nYour task is as follows:  Create different versions of the `read_rides()` function\nthat use each of these data structures to represent a single row of data.\nThen, find out the resulting memory use of each option.   Find out which\napproach offers the most efficient storage if you were working with a lot \nof data all at once.\n\n\\[ [Solution](soln2_1.md) | [Index](index.md) | [Exercise 1.6](ex1_6.md) | [Exercise 2.2](ex2_2.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex2_2.md",
    "content": "\\[ [Index](index.md) | [Exercise 2.1](ex2_1.md) | [Exercise 2.3](ex2_3.md) \\]\n\n# Exercise 2.2\n\n*Objectives:*\n\n- Work with various containers\n- List/Set/Dict Comprehensions\n- Collections module\n- Data analysis challenge\n\nMost Python programmers are generally familiar with lists, dictionaries,\ntuples, and other basic datatypes. In this exercise, we'll put that\nknowledge to work to solve various data analysis problems.\n\n## (a) Preliminaries\n\nTo get started, let's review some basics with a slightly simpler dataset--\na portfolio of stock holdings. Create a file `readport.py` and put this\ncode in it:\n\n```python\n# readport.py\n\nimport csv\n\n# A function that reads a file into a list of dicts\ndef read_portfolio(filename):\n    portfolio = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            record = {\n                'name' : row[0],\n                'shares' : int(row[1]),\n                'price' : float(row[2])\n            }\n            portfolio.append(record)\n    return portfolio\n```\n\nThis file reads some simple stock market data in the file `Data/portfolio.csv`.  Use\nthe function to read the file and look at the results:\n\n```python\n>>> portfolio = read_portfolio('Data/portfolio.csv')\n>>> from pprint import pprint\n>>> pprint(portfolio)\n[{'name': 'AA', 'price': 32.2, 'shares': 100},\n {'name': 'IBM', 'price': 91.1, 'shares': 50},\n {'name': 'CAT', 'price': 83.44, 'shares': 150},\n {'name': 'MSFT', 'price': 51.23, 'shares': 200},\n {'name': 'GE', 'price': 40.37, 'shares': 95},\n {'name': 'MSFT', 'price': 65.1, 'shares': 50},\n {'name': 'IBM', 'price': 70.44, 'shares': 100}]\n>>>\n```\n\nIn this data, each row consists of a stock name, a number of held\nshares, and a purchase price.   There are multiple entries for\ncertain stock names such as MSFT and IBM.\n\n## (b) Comprehensions\n\nList, set, and dictionary comprehensions can be a useful tool for manipulating\ndata.  For example, try these operations:\n\n```python\n>>> # Find all holdings more than 100 shares\n>>> [s for s in portfolio if s['shares'] > 100]\n[{'name': 'CAT', 'shares': 150, 'price': 83.44}, \n {'name': 'MSFT', 'shares': 200, 'price': 51.23}]\n\n>>> # Compute total cost (shares * price)\n>>> sum([s['shares']*s['price'] for s in portfolio])\n44671.15\n>>>\n\n>>> # Find all unique stock names (set)\n>>> { s['name'] for s in portfolio }\n{'MSFT', 'IBM', 'AA', 'GE', 'CAT'}\n>>>\n\n>>> # Count the total shares of each of stock\n>>> totals = { s['name']: 0 for s in portfolio }\n>>> for s in portfolio:\n        totals[s['name']] += s['shares']\n\n>>> totals\n{'AA': 100, 'IBM': 150, 'CAT': 150, 'MSFT': 250, 'GE': 95}\n>>> \n```\n\n## (c) Collections\n\nThe `collections` module has a variety of classes for more specialized data\nmanipulation.  For example, the last example could be solved with a `Counter` like this:\n\n```python\n>>> from collections import Counter\n>>> totals = Counter()\n>>> for s in portfolio:\n        totals[s['name']] += s['shares']\n\n>>> totals\nCounter({'MSFT': 250, 'IBM': 150, 'CAT': 150, 'AA': 100, 'GE': 95})\n>>>\n```\n\nCounters are interesting in that they support other kinds of operations such as ranking\nand mathematics.  For example:\n\n```python\n>>> # Get the two most common holdings\n>>> totals.most_common(2)\n[('MSFT', 250), ('IBM', 150)]\n>>>\n\n>>> # Adding counters together\n>>> more = Counter()\n>>> more['IBM'] = 75\n>>> more['AA'] = 200\n>>> more['ACME'] = 30\n>>> more\nCounter({'AA': 200, 'IBM': 75, 'ACME': 30})\n>>> totals\nCounter({'MSFT': 250, 'IBM': 150, 'CAT': 150, 'AA': 100, 'GE': 95})\n>>> totals + more\nCounter({'AA': 300, 'MSFT': 250, 'IBM': 225, 'CAT': 150, 'GE': 95, 'ACME': 30})\n>>> \n```\n\nThe `defaultdict` object can be used to group data.  For example, suppose\nyou want to make it easy to find all matching entries for a given name such as\nIBM.  Try this:\n\n```python\n>>> from collections import defaultdict\n>>> byname = defaultdict(list)\n>>> for s in portfolio:\n        byname[s['name']].append(s)\n\n>>> byname['IBM']\n[{'name': 'IBM', 'shares': 50, 'price': 91.1}, {'name': 'IBM', 'shares': 100, 'price': 70.44}]\n>>> byname['AA']\n[{'name': 'AA', 'shares': 100, 'price': 32.2}]\n>>>\n```\n\nThe key feature that makes this work is that a defaultdict\nautomatically initializes elements for you--allowing an insertion of a\nnew element and an `append()` operation to be combined together.\n\n## (d) Data Analysis Challenge\n\nIn the last exercise you just wrote some code to read CSV-data related\nto the Chicago Transit Authority.  For example, you can grab the data\nas dictionaries like this:\n\n```python\n>>> import readrides\n>>> rows = readrides.read_rides_as_dicts('Data/ctabus.csv')\n>>>\n```\n\nIt would be a shame to do all of that work and then do nothing with\nthe data.\n\nIn this exercise, your task is this: write a program to answer the\nfollowing three questions:\n\n1. How many bus routes exist in Chicago?\n\n2. How many people rode the number 22 bus on February 2, 2011?  What about any route on any date of your choosing?\n\n3. What is the total number of rides taken on each bus route?\n\n4. What five bus routes had the greatest ten-year increase in ridership from 2001 to 2011?\n\nYou are free to use any technique whatsoever to answer the above\nquestions as long as it's part of the Python standard library (i.e.,\nbuilt-in datatypes, standard library modules, etc.). \n\n\\[ [Solution](soln2_2.md) | [Index](index.md) | [Exercise 2.1](ex2_1.md) | [Exercise 2.3](ex2_3.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex2_3.md",
    "content": "\\[ [Index](index.md) | [Exercise 2.2](ex2_2.md) | [Exercise 2.4](ex2_4.md) \\]\n\n# Exercise 2.3\n\n*Objectives:*\n\n- Iterate like a pro\n\n*Files Modified:* None.\n\nIteration is an essential Python skill.  In this exercise, we look at\na number of common iteration idioms.\n\nStart the exercise by grabbing some rows of data from a CSV file.\n\n```python\n>>> import csv\n>>> f = open('Data/portfolio.csv')\n>>> f_csv = csv.reader(f)\n>>> headers = next(f_csv)\n>>> headers\n['name', 'shares', 'price']\n>>> rows = list(f_csv)\n>>> from pprint import pprint\n>>> pprint(rows)\n[['AA', '100', '32.20'],\n ['IBM', '50', '91.10'],\n ['CAT', '150', '83.44'],\n ['MSFT', '200', '51.23'],\n ['GE', '95', '40.37'],\n ['MSFT', '50', '65.10'],\n ['IBM', '100', '70.44']]\n>>>\n```\n\n## (a) Basic Iteration and Unpacking\n\nThe `for` statement iterates over any sequence of data. For example:\n\n```python\n>>> for row in rows:\n        print(row)\n\n['AA', '100', '32.20']\n['IBM', '50', '91.10']\n['CAT', '150', '83.44']\n['MSFT', '200', '51.23']\n['GE', '95', '40.37']\n['MSFT', '50', '65.10']\n['IBM', '100', '70.44']\n>>>\n```\n\nUnpack the values into separate variables if you need to:\n\n```python\n>>> for name, shares, price in rows:\n        print(name, shares, price)\n\nAA 100 32.20\nIBM 50 91.10\nCAT 150 83.44\nMSFT 200 51.23\nGE 95 40.37\nMSFT 50 65.10\nIBM 100 70.44\n>>>\n```\n\nIt's somewhat common to use `_` or `__` as a throw-away variable if you don't care\nabout one or more of the values.  For example:\n\n```python\n>>> for name, _, price in rows:\n        print(name, price)\n\nAA 32.20\nIBM 91.10\nCAT 83.44\nMSFT 51.23\nGE 40.37\nMSFT 65.10\nIBM 70.44\n>>>\n```\n\nIf you don't know how many values are being unpacked, you can use `*` as a wildcard.\nTry this experiment in grouping the data by name:\n\n```python\n>>> from collections import defaultdict\n>>> byname = defaultdict(list)\n>>> for name, *data in rows:\n        byname[name].append(data)\n\n>>> byname['IBM']\n[['50', '91.10'], ['100', '70.44']]\n>>> byname['CAT']\n[['150', '83.44']]\n>>> for shares, price in byname['IBM']:\n        print(shares, price)\n\n50 91.10\n100 70.44\n>>>\n```\n\n## (b) Counting with enumerate()\n\n`enumerate()` is a useful function if you ever need to keep a counter\nor index while iterating. For example, suppose you wanted an extra row\nnumber:\n\n```python\n>>> for rowno, row in enumerate(rows):\n        print(rowno, row)\n\n0 ['AA', '100', '32.20']\n1 ['IBM', '50', '91.10']\n2 ['CAT', '150', '83.44']\n3 ['MSFT', '200', '51.23']\n4 ['GE', '95', '40.37']\n5 ['MSFT', '50', '65.10']\n6 ['IBM', '100', '70.44']\n>>>\n```\n\nYou can combine this with unpacking if you're careful about how you structure it:\n\n```python\n>>> for rowno, (name, shares, price) in enumerate(rows):\n        print(rowno, name, shares, price)\n\n0 AA 100 32.20\n1 IBM 50 91.10\n2 CAT 150 83.44\n3 MSFT 200 51.23\n4 GE 95 40.37\n5 MSFT 50 65.10\n6 IBM 100 70.44\n>>> \n```\n\n## (c) Using the zip() function\n\nThe `zip()` function is most commonly used to pair data.  For example,\nrecall that you created a `headers` variable:\n\n```python\n>>> headers\n['name', 'shares', 'price']\n>>>\n```\n\nThis might be useful to combine with the other row data:\n\n```python\n>>> row = rows[0]\n>>> row\n['AA', '100', '32.20']\n>>> for col, val in zip(headers, row):\n        print(col, val)\n\nname AA\nshares 100\nprice 32.20\n>>>\n```\n\nOr maybe you can use it to make a dictionary:\n\n```python\n>>> dict(zip(headers, row))\n{'name': 'AA', 'shares': '100', 'price': '32.20'}\n>>>\n```\n\nOr maybe a sequence of dictionaries:\n\n```python\n>>> for row in rows:\n        record = dict(zip(headers, row))\n        print(record)\n\n{'name': 'AA', 'shares': '100', 'price': '32.20'}\n{'name': 'IBM', 'shares': '50', 'price': '91.10'}\n{'name': 'CAT', 'shares': '150', 'price': '83.44'}\n{'name': 'MSFT', 'shares': '200', 'price': '51.23'}\n{'name': 'GE', 'shares': '95', 'price': '40.37'}\n{'name': 'MSFT', 'shares': '50', 'price': '65.10'}\n{'name': 'IBM', 'shares': '100', 'price': '70.44'}\n>>>\n```\n\n## (d) Generator Expressions\n\nA generator expression is almost exactly the same as a list\ncomprehension except that it does not create a list.  Instead, it\ncreates an object that produces the results incrementally--typically\nfor consumption by iteration. Try a simple example:\n\n```python\n>>> nums = [1,2,3,4,5]\n>>> squares = (x*x for x in nums)\n>>> squares\n<generator object <genexpr> at 0x37caa8>\n>>> for n in squares:\n        print(n)\n\n1\n4\n9\n16\n25\n>>>\n```\n\nYou will notice that a generator expression can only be used once.\nWatch what happens if you do the for-loop again:\n\n```python\n>>> for n in squares:\n        print(n)\n\n>>>\n```\n\nYou can manually get the results one-at-a-time if you use the\n`next()` function. Try this:\n\n```python\n>>> squares = (x*x for x in nums)\n>>> next(squares)\n1\n>>> next(squares)\n4\n>>> next(squares)\n9\n>>>\n```\n\nKeeping typing `next()` to see what happens when there is no\nmore data.\n\nIf the task you are performing is more complicated, you can\nstill take advantage of generators by writing a generator function \nand using the `yield` statement instead.\nFor example:\n\n```python\n>>> def squares(nums):\n        for x in nums:\n            yield x*x\n\n>>> for n in squares(nums):\n        print(n)\n\n1\n4\n9\n16\n25\n>>>\n```\n\nWe'll return to generator functions a little later in the course--for now,\njust view such functions as having the interesting property of feeding\nvalues to the `for`-statement.\n\n## (e) Generator Expressions and Reduction Functions\n\nGenerator expressions are especially useful for feeding data into\nfunctions such as `sum()`, `min()`, `max()`,\n`any()`, etc.   Try some examples using the portfolio data from\nearlier.  Carefully observe that these examples are missing some\nextra square brackets ([]) that appeared when using list comprehensions.\n\n```python\n>>> from readport import read_portfolio\n>>> portfolio = read_portfolio('Data/portfolio.csv')\n>>> sum(s['shares']*s['price'] for s in portfolio)\n44671.15\n>>> min(s['shares'] for s in portfolio)\n50\n>>> any(s['name'] == 'IBM' for s in portfolio)\nTrue\n>>> all(s['name'] == 'IBM' for s in portfolio)\nFalse\n>>> sum(s['shares'] for s in portfolio if s['name'] == 'IBM')\n150\n>>>\n```\n\nHere is a subtle use of a generator expression in making comma\nseparated values:\n\n```python\n>>> s = ('GOOG',100,490.10)\n>>> ','.join(s)\n... observe that it fails ...\n>>> ','.join(str(x) for x in s)    # This works\n'GOOG,100,490.1'\n>>>\n```\n\nThe syntax in the above examples takes some getting used to, but the\ncritical point is that none of the operations ever create a fully\npopulated list of results.  This gives you a big memory savings.  However,\nyou do need to make sure you don't go overboard with the syntax.\n\n## (f) Saving a lot of memory\n\nIn [Exercise 2.1](ex2_1.md) you wrote a function\n`read_rides_as_dicts()` that read the CTA bus data into a list of\ndictionaries.  Using it requires a lot of memory. For example,\nlet's find the day on which the route 22 bus had the greatest\nridership:\n\n```python\n>>> import tracemalloc\n>>> tracemalloc.start()\n>>> import readrides\n>>> rows = readrides.read_rides_as_dicts('Data/ctabus.csv')\n>>> rt22 = [row for row in rows if row['route'] == '22']\n>>> max(rt22, key=lambda row: row['rides'])\n{'date': '06/11/2008', 'route': '22', 'daytype': 'W', 'rides': 26896}\n>>> tracemalloc.get_traced_memory()\n... look at result. Should be around 220MB\n>>>\n```\n\nNow, let's try an example involving generators. Restart Python\nand try this:\n\n```python\n>>> # RESTART\n>>> import tracemalloc\n>>> tracemalloc.start()\n>>> import csv\n>>> f = open('Data/ctabus.csv')\n>>> f_csv = csv.reader(f)\n>>> headers = next(f_csv)\n>>> rows = (dict(zip(headers,row)) for row in f_csv)\n>>> rt22 = (row for row in rows if row['route'] == '22')\n>>> max(rt22, key=lambda row: int(row['rides']))\n{'date': '06/11/2008', 'route': '22', 'daytype': 'W', 'rides': 26896}\n>>> tracemalloc.get_traced_memory()\n... look at result. Should be a LOT smaller than before\n>>>\n```\n\nKeep in mind that you just processed the entire dataset as if it was\nstored as a sequence of dictionaries.  Yet, nowhere did you actually\ncreate and store a list of dictionaries.   Not all problems can be\nstructured in this way, but if you can work with data in an\niterative manner, generator expressions can save a huge amount of memory.\n\n\\[ [Solution](soln2_3.md) | [Index](index.md) | [Exercise 2.2](ex2_2.md) | [Exercise 2.4](ex2_4.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex2_4.md",
    "content": "\\[ [Index](index.md) | [Exercise 2.3](ex2_3.md) | [Exercise 2.5](ex2_5.md) \\]\n\n# Exercise 2.4\n\n*Objectives:*\n\n- Make a new primitive type\n\nIn most programs, you use the primitive types such as `int`, `float`,\nand `str` to represent data.  However, you're not limited to just those\ntypes.  The standard library has modules such as the `decimal` and\n`fractions` module that implement new primitive types.  You can also\nmake your own types as long as you understand the underlying protocols\nwhich make Python objects work.  In this exercise, we'll make a new\nprimitive type.  There are a lot of little details to worry about, but\nthis will give you a general sense for what's required.\n\n## (a) Mutable Integers\n\nPython integers are normally immutable.   However, suppose you wanted to\nmake a mutable integer object.  Start off by making a class like this:\n\n```python\n# mutint.py\n\nclass MutInt:\n    __slots__ = ['value']\n\n    def __init__(self, value):\n        self.value = value\n```\n\nTry it out:\n\n```python\n>>> a = MutInt(3)\n>>> a\n<__main__.MutInt object at 0x10e79d408>\n>>> a.value\n3\n>>> a.value = 42\n>>> a.value\n42\n>>> a + 10\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: unsupported operand type(s) for +: 'MutInt' and 'int'\n>>>\n```\n\nThat's all very exciting except that nothing really works with this\nnew `MutInt` object. Printing is horrible, none of the math\noperators work, and it's basically rather useless.  Well, except for\nthe fact that its value is mutable--it does have that.\n\n## (b) Fixing output\n\nYou can fix output by giving the object methods such as `__str__()`,\n`__repr__()`, and `__format__()`.  For example:\n\n```python\n# mint.py\n\nclass MutInt:\n    __slots__ = ['value']\n\n    def __init__(self, value):\n        self.value = value\n\n    def __str__(self):\n        return str(self.value)\n\n    def __repr__(self):\n        return f'MutInt({self.value!r})'\n\n    def __format__(self, fmt):\n        return format(self.value, fmt)\n```\n\nTry it out:\n\n```python\n>>> a = MutInt(3)\n>>> print(a)\n3\n>>> a\nMutInt(3)\n>>> f'The value is {a:*^10d}'\nThe value is ****3*****\n>>> a.value = 42\n>>> a\nMutInt(42)\n>>>\n```\n\n## (c) Math Operators\n\nYou can make an object work with various math operators if you implement the\nappropriate methods for it.  However, it's your responsibility to\nrecognize other types of data and implement the appropriate conversion\ncode.  Modify the `MutInt` class by giving it an `__add__()` method\nas follows:\n\n```python\nclass MutInt:\n    __slots__ = ['value']\n\n    def __init__(self, value):\n        self.value = value\n\n    ...\n\n    def __add__(self, other):\n        if isinstance(other, MutInt):\n            return MutInt(self.value + other.value)\n        elif isinstance(other, int):\n            return MutInt(self.value + other)\n        else:\n            return NotImplemented\n```\n\nWith this change, you should find that you can add both integers and\nmutable integers. The result is a `MutInt` instance. Adding\nother kinds of numbers results in an error:\n\n```python\n>>> a = MutInt(3)\n>>> b = a + 10\n>>> b\nMutInt(13)\n>>> b.value = 23\n>>> c = a + b\n>>> c\nMutInt(26)\n>>> a + 3.5\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: unsupported operand type(s) for +: 'MutInt' and 'float'\n>>> \n```\n\nOne problem with the code is that it doesn't work when the order of operands\nis reversed.  Consider:\n\n```python\n>>> a + 10\nMutInt(13)\n>>> 10 + a\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: unsupported operand type(s) for +: 'int' and 'MutInt'\n>>> \n```\n\nThis is occurring because the `int` type has no knowledge of `MutInt`\nand it's confused. This can be fixed by adding an `__radd__()` method.  This\nmethod is called if the first attempt to call `__add__()` didn't work with the\nprovided object.\n\n```python\nclass MutInt:\n    __slots__ = ['value']\n\n    def __init__(self, value):\n        self.value = value\n\n    ...\n\n    def __add__(self, other):\n        if isinstance(other, MutInt):\n            return MutInt(self.value + other.value)\n        elif isinstance(other, int):\n            return MutInt(self.value + other)\n        else:\n            return NotImplemented\n\n    __radd__ = __add__    # Reversed operands\n```\n\nWith this change, you'll find that addition works:\n\n```python\n>>> a = MutInt(3)\n>>> a + 10\nMutInt(13)\n>>> 10 + a\nMutInt(13)\n>>>\n```\n\nSince our integer is mutable, you can also make it recognize the in-place\nadd-update operator `+=` by implementing the `__iadd__()` method:\n\n```python\nclass MutInt:\n    __slots__ = ['value']\n\n    def __init__(self, value):\n        self.value = value\n\n    ...\n\n    def __iadd__(self, other):\n        if isinstance(other, MutInt):\n            self.value += other.value\n            return self\n        elif isinstance(other, int):\n            self.value += other\n            return self\n        else:\n            return NotImplemented\n```\n\nThis allows for interesting uses like this:\n\n```python\n>>> a = MutInt(3)\n>>> b = a\n>>> a += 10\n>>> a\nMutInt(13)\n>>> b                 # Notice that b also changes\nMutInt(13)\n>>>\n```\n\nThat might seem kind of strange that `b` also changes, but there are subtle features like\nthis with built-in Python objects. For example:\n\n```python\n>>> a = [1,2,3]\n>>> b = a\n>>> a += [4,5]\n>>> a\n[1, 2, 3, 4, 5]\n>>> b\n[1, 2, 3, 4, 5]\n\n>>> c = (1,2,3)\n>>> d = c\n>>> c += (4,5)\n>>> c\n(1, 2, 3, 4, 5)\n>>> d                  # Explain difference from lists\n(1, 2, 3)\n>>> \n```\n\n## (d) Comparisons\n\nOne problem is that comparisons still don't work.  For example:\n\n```python\n>>> a = MutInt(3)\n>>> b = MutInt(3)\n>>> a == b\nFalse\n>>> a == 3\nFalse\n>>>\n```\n\nYou can fix this by adding an `__eq__()` method.  Further methods such\nas `__lt__()`, `__le__()`, `__gt__()`, `__ge__()` can be used to\nimplement other comparisons.  For example:\n\n```python\nclass MutInt:\n    __slots__ = ['value']\n\n    def __init__(self, value):\n        self.value = value\n\n    ...\n    def __eq__(self, other):\n        if isinstance(other, MutInt):\n            return self.value == other.value\n        elif isinstance(other, int):\n            return self.value == other\n        else:\n            return NotImplemented\n        \n    def __lt__(self, other):\n        if isinstance(other, MutInt):\n            return self.value < other.value\n        elif isinstance(other, int):\n            return self.value < other\n        else:\n            return NotImplemented\n```\n\nTry it:\n\n```python\n>>> a = MutInt(3)\n>>> b = MutInt(3)\n>>> a == b\nTrue\n>>> c = MutInt(4)\n>>> a < c\nTrue\n>>> a <= c\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: '<=' not supported between instances of 'MutInt' and 'MutInt'\n>>> \n```\n\nThe reason the `<=` operator is failing is that no `__le__()` method was provided.\nYou could code it separately, but an easier way to get it is to use the `@total_ordering`\ndecorator:\n\n```python\nfrom functools import total_ordering\n\n@total_ordering\nclass MutInt:\n    __slots__ = ['value']\n\n    def __init__(self, value):\n        self.value = value\n\n    ...\n\n    def __eq__(self, other):\n        if isinstance(other, MutInt):\n            return self.value == other.value\n        elif isinstance(other, int):\n            return self.value == other\n        else:\n            return NotImplemented\n        \n    def __lt__(self, other):\n        if isinstance(other, MutInt):\n            return self.value < other.value\n        elif isinstance(other, int):\n            return self.value < other\n        else:\n            return NotImplemented\n```\n\n`@total_ordering` fills in the missing comparison methods for you as long as\nyou minimally provide an equality operator and one of the other relations.\n\n## (e) Conversions\n\nYour new primitive type is almost complete.  You might want to give it \nthe ability to work with some common conversions. For example:\n\n```python\n>>> a = MutInt(3)\n>>> int(a)\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: int() argument must be a string, a bytes-like object or a number, not 'MutInt'\n>>> float(a)\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: float() argument must be a string, a bytes-like object or a number, not 'MutInt'\n>>>\n```\n\nYou can give your class an `__int__()` and `__float__()` method to fix this:\n\n```python\nfrom functools import total_ordering\n\n@total_ordering\nclass MutInt:\n    __slots__ = ['value']\n\n    def __init__(self, value):\n        self.value = value\n\n    ...\n\n    def __int__(self):\n        return self.value\n\n    def __float__(self):\n        return float(self.value)\n```\n\nNow, you can properly convert:\n\n```python\n>>> a = MutInt(3)\n>>> int(a)\n3\n>>> float(a)\n3.0\n>>>\n```\n\nAs a general rule, Python never automatically converts data though. Thus, even though you\ngave the class an `__int__()` method, `MutInt` is still not going to work in all\nsituations when an integer might be expected.  For example, indexing:\n\n```python\n>>> names = ['Dave', 'Guido', 'Paula', 'Thomas', 'Lewis']\n>>> a = MutInt(1)\n>>> names[a]\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: list indices must be integers or slices, not MutInt\n>>> \n```\n\nThis can be fixed by giving `MutInt` an `__index__()` method that produces an integer.\nModify the class like this:\n\n```python\nfrom functools import total_ordering\n\n@total_ordering\nclass MutInt:\n    __slots__ = ['value']\n\n    def __init__(self, value):\n        self.value = value\n\n    ...\n\n    def __int__(self):\n        return self.value\n\n    __index__ = __int__     # Make indexing work\n```\n\n\n**Discussion**\n\nMaking a new primitive datatype is actually one of the most complicated\nprogramming tasks in Python.   There are a lot of edge cases and low-level\nissues to worry about--especially with regard to how your type interacts\nwith other Python types.  Probably the key thing to keep in mind is that\nyou can customize almost every aspect of how an object interacts with the\nrest of Python if you know the underlying protocols.   If you're going to\ndo this, it's advisable to look at the existing code for something similar\nto what you're trying to make.\n\n\\[ [Solution](soln2_4.md) | [Index](index.md) | [Exercise 2.3](ex2_3.md) | [Exercise 2.5](ex2_5.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex2_5.md",
    "content": "\\[ [Index](index.md) | [Exercise 2.4](ex2_4.md) | [Exercise 2.6](ex2_6.md) \\]\n\n# Exercise 2.5\n\n*Objectives:*\n\n- Look at memory allocation behavior of lists and dicts\n- Make a custom container\n\n*Files Created:* None\n\n## (a) List growth\n\nPython lists are highly optimized for performing `append()`\noperations. Each time a list grows, it grabs a larger chunk of memory\nthan it actually needs with the expectation that more data will be\nadded to the list later.  If new items are added and space is\navailable, the `append()` operation stores the item without\nallocating more memory.\n\nExperiment with this feature of lists by using\nthe `sys.getsizeof()` function on a list and appending a few\nmore items.\n\n```python\n>>> import sys\n>>> items = []\n>>> sys.getsizeof(items)\n64\n>>> items.append(1)\n>>> sys.getsizeof(items)\n96\n>>> items.append(2)\n>>> sys.getsizeof(items)    # Notice how the size does not increase\n96\n>>> items.append(3)\n>>> sys.getsizeof(items)    # It still doesn't increase here\n96\n>>> items.append(4)\n>>> sys.getsizeof(items)    # Not yet.\n96\n>>> items.append(5)\n>>> sys.getsizeof(items)    # Notice the size has jumped\n128\n>>>\n```\n\nA list stores its items by reference.  So, the memory required for\neach item is a single memory address.  On a 64-bit machine, an address\nis typically 8 bytes.  However, if Python has been compiled for\n32-bits, it might be 4 bytes and the numbers for the above example\nwill be half of what's shown.\n\n## (b) Dictionary/Class Growth\n\nPython dictionaries (and classes) allow up to 5 values to be stored\nbefore their reserved memory doubles.  Investigate by making a dictionary\nand adding a few more values to it:\n\n```python\n>>> row = { 'route': '22', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354 }\n>>> sys.getsizeof(row)\n240\n>>> row['a'] = 1\n>>> sys.getsizeof(row)\n240\n>>> row['b'] = 2\n>>> sys.getsizeof(row)\n368\n>>> \n```\n\nDoes the memory go down if you delete the item you just added?\n\nFood for thought:  If you are creating large numbers of records,\nrepresenting each record as a dictionary might not be the most\nefficient approach--you could be paying a heavy price for the convenience\nof having a dictionary.   It might be better to consider the use of tuples,\nnamed tuples, or classes that define `__slots__`.\n\n## (c) Changing Your Orientation (to Columns)\n\nYou can often save a lot of memory if you change your view of data.\nFor example, what happens if you read all of the bus data into a\ncolumns using this function?\n\n```python\n# readrides.py\n\n...\n\ndef read_rides_as_columns(filename):\n    '''\n    Read the bus ride data into 4 lists, representing columns\n    '''\n    routes = []\n    dates = []\n    daytypes = []\n    numrides = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            routes.append(row[0])\n            dates.append(row[1])\n            daytypes.append(row[2])\n            numrides.append(int(row[3]))\n    return dict(routes=routes, dates=dates, daytypes=daytypes, numrides=numrides)\n```\n\nIn theory, this function should save a lot of memory.  Let's analyze it before trying it.\n\nFirst, the datafile contained 577563 rows of data where each row contained\nfour values. If each row is stored as a dictionary, then those dictionaries\nare minimally 240 bytes in size.\n\n```python\n>>> nrows = 577563     # Number of rows in original file\n>>> nrows * 240\n138615120\n>>>\n```\n\nSo, that's 138MB just for the dictionaries themselves.  This does not\ninclude any of the values actually stored in the dictionaries.\n\nBy switching to columns, the data is stored in 4 separate lists.  \nEach list requires 8 bytes per item to store a pointer.  So, here's\na rough estimate of the list requirements:\n\n```python\n>>> nrows * 4 * 8\n18482016\n>>>\n```\n\nThat's about 18MB in list overhead.  So, switching to a column orientation\nshould save about 120MB of memory solely from eliminating all of the extra information that\nneeds to be stored in dictionaries. \n\nTry using this function to read the bus data and look at the memory use.\n\n```python\n>>> import tracemalloc\n>>> tracemalloc.start()\n>>> columns = read_rides_as_columns('Data/ctabus.csv')\n>>> tracemalloc.get_traced_memory()\n... look at the result ...\n>>>\n```\n\nDoes the result reflect the expected savings in memory from our rough calculations above?\n\n## (d) Making a Custom Container - The Great Fake Out\n\nStoring the data in columns offers a much better memory savings, but\nthe data is now rather weird to work with.  In fact, none of our\nearlier analysis code from [Exercise 2.2](ex2_2.md) can work\nwith columns. The reason everything is broken is that you've broken\nthe data abstraction that was used in earlier exercises--namely the\nassumption that data is stored as a list of dictionaries.\n\nThis can be fixed if you're willing to make a custom container object\nthat \"fakes\" it. Let's do that.\n\nThe earlier analysis code assumes the data is stored in a sequence of\nrecords.  Each record is represented as a dictionary.  Let's start\nby making a new \"Sequence\" class.  In this class, we store the\nfour columns of data that were being using in the `read_rides_as_columns()`\nfunction.\n\n```python\n# readrides.py\n\nimport collections\n...\nclass RideData(collections.abc.Sequence):\n    def __init__(self):\n        self.routes = []      # Columns\n        self.dates = []\n        self.daytypes = []\n        self.numrides = []\n```\n\nTry creating a `RideData` instance.  You'll find that it fails with an \nerror message like this:\n\n```python\n>>> records = RideData()\nTraceback (most recent call last):\n...\nTypeError: Can't instantiate abstract class RideData with abstract methods __getitem__, __len__\n>>>\n```\n\nCarefully read the error message. It tells us what we need to\nimplement. Let's add a `__len__()` and `__getitem__()` method.  In the\n`__getitem__()` method, we'll make a dictionary.  In addition, we'll\ncreate an `append()` method that takes a dictionary and unpacks it\ninto 4 separate `append()` operations.\n\n```python\n# readrides.py\n...\n\nclass RideData(collections.abc.Sequence):\n    def __init__(self):\n        # Each value is a list with all the values (a column)\n        self.routes = []\n        self.dates = []\n        self.daytypes = []\n        self.numrides = []\n        \n    def __len__(self):\n        # All lists assumed to have the same length\n        return len(self.routes)\n\n    def __getitem__(self, index):\n        return { 'route': self.routes[index],\n                 'date': self.dates[index],\n                 'daytype': self.daytypes[index],\n                 'rides': self.numrides[index] }\n\n    def append(self, d):\n        self.routes.append(d['route'])\n        self.dates.append(d['date'])\n        self.daytypes.append(d['daytype'])\n        self.numrides.append(d['rides'])\n```\n\nIf you've done this correctly, you should be able to drop this object into\nthe previously written `read_rides_as_dicts()` function.  It involves\nchanging only one line of code:\n\n```python\n# readrides.py\n...\n\ndef read_rides_as_dicts(filename):\n    '''\n    Read the bus ride data as a list of dicts\n    '''\n    records = RideData()      # <--- CHANGE THIS\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = {\n                'route': route, \n                'date': date, \n                'daytype': daytype, \n                'rides' : rides\n                }\n            records.append(record)\n    return records\n```\n\nIf you've done this right, old code should work exactly as it did before.\nFor example:\n\n```python\n>>> rows = readrides.read_rides_as_dicts('Data/ctabus.csv')\n>>> rows\n<readrides.RideData object at 0x10f5054a8>\n>>> len(rows)\n577563\n>>> rows[0]\n{'route': '3', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354}\n>>> rows[1]\n{'route': '4', 'date': '01/01/2001', 'daytype': 'U', 'rides': 9288}\n>>> rows[2]\n{'route': '6', 'date': '01/01/2001', 'daytype': 'U', 'rides': 6048}\n>>> \n```\n\nRun your earlier CTA code from [Exercise 2.2](ex2_2.md).  It\nshould work without modification, but use substantially less memory.\n\n## (e) Challenge\n\nWhat happens when you take a slice of ride data?\n\n```python\n>>> r = rows[0:10]\n>>> r\n... look at result ...\n>>>\n```\n\nIt's probably going to look a little crazy.  Can you modify \nthe `RideData` class so that it produces a proper slice that\nlooks like a list of dictionaries? For example, like this:\n\n```python\n>>> rows = readrides.read_rides_as_dicts('Data/ctabus.csv')\n>>> rows\n<readrides.RideData object at 0x10f5054a8>\n>>> len(rows)\n577563\n>>> r = rows[0:10]\n>>> r\n<readrides.RideData object at 0x10f5068c8>\n>>> len(r)\n10\n>>> r[0]\n{'route': '3', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354}\n>>> r[1]\n{'route': '4', 'date': '01/01/2001', 'daytype': 'U', 'rides': 9288}\n>>>\n```\n\n\\[ [Solution](soln2_5.md) | [Index](index.md) | [Exercise 2.4](ex2_4.md) | [Exercise 2.6](ex2_6.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex2_6.md",
    "content": "\\[ [Index](index.md) | [Exercise 2.5](ex2_5.md) | [Exercise 3.1](ex3_1.md) \\]\n\n# Exercise 2.6\n\n*Objectives:*\n\n- Explore the power of having first-class objects.\n- Better understand Python's memory model\n\n*Files Created:* `reader.py`\n\nIn previous exercises, you wrote various functions for reading CSV data in different\nfiles.  Surely this is a problem that could be generalized in some way.  In this\nexercise, we explore that idea.\n\n## (a) First-class Data\n\nIn the file `Data/portfolio.csv`, you read data organized as columns\nthat look like this:\n\n```python\n\"AA\",100,32.20\n\"IBM\",50,91.10\n...\n```\n\nIn previous code, this data was processed by hard-coding all of the\ntype conversions.  For example:\n\n```python\nrows = csv.reader(f)\nfor row in rows:\n    name   = row[0]\n    shares = int(row[1])\n    price  = float(row[2])\n```\n\nThis kind of conversion can also be performed in a more clever manner\nusing some list operations. Make a Python list that contains the\nconversions you want to carry out on each column:\n\n```python\n>>> coltypes = [str, int, float]\n>>>\n```\n\nThe reason you can even create this list is that everything in Python\nis \"first-class.\"  So, if you want to have a list of functions, that's\nfine. \n\nNow, read a row of data from the above file:\n\n```python\n>>> import csv\n>>> f = open('Data/portfolio.csv')\n>>> rows = csv.reader(f)\n>>> headers = next(rows)\n>>> row = next(rows)\n>>> row\n['AA', '100', '32.20']\n>>>\n```\n\nZip the column types with the row and look at the result:\n\n```python\n>>> r = list(zip(coltypes, row))\n>>> r\n[(<class 'str'>, 'AA'), (<class 'int'>, '100'), (<class 'float'>,'32.20')]\n>>>\n```\n\nYou will notice that this has paired a type conversion with a value.  For example, `int` is\npaired with the value `'100'`.   Now, try this:\n\n```python\n>>> record = [func(val) for func, val in zip(coltypes, row)]\n>>> record\n['AA', 100, 32.2]\n>>>\n```\n\nMake sure you understand what's happening in the above code.  In the\nloop, the `func` variable is one of the type conversion functions\n(e.g., `str`, `int`, etc.) and the `val` variable is one of the values\nlike `'AA'`, `'100'`.  The expression `func(val)` is converting\na value (kind of like a type cast).\n\nYou can take it a step further and make dictionaries by using the column\nheaders.   For example:\n\n```python\n>>> dict(zip(headers, record))\n{'name': 'AA', 'shares': 100, 'price': 32.2}\n>>>\n```\n\nIf you prefer, you can perform all of these steps at once using a\ndictionary comprehension:\n\n```python\n>>> { name:func(val) for name, func, val in zip(headers, coltypes, row) }\n{'name': 'AA', 'shares': 100, 'price': 32.2}\n>>>\n```\n\n## (b) Making a Parsing Utility Function\n\nCreate a new file `reader.py`.  In that file, define a\nutility function `read_csv_as_dicts()` that reads a file of CSV\ndata into a list of dictionaries where the user specifies\nthe type conversions for each column.\n\nHere is how it should work:\n\n```python\n>>> import reader\n>>> portfolio = reader.read_csv_as_dicts('Data/portfolio.csv', [str,int,float])\n>>> for s in portfolio:\n         print(s)\n\n{'name': 'AA', 'shares': 100, 'price': 32.2}\n{'name': 'IBM', 'shares': 50, 'price': 91.1}\n{'name': 'CAT', 'shares': 150, 'price': 83.44}\n{'name': 'MSFT', 'shares': 200, 'price': 51.23}\n{'name': 'GE', 'shares': 95, 'price': 40.37}\n{'name': 'MSFT', 'shares': 50, 'price': 65.1}\n{'name': 'IBM', 'shares': 100, 'price': 70.44}\n>>>\n```\n\nOr, if you wanted to read the CTA data:\n\n```python\n>>> rows = reader.read_csv_as_dicts('Data/ctabus.csv', [str,str,str,int])\n>>> len(rows)\n577563\n>>> rows[0]\n{'daytype': 'U', 'route': '3', 'rides': 7354, 'date': '01/01/2001'}\n>>> \n```\n\n## (c) Memory Revisited\n\nIn [Exercise 2.1](ex2_1.md) we explored memory use and in [Exercise 2.2](ex2_2.md), we\ndetermined that there were 181 unique bus routes in Chicago.\n\n```python\n>>> routes = { row['route'] for row in rows }\n>>> len(routes)\n181\n>>>\n```\n\nQuestion:  How many unique route string objects are contained in the ride data?\nInstead of building a set of routes, build a set of object ids instead:\n\n```python\n>>> routeids = { id(row['route']) for row in rows }\n>>> len(routeids)\n542305\n>>>\n```\n\nThink about this for a moment--there are only 181 distinct route\nnames, but the resulting list of dictionaries contains 542305\ndifferent route strings.  Maybe this is something that could be fixed\nwith a bit of caching or object reuse.  As it turns out, Python has\na function that can be used to cache strings, `sys.intern()`.  For example:\n\n```python\n>>> a = 'hello world'\n>>> b = 'hello world'\n>>> a is b\nFalse\n>>> import sys\n>>> a = sys.intern(a)\n>>> b = sys.intern(b)\n>>> a is b\nTrue\n>>>\n```\n\nRestart Python and try this:\n\n```python\n>>> # ------------------ RESTART ---------```\n>>> import tracemalloc\n>>> tracemalloc.start()\n>>> from sys import intern\n>>> import reader\n>>> rows = reader.read_csv_as_dicts('Data/ctabus.csv', [intern, str, str, int])\n>>> routeids = { id(row['route']) for row in rows }\n>>> len(routeids)\n181\n>>>\n```\n\nTake a look at the memory use.  \n\n```python\n>>> tracemalloc.get_traced_memory()\n... look at result ...\n>>> \n```\n\nThe memory should drop quite a bit.  Observation:  There is a lot of repetition \ninvolving dates as well.   What happens if you also cache the date strings?\n\n```python\n>>> # ------------------ RESTART ---------```\n>>> import tracemalloc\n>>> tracemalloc.start()\n>>> from sys import intern\n>>> import reader\n>>> rows = reader.read_csv_as_dicts('Data/ctabus.csv', [intern, intern, str, int])\n>>> tracemalloc.get_traced_memory()\n... look at result ...\n>>> \n```\n\n## (d) Special Challenge Project\n\nIn [Exercise 2.5](ex2_5.md), we created a class `RideData` that\nstored all of the bus data in columns, but actually presented the data\nto a user as a sequence of dictionaries.  It saved a lot of memory\nthrough various forms of magic.\n\nCan you generalize that idea?   Specifically, can you make a general\npurpose function `read_csv_as_columns()` that works like this:\n\n```python\n>>> data = read_csv_as_columns('Data/ctabus.csv', types=[str, str, str, int])\n>>> data\n<__main__.DataCollection object at 0x102b45048>\n>>> len(data)\n577563\n>>> data[0]\n{'route': '3', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354}\n>>> data[1]\n{'route': '4', 'date': '01/01/2001', 'daytype': 'U', 'rides': 9288}\n>>> data[2]\n{'route': '6', 'date': '01/01/2001', 'daytype': 'U', 'rides': 6048}\n>>> \n```\n\nThis function is supposed to be general purpose--you can give it any file and\na list of the column types and it will read the data.  The data is read into\na class `DataCollection` that stores the data as columns internally.  The data\npresents itself as a sequence of dictionaries when accessed however.\n\nTry using this function with the string interning trick in the last part. How\nmuch memory does it take to store all of the ride data now?   Can you still use\nthis function with your earlier CTA analysis code?\n\n## (e) Deep Thought\n\nIn this exercise, you have written two functions, `read_csv_as_dicts()` and\n`read_csv_as_columns()`.  These functions present data to the user in the same way.\nFor example:\n\n```python\n>>> data1 = read_csv_as_dicts('Data/ctabus.csv', [str, str, str, int])\n>>> len(data1)\n577563\n>>> data1[0]\n{'route': '3', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354}\n>>> data1[1]\n{'route': '4', 'date': '01/01/2001', 'daytype': 'U', 'rides': 9288}\n>>>\n\n>>> data2 = read_csv_as_columns('Data/ctabus.csv', [str, str, str, int])\n>>> len(data2)\n577563\n>>> data2[0]\n{'route': '3', 'date': '01/01/2001', 'daytype': 'U', 'rides': 7354}\n>>> data2[1]\n{'route': '4', 'date': '01/01/2001', 'daytype': 'U', 'rides': 9288}\n>>>\n```\n\nIn fact, you can use either function in the CTA data analysis code\nthat you wrote.  Yet, under the covers completely different things are\nhappening.  The `read_csv_as_columns()` function is storing the data\nin a different representation.  It's relying on Python sequence\nprotocols (magic methods) to present information to you in a more useful\nway.\n\nThis is really part of a much bigger programming concept of \"Data\nAbstraction\".  When writing programs, the way in which data is\npresented is often more important than how the data is actually put\ntogether under the hood.  Although we're presenting the data as a \nsequence of dictionaries, there's a great deal of flexibility in \nhow that actually happens behind the scenes.  That's a powerful\nidea and something to think about when writing your own programs.\n\n\\[ [Solution](soln2_6.md) | [Index](index.md) | [Exercise 2.5](ex2_5.md) | [Exercise 3.1](ex3_1.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex3_1.md",
    "content": "\\[ [Index](index.md) | [Exercise 2.6](ex2_6.md) | [Exercise 3.2](ex3_2.md) \\]\n\n# Exercise 3.1\n\n*Objectives:*\n\n- Define a simple class\n\n*Files Modified:* `stock.py`\n\nIn [Exercise 1.5](ex1_5.md), you defined a simple class\n`Stock` for representing a holding of stock.  In this exercise,\nwe're simply going to add a few features to that class as well as\nwrite some utility functions.\n\n## (a) Adding a new method\n\nAdd a new method `sell(nshares)` to Stock that sells a certain number\nof shares by decrementing the share count.  Have it work like this:\n\n```python\n>>> s = Stock('GOOG',100,490.10)\n>>> s.shares\n100\n>>> s.sell(25)\n>>> s.shares\n75\n>>>\n```\n\n## (b) Reading a portfolio\n\nAdd a function `read_portfolio()` to your `stock.py` program that\nreads a file of portfolio data into a list of `Stock` objects. Here's how it should work:\n\n```python\n>>> portfolio = read_portfolio('Data/portfolio.csv')\n>>> for s in portfolio:\n        print(s)\n\n<__main__.Stock object at 0x3902f0>\n<__main__.Stock object at 0x390270>\n<__main__.Stock object at 0x390330>\n<__main__.Stock object at 0x390370>\n<__main__.Stock object at 0x3903b0>\n<__main__.Stock object at 0x3903f0>\n<__main__.Stock object at 0x390430>\n>>>\n```\n\nYou already wrote a similar function as part of\n[Exercise 2.3](ex2_3.md).  Design discussion: Should\n`read_portfolio()` be a separate function or part of the class\ndefinition?\n\n## (c) Printing a Table\n\nTake the data read in part (b) and use it to make a nicely formatted\ntable. For example:\n\n```python\n>>> portfolio = read_portfolio('Data/portfolio.csv')\n>>> for s in portfolio:\n           print('%10s %10d %10.2f' % (s.name, s.shares, s.price))\n\n        AA        100      32.20\n       IBM         50      91.10\n       CAT        150      83.44\n      MSFT        200      51.23\n        GE         95      40.37\n      MSFT         50      65.10\n       IBM        100      70.44\n>>>\n```\n\nTake this code and put it in a function `print_portfolio()` that \nproduces the same output, but additionally adds some table headers.\nFor example:\n\n```python\n>>> portfolio = read_portfolio('Data/portfolio.csv')\n>>> print_portfolio(portfolio)\n      name     shares      price\n---------- ---------- ---------- \n        AA        100      32.20\n       IBM         50      91.10\n       CAT        150      83.44\n      MSFT        200      51.23\n        GE         95      40.37\n      MSFT         50      65.10\n       IBM        100      70.44\n>>>\n```\n\n\\[ [Solution](soln3_1.md) | [Index](index.md) | [Exercise 2.6](ex2_6.md) | [Exercise 3.2](ex3_2.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex3_2.md",
    "content": "\\[ [Index](index.md) | [Exercise 3.1](ex3_1.md) | [Exercise 3.3](ex3_3.md) \\]\n\n# Exercise 3.2\n\n*Objectives:*\n\n- Learn about attribute access\n- Learn how use getattr(), setattr(), and related functions.\n- Experiment with bound methods.\n\n\n*Files Created:* `tableformat.py`\n\n## (a) The Three Operations\n\nThe entire Python object system consists of just three core operations: getting, setting, and deleting\nof attributes.  Normally, these are accessed via the dot (.) like this:\n\n```python\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s.name    #  get\n'GOOG'\n>>> s.shares = 50    # set\n>>> del s.shares     # delete\n>>>\n```\n\nThe three operations are also available as functions.  For example:\n\n```python\n>>> getattr(s, 'name')            # Same as s.name\n'GOOG'\n>>> setattr(s, 'shares', 50)      # Same as s.shares = 50\n>>> delattr(s, 'shares')          # Same as del s.shares\n>>>\n```\n\nAn additional function `hasattr()` can be used to probe an object for the existence of an attribute:\n\n```python\n>>> hasattr(s, 'name')\nTrue\n>>> hasattr(s, 'blah')\nFalse\n>>>\n```\n\n## (b) Using getattr()\n\nThe `getattr()` function is extremely useful for\nwriting code that processes objects in an extremely generic way.  To\nillustrate, consider this example which prints out a set of\nuser-defined attributes:\n\n```python\n>>> s= Stock('GOOG', 100, 490.1)\n>>> fields = ['name','shares','price']\n>>> for name in fields:\n           print(name, getattr(s, name))\n\nname GOOG\nshares 100\nprice 490.1\n>>>\n```\n\n## (c) Table Output\n\nIn [Exercise 3.1](ex3_1.md), you wrote a function `print_portfolio()`\nthat made a nicely formatted table.   That function was custom tailored\nto a list of `Stock` objects.  However, it can be completely generalized\nto work with any list of objects using the technique in part (b).\n\nCreate a new module called `tableformat.py`.  In that program,\nwrite a function `print_table()` that takes a sequence (list) of objects,\na list of attribute names, and prints a nicely formatted table. For example:\n\n```python\n>>> import stock\n>>> import tableformat\n>>> portfolio = stock.read_portfolio('Data/portfolio.csv')\n>>> tableformat.print_table(portfolio, ['name','shares','price'])\n      name     shares      price\n---------- ---------- ---------- \n        AA        100       32.2\n       IBM         50       91.1\n       CAT        150      83.44\n      MSFT        200      51.23\n        GE         95      40.37\n      MSFT         50       65.1\n       IBM        100      70.44\n\n>>> tableformat.print_table(portfolio,['shares','name'])\n    shares       name\n---------- ---------- \n       100         AA\n        50        IBM\n       150        CAT\n       200       MSFT\n        95         GE\n        50       MSFT\n       100        IBM\n>>> \n```\n\nFor simplicity, just have the `print_table()` function print each field in\na 10-character wide column.\n\n## (d) Bound Methods\n\nIt may be surprising, but method calls are layered onto the machinery used\nfor simple attributes.  Essentially, a method is an attribute that \nexecutes when you add the required parentheses () to call it like a function.  For\nexample:\n\n```python\n>>> s = Stock('GOOG',100,490.10)\n>>> s.cost           # Looks up the method\n<bound method Stock.cost of <__main__.Stock object at 0x409530>>\n>>> s.cost()         # Looks up and calls the method\n49010.0\n\n>>> # Same operations using getattr()\n>>> getattr(s, 'cost')\n<bound method Stock.cost of <__main__.Stock object at 0x409530>>\n>>> getattr(s, 'cost')()\n49010.0\n>>> \n```\n\nA bound method is attached to the object where it came from.   If that\nobject is modified, the method will see the modifications.  You can\nview the original object by inspecting the `__self__` attribute\nof the method.\n\n```python\n>>> c = s.cost\n>>> c()\n49010.0\n>>> s.shares = 75\n>>> c()\n36757.5\n>>> c.__self__\n<__main__.Stock object at 0x409530>\n>>> c.__func__\n<function cost at 0x37cc30>\n>>> c.__func__(c.__self__)      # This is what happens behind the scenes of calling c()\n36757.5\n>>>\n```\n\nTry it with the `sell()` method just to make sure you\nunderstand the mechanics:\n\n```python\n>>> f = s.sell\n>>> f.__func__(f.__self__, 25)     # Same as s.sell(25)\n>>> s.shares\n50\n>>>\n```\n\n\\[ [Solution](soln3_2.md) | [Index](index.md) | [Exercise 3.1](ex3_1.md) | [Exercise 3.3](ex3_3.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex3_3.md",
    "content": "\\[ [Index](index.md) | [Exercise 3.2](ex3_2.md) | [Exercise 3.4](ex3_4.md) \\]\n\n# Exercise 3.3\n\n*Objectives:*\n\n- Learn about class variables and class methods\n\n*Files Modified:* `stock.py`, `reader.py`\n\nInstances of the `Stock` class defined in the previous exercise are\nnormally created as follows:\n\n```python\n>>> s = Stock('GOOG', 100, 490.1)\n>>>\n```\n\nHowever, the `read_portfolio()` function also creates instances from rows\nof data read from files.  For example, code such as the following is used:\n\n```python\n>>> import csv\n>>> f = open('Data/portfolio.csv')\n>>> rows = csv.reader(f)\n>>> headers = next(rows)\n>>> row = next(rows)\n>>> row\n['AA', '100', '32.20']\n>>> s = Stock(row[0], int(row[1]), float(row[2]))\n>>>\n```\n\n## (a) Alternate constructors\n\nPerhaps the creation of a `Stock` from a row of raw data is better handled\nby an alternate constructor.   Modify the `Stock` class so that it has\na `types` class variable and `from_row()` class method like this:\n\n```python\nclass Stock:\n    types = (str, int, float)\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls.types, row)]\n        return cls(*values)\n    ...\n```\n\nHere's how to test it:\n\n```python\n>>> row = ['AA', '100', '32.20']\n>>> s = Stock.from_row(row)\n>>> s.name\n'AA'\n>>> s.shares\n100\n>>> s.price\n32.2\n>>> s.cost()\n3220.0000000000005\n>>>\n```\n\nCarefully observe how the string values in the row have been converted to a proper type.\n\n## (b) Class variables and inheritance\n\nClass variables such as `types` are sometimes used as a customization mechanism\nwhen inheritance is used.  For example, in the `Stock` class, the types can be\neasily changed in a subclass.  Try this example which changes the `price` attribute\nto a `Decimal` instance (which is often better suited to financial calculations):\n\n```python\n>>> from decimal import Decimal\n>>> class DStock(Stock):\n        types = (str, int, Decimal)\n\n>>> row = ['AA', '100', '32.20']\n>>> s = DStock.from_row(row)\n>>> s.price\nDecimal('32.20')\n>>> s.cost()\nDecimal('3220.0')\n>>>\n```\n\n**Design Discussion**\n\nThe problem being addressed in this exercise concerns the conversion of data read\nfrom a file.  Would it make sense to perform these conversions in the `__init__()` \nmethod of the `Stock` class instead?  For example:\n\n```python\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = str(name)\n        self.shares = int(shares)\n        self.price = float(price)\n```\n\nBy doing this, you would convert a row of data as follows:\n\n```python\n>>> row = ['AA', '100', '32.20']\n>>> s = Stock(*row)\n>>> s.name\n'AA'\n>>> s.shares\n100\n>>> s.price\n32.2\n>>>\n```\n\nIs this good or bad?  What are your thoughts?  On the whole, I think\nit's a questionable design since it mixes two different things\ntogether--the creation of an instance and the conversion of data.\nPlus, the implicit conversions in `__init__()` limit flexibility and\nmight introduce weird bugs if a user isn't paying careful attention.\n\n## (c) Reading Objects\n\nChange the `read_portfolio()` function to use the new `Stock.from_row()`\nmethod that you wrote. \n\nPoint of thought:  Which version of code do you like better?  The\nversion of `read_portfolio()` that performed the type conversions or\nthe one that performs conversions in the `Stock.from_row()` method?\nThink about data abstraction.\n\nCan you modify `read_portfolio()` to create objects using a class other\nthan `Stock`?  For example, can the user select the class that they want?  \n\n## (d) Generalizing\n\nA useful feature of class-methods is that you can use them to put a\nhighly uniform instance creation interface on a wide variety of\nclasses and write general purpose utility functions that use them.\n\nEarlier, you created a file `reader.py` that had some functions for\nreading CSV data.  Add the following `read_csv_as_instances()`\nfunction to the file which accepts a class as input and uses the class\n`from_row()` method to create a list of instances:\n\n```python\n# reader.py\n...\n\ndef read_csv_as_instances(filename, cls):\n    '''\n    Read a CSV file into a list of instances\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            records.append(cls.from_row(row))\n    return records\n```\n\nGet rid of the `read_portfolio()` function--you don't need that anymore.  If you want to\nread a list of `Stock` objects, do this:\n\n```python\n>>> # Read a portfolio of Stock instances\n>>> from reader import read_csv_as_instances\n>>> from stock import Stock\n>>> portfolio = read_csv_as_instances('Data/portfolio.csv', Stock)\n>>> portfolio\n[<__main__.Stock object at 0x100674748>, \n<__main__.Stock object at 0x1006746d8>, \n<__main__.Stock object at 0x1006747b8>, \n<__main__.Stock object at 0x100674828>, \n<__main__.Stock object at 0x100674898>, \n<__main__.Stock object at 0x100674908>, \n<__main__.Stock object at 0x100674978>]\n>>>\n```\n\nHere is another example of how you might use `read_csv_as_instances()` with a completely different class:\n\n```python\n>>> class Row:\n         def __init__(self, route, date, daytype, numrides):\n             self.route = route\n             self.date = date\n             self.daytype = daytype\n             self.numrides = numrides\n         @classmethod\n         def from_row(cls, row):\n             return cls(row[0], row[1], row[2], int(row[3]))\n\n>>> rides = read_csv_as_instances('Data/ctabus.csv', Row)\n>>> len(rides)\n577563\n>>>\n```\n\n**Discussion**\n\nThis exercise illustrates the two most common uses of class variables\nand class methods.  Class variables are often used to hold a global\nparameter (e.g., a configuration setting) that is shared across all\ninstances.  Sometimes subclasses will inherit from the base class and\noverride the setting to change behavior.\n\nClass methods are most commonly used to implement alternate\nconstructors as shown.  A common way to spot such class methods is to\nlook for the word \"from\" in the name.  For example, here is an example\non built-in dictionaries:\n\n```python\n>>> d = dict.fromkeys(['a','b','c'], 0)     # class method\n>>> d\n{'a': 0, 'c': 0, 'b': 0}\n>>>\n```\n\n\\[ [Solution](soln3_3.md) | [Index](index.md) | [Exercise 3.2](ex3_2.md) | [Exercise 3.4](ex3_4.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex3_4.md",
    "content": "\\[ [Index](index.md) | [Exercise 3.3](ex3_3.md) | [Exercise 3.5](ex3_5.md) \\]\n\n# Exercise 3.4\n\n*Objectives:*\n\n- Learn how to encapsulate object internals using private\n  attributes, properties, and slots\n\n*Files Modified:* `stock.py`\n\n## (a) Private attributes\n\nAs a general rule, attributes that are internal to a class should have a leading underscore.\nIn the previous exercise, the `Stock` class had a `types` class variable that was\nused for converting rows of data.  Change the code so that this variable has a leading\nunderscore on it.\n\n## (b) Properties for computed attributes\n\nEarlier, you defined a class `Stock`.  For example:\n\n```python\n>>> s = Stock('GOOG',100,490.10)\n>>> s.name\n'GOOG'\n>>> s.shares\n100\n>>> s.price\n490.1\n>>> s.cost()\n49010.0\n>>>\n```\n\nUsing a property, turn `cost()` into an attribute that no longer requires the parentheses.   For example:\n\n```python\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s.cost               # Property. Computes the cost\n49010.0\n>>> \n```\n\n## (c) Enforcing Validation Rules\n\nUsing properties and private attributes, modify the `shares` attribute\nof the `Stock` class so that it can only be assigned a non-negative\ninteger value.    In addition, modify the `price` attribute so that it\ncan only be assigned a non-negative floating point value.\n\nThe new object should work almost exactly the same as\nthe old one except for extra type and value checking.\n\n```python\n>>> s = Stock('GOOG', 100, 490.10)\n>>> s.shares = 50          # OK\n>>> s.shares = '50'\nTraceback (most recent call last):\n...\nTypeError: Expected integer\n>>> s.shares = -10\nTraceback (most recent call last):\n...\nValueError: shares must be >= 0\n\n>>> s.price = 123.45       # OK\n>>> s.price = '123.45'\nTraceback (most recent call last):\n...\nTypeError: Expected float\n>>> s.price = -10.0\nTraceback (most recent call last):\n...\nValueError: price must be >= 0\n>>>\n```\n\n## (d) Adding `__slots__`\n\nModify your new `Stock` class to use `__slots__`.  You will find that\nyou have to use a different set of attribute names than\nbefore--specifically, you will have to list the private attribute\nnames (e.g., if a property is storing a value in an attribute\n`_shares`, that is the name you list in `__slots__`).  Verify that the\nclass still works and that you can no longer add new attributes.\n\n```python\n>>> s = Stock('GOOG', 100, 490.10)\n>>> s.spam = 42\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nAttributeError: 'Stock' object has no attribute 'spam'\n>>>\n```\n\n## (e) Reconciling Types\n\nIn the current `Stock` class, there is a `_types` class variable\nthat gives conversions when reading from a file, but there are also\nproperties that are enforcing types.  Who is in charge of this show?\nFix the property definitions so that they use the types specified\nin the `_types` class variable.  Make sure the properties work\nwhen types are changed via subclassing.   For example:\n\n```python\n>>> from decimal import Decimal\n>>> class DStock(Stock):\n        _types = (str, int, Decimal)\n\n>>> s = DStock('AA', 50, Decimal('91.1'))\n>>> s.price = 92.3\nTraceback (most recent call last):\n...\nTypeError: Expected a Decimal\n>>> \n```\n\n**Discussion**\n\nThe resulting `Stock` class at the end of this exercise is a muddled\nmess of properties, type checking, constructors, and other details.\nImagine how unpleasant it would be to maintain code that featured\ndozens or hundreds of such class definitions.\n\nWe're going to figure out how to simplify things considerably, but it's\ngoing to take some time and some more advanced techniques. Stay tuned.\n\n\\[ [Solution](soln3_4.md) | [Index](index.md) | [Exercise 3.3](ex3_3.md) | [Exercise 3.5](ex3_5.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex3_5.md",
    "content": "\\[ [Index](index.md) | [Exercise 3.4](ex3_4.md) | [Exercise 3.6](ex3_6.md) \\]\n\n# Exercise 3.5\n\n*Objectives:*\n\n- Learn how to use inheritance to write extensible code.\n- See a practical use of inheritance by writing a program that must\noutput data in a variety of user-selectable formats such as plain-text,\nCSV, and HTML.\n\n*Files Modified:* `tableformat.py`\n\nOne major use of classes in Python is in writing code that be\nextended/adapted in various ways.  To illustrate, in\n[Exercise 3.2](ex3_2.md) you created a function `print_table()`\nthat made tables.  You used this to make output from the `portfolio`\nlist. For example:\n\n```python\n>>> import stock\n>>> import reader\n>>> import tableformat\n>>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)\n>>> tableformat.print_table(portfolio, ['name','shares','price'])\n      name     shares      price\n---------- ---------- ---------- \n        AA        100       32.2\n       IBM         50       91.1\n       CAT        150      83.44\n      MSFT        200      51.23\n        GE         95      40.37\n      MSFT         50       65.1\n       IBM        100      70.44\n>>> \n```\n\nSuppose you wanted the `print_table()` function to be able to\nmake tables in any number of output formats such as CSV, XML, HTML,\nExcel, etc.  Trying to modify the function to support all of those\noutput formats at once would be painful.  A better way to do this\ninvolves moving the output-related formatting code to a class and using\ninheritance to implement different output formats.\n\n## (a) Defining a generic formatter class\n\nAdd the following class definition to the `tableformat.py` file:\n\n```python\nclass TableFormatter:\n    def headings(self, headers):\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        raise NotImplementedError()\n```\n\nNow, modify the `print_table()` function so that it accepts a `TableFormatter` instance\nand invokes methods on it to produce output:\n\n```python\ndef print_table(records, fields, formatter):\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n```\n\nThese two classes are meant to be used together.  For example:\n\n```\n>>> import stock, reader, tableformat\n>>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)\n>>> formatter = tableformat.TableFormatter()\n>>> tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)\nTraceback (most recent call last):\n...\nNotImplementedError\n>>>\n```\n\nFor now, it doesn't do much of anything interesting.  You'll fix this in the next section.\n\n## (b) Implementing a concrete formatter\n\nThe `TableFormatter` isn't meant to be used by itself. Instead, it is merely a base \nfor other classes that will implement the formatting.  Add the following class to\n`tableformat.py`:\n\n```python\nclass TextTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(' '.join('%10s' % h for h in headers))\n        print(('-'*10 + ' ')*len(headers))\n    \n    def row(self, rowdata):\n        print(' '.join('%10s' % d for d in rowdata))\n```\n\nNow, use your new class as follows:\n\n```python\n>>> import stock, reader, tableformat\n>>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)\n>>> formatter = tableformat.TextTableFormatter()\n>>> tableformat.print_table(portfolio, ['name','shares','price'], formatter)\n     name     shares      price\n---------- ---------- ---------- \n        AA        100       32.2\n       IBM         50       91.1\n       CAT        150      83.44\n      MSFT        200      51.23\n        GE         95      40.37\n      MSFT         50       65.1\n       IBM        100      70.44\n>>> \n```\n\n## (c) Adding More Implementations\n\nCreate a class `CSVTableFormatter` that allows output to be generated in CSV format:\n\n```python\n>>> import stock, reader, tableformat\n>>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)\n>>> formatter = tableformat.CSVTableFormatter()\n>>> tableformat.print_table(portfolio, ['name','shares','price'], formatter)\nname,shares,price\nAA,100,32.2\nIBM,50,91.1\nCAT,150,83.44\nMSFT,200,51.23\nGE,95,40.37\nMSFT,50,65.1\nIBM,100,70.44\n>>>\n```\n\nCreate a class `HTMLTableFormatter` that generates output in HTML format:\n\n```python\n>>> import stock, reader, tableformat\n>>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)\n>>> formatter = tableformat.HTMLTableFormatter()\n>>> tableformat.print_table(portfolio, ['name','shares','price'], formatter)\n<tr> <th>name</th> <th>shares</th> <th>price</th> </tr>\n<tr> <td>AA</td> <td>100</td> <td>32.2</td> </tr>\n<tr> <td>IBM</td> <td>50</td> <td>91.1</td> </tr>\n<tr> <td>CAT</td> <td>150</td> <td>83.44</td> </tr>\n<tr> <td>MSFT</td> <td>200</td> <td>51.23</td> </tr>\n<tr> <td>GE</td> <td>95</td> <td>40.37</td> </tr>\n<tr> <td>MSFT</td> <td>50</td> <td>65.1</td> </tr>\n<tr> <td>IBM</td> <td>100</td> <td>70.44</td> </tr>\n>>>\n```\n\n## (d) Making it Easier To Choose\n\nOne problem with using inheritance is the added complexity of picking\ndifferent classes to use (e.g., remembering the names, using the right\n`import` statements, etc.).   A factory function can simplify this.  Add\na function `create_formatter()` to your `tableformat.py` file that \nallows a user to more easily make a formatter by specifying a format such as `'text'`, `'csv'`, or `'html'`.   For example:\n\n```python\n>>> from tableformat import create_formatter, print_table\n>>> formatter = create_formatter('html')\n>>> print_table(portfolio, ['name','shares','price'], formatter)\n<tr> <th>name</th> <th>shares</th> <th>price</th> </tr>\n<tr> <td>AA</td> <td>100</td> <td>32.2</td> </tr>\n<tr> <td>IBM</td> <td>50</td> <td>91.1</td> </tr>\n<tr> <td>CAT</td> <td>150</td> <td>83.44</td> </tr>\n<tr> <td>MSFT</td> <td>200</td> <td>51.23</td> </tr>\n<tr> <td>GE</td> <td>95</td> <td>40.37</td> </tr>\n<tr> <td>MSFT</td> <td>50</td> <td>65.1</td> </tr>\n<tr> <td>IBM</td> <td>100</td> <td>70.44</td> </tr>\n>>>\n```\n\n**Discussion**\n\nThe `TableFormatter` class in this exercise is an example of something known\nas an \"Abstract Base Class.\"   It's not something that's meant to be used directly.\nInstead, it's serving as a kind of interface specification for a program component--in\nthis case the various output formats.   Essentially, the code that produces the table\nwill be programmed against the abstract base class with the expectation that a user\nwill provide a suitable implementation.  As long as all of the required methods\nhave been implemented, it should all just \"work\" (fingers crossed).\n\n\\[ [Solution](soln3_5.md) | [Index](index.md) | [Exercise 3.4](ex3_4.md) | [Exercise 3.6](ex3_6.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex3_6.md",
    "content": "\\[ [Index](index.md) | [Exercise 3.5](ex3_5.md) | [Exercise 3.7](ex3_7.md) \\]\n\n# Exercise 3.6\n\n*Objectives:*\n\n- Learn how to customize the behavior of objects by redefining special methods.\n- Change the way that user-defined objects get printed\n- Make objects comparable\n- Create a context manager\n\n*Files Created:* None\n\n*Files Modified:* `stock.py`\n\n## (a) Better output for representing objects\n\nAll Python objects have two string representations.  The first\nrepresentation is created by string conversion via `str()`\n(which is called by `print`).   The string representation is\nusually a nicely formatted version of the object meant for humans.\nThe second representation is a code representation of the object\ncreated by `repr()` (or simply by viewing a value in the\ninteractive shell).   The code representation typically shows you the\ncode that you have to type to get the object.  Here is an example\nthat illustrates using dates:\n\n```python\n>>> from datetime import date\n>>> d = date(2008, 7, 5)\n>>> print(d)              # uses str()\n2008-07-05\n>>> d    # uses repr()\ndatetime.date(2008, 7, 5)\n>>>\n```\n\nThere are several techniques for obtaining the `repr()` string\nin output:\n\n```python\n>>> print('The date is', repr(d))\nThe date is datetime.date(2008, 7, 5)\n>>> print(f'The date is {d!r}')\nThe date is datetime.date(2008, 7, 5)\n>>> print('The date is %r' % d)\nThe date is datetime.date(2008, 7, 5)\n>>> \n```\n\nModify the `Stock` object that you've created so that\nthe `__repr__()` method\nproduces more useful output.  For example:\n\n```python\n>>> goog = Stock('GOOG', 100, 490.10)\n>>> goog\nStock('GOOG', 100, 490.1)\n>>>\n```\n\nSee what happens when you read a portfolio of stocks and view the\nresulting list after you have made these changes.  For example:\n\n```python\n>>> import stock, reader\n>>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)\n>>> portfolio\n[Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44), Stock('MSFT', 200, 51.23), \n Stock('GE', 95, 40.37), Stock('MSFT', 50, 65.1), Stock('IBM', 100, 70.44)]\n>>>\n```\n\n## (b) Making objects comparable\n\nWhat happens if you create two identical `Stock` objects and try to compare them? Find out:\n\n```python\n>>> a = Stock('GOOG', 100, 490.1)\n>>> b = Stock('GOOG', 100, 490.1)\n>>> a == b\nFalse\n>>>\n```\n\nYou can fix this by giving the `Stock` class an `__eq__()` method.  For example:\n\n```python\nclass Stock:\n    ...\n    def __eq__(self, other):\n        return isinstance(other, Stock) and ((self.name, self.shares, self.price) == \n                                             (other.name, other.shares, other.price))\n    ...\n```\n\nMake this change and try comparing two objects again. \n\n## (c) A Context Manager\n\nIn [Exercise 3.5](ex3_5.md), you made it possible for users to make\nnicely formatted tables.  For example:\n\n```python\n>>> from tableformat import create_formatter\n>>> formatter = create_formatter('text')\n>>> print_table(portfolio, ['name','shares','price'], formatter)\n      name     shares      price\n---------- ---------- ---------- \n        AA        100       32.2\n       IBM         50       91.1\n       CAT        150      83.44\n      MSFT        200      51.23\n        GE         95      40.37\n      MSFT         50       65.1\n       IBM        100      70.44\n>>>\n```\n\nOne issue with the code is that all tables are printed to standard out\n(`sys.stdout`).  Suppose you wanted to redirect the output to a file\nor some other location.  In the big picture, you might modify all of\nthe table formatting code to allow a different output file.  However,\nin a pinch, you could also solve this with a context manager.\n\nDefine the following context manager:\n\n```python\n>>> import sys\n>>> class redirect_stdout:\n        def __init__(self, out_file):\n            self.out_file = out_file\n        def __enter__(self):\n            self.stdout = sys.stdout\n            sys.stdout = self.out_file\n            return self.out_file\n        def __exit__(self, ty, val, tb):\n            sys.stdout = self.stdout\n```\n\nThis context manager works by making a temporary patch to `sys.stdout` to cause\nall output to redirect to a different file.  On exit, the patch is reverted. \nTry it out:\n\n```python\n>>> from tableformat import create_formatter\n>>> formatter = create_formatter('text')\n>>> with redirect_stdout(open('out.txt', 'w')) as file:\n        tableformat.print_table(portfolio, ['name','shares','price'], formatter)\n        file.close()\n\n>>> # Inspect the file\n>>> print(open('out.txt').read())\n      name     shares      price\n---------- ---------- ---------- \n        AA        100       32.2\n       IBM         50       91.1\n       CAT        150      83.44\n      MSFT        200      51.23\n        GE         95      40.37\n      MSFT         50       65.1\n       IBM        100      70.44\n>>>\n```\n\n\\[ [Solution](soln3_6.md) | [Index](index.md) | [Exercise 3.5](ex3_5.md) | [Exercise 3.7](ex3_7.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex3_7.md",
    "content": "\\[ [Index](index.md) | [Exercise 3.6](ex3_6.md) | [Exercise 3.8](ex3_8.md) \\]\n\n# Exercise 3.7\n\n*Objectives:*\n\n- Type checking and interfaces\n- Abstract base classes\n\n*Files Modified:* `tableformat.py`\n\nIn [Exercise 3.5](ex3_5.md), we modified the `tableformat.py` file to have a `TableFormatter`\nclass and to use various subclasses for different output formats.  In this exercise, we extend that\ncode a bit more.\n\n## (a) Interfaces and type checking\n\nModify the `print_table()` function  so that it checks if the\nsupplied formatter instance inherits from `TableFormatter`.  If\nnot, raise a `TypeError`.\n\nYour new code should catch situations like this:\n\n```python\n>>> import stock, reader, tableformat\n>>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)\n>>> class MyFormatter:\n        def headings(self,headers): pass\n        def row(self,rowdata): pass\n\n>>> tableformat.print_table(portfolio, ['name','shares','price'], MyFormatter())\nTraceback (most recent call last):\n...\nTypeError: Expected a TableFormatter\n>>> \n```\n\nAdding a check like this might add some degree of safety to the program.   However you should\nstill be aware that type-checking is rather weak in Python.  There is no guarantee that the\nobject passed as a formatter will work correctly even if it happens to inherit from the\nproper base class.  This next part addresses that issue.\n\n## (b) Abstract Base Classes\n\nModify the `TableFormatter` base class so that it is defined as a proper\nabstract base class using the `abc` module.   Once you have done that, try\nthis experiment:\n\n```python\n>>> class NewFormatter(TableFormatter):\n        def headers(self, headings):\n            pass\n        def row(self, rowdata):\n            pass\n\n>>> f = NewFormatter()\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: Can't instantiate abstract class NewFormatter with abstract methods headings\n>>> \n```\n\nHere, the abstract base class caught a spelling error in the class--the fact that\nthe `headings()` method was incorrectly given as `headers()`.\n\n## (c) Algorithm Template Classes\n\nThe file `reader.py` contains two functions, `read_csv_as_dicts()` and `read_csv_as_instances()`.\nBoth of those functions are almost identical--there is just one tiny bit of code that's\ndifferent.  Maybe that code could be consolidated into a class definition of some sort.\nAdd the following class to the `reader.py` file:\n\n```python\n# reader.py\n\n\nimport csv\nfrom abc import ABC, abstractmethod\n\nclass CSVParser(ABC):\n\n    def parse(self, filename):\n        records = []\n        with open(filename) as f:\n            rows = csv.reader(f)\n            headers = next(rows)\n            for row in rows:\n                record = self.make_record(headers, row)\n                records.append(record)\n        return records\n\n    @abstractmethod\n    def make_record(self, headers, row):\n        pass\n```\n\nThis code provides a shell (or template) of the CSV parsing functionality.   To use it, you subclass it, add\nany additional attributes you might need, and redefine the `make_record()` method. For example:\n\n```python\nclass DictCSVParser(CSVParser):\n    def __init__(self, types):\n        self.types = types\n\n    def make_record(self, headers, row):\n        return { name: func(val) for name, func, val in zip(headers, self.types, row) }\n\nclass InstanceCSVParser(CSVParser):\n    def __init__(self, cls):\n        self.cls = cls\n\n    def make_record(self, headers, row):\n        return self.cls.from_row(row)\n```\n\nAdd the above classes to the `reader.py` file.  Here's how you would use one of them:\n\n```python\n>>> from reader import DictCSVParser\n>>> parser = DictCSVParser([str, int, float])\n>>> port = parser.parse('Data/portfolio.csv')\n>>>\n```\n\nIt works, but it's kind of annoying.  To fix this, reimplement the `read_csv_as_dicts()` and\n`read_csv_as_instances()` functions to use these classes.   Your refactored code should work\nexactly the same way that it did before.  For example:\n\n```python\n>>> import reader\n>>> import stock\n>>> port = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)\n>>>\n```\n\n\\[ [Solution](soln3_7.md) | [Index](index.md) | [Exercise 3.6](ex3_6.md) | [Exercise 3.8](ex3_8.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex3_8.md",
    "content": "\\[ [Index](index.md) | [Exercise 3.7](ex3_7.md) | [Exercise 4.1](ex4_1.md) \\]\n\n# Exercise 3.8\n\n*Objectives:*\n\n- Learn about mixin classes and cooperative inheritance\n\n*Files Modified:* `tableformat.py`\n\n## (a) The Trouble with Column Formatting\n\nIf you go all the way back to [Exercise 3.1](ex3_1.md), you \nwrote a function `print_portfolio()` that produced a table like this:\n\n```python\n>>> portfolio = read_portfolio('Data/portfolio.csv')\n>>> print_portfolio(portfolio)\n      name     shares      price\n---------- ---------- ---------- \n        AA        100      32.20\n       IBM         50      91.10\n       CAT        150      83.44\n      MSFT        200      51.23\n        GE         95      40.37\n      MSFT         50      65.10\n       IBM        100      70.44\n>>>\n```\n\nThe `print_table()` function developed in the last several exercises\nalmost replaces this functionality--almost.  The one problem that it\nhas is that it can't precisely format the content of each column.  For\nexample, notice how the values in the `price` column are precisely\nformatted with 2 decimal points.  The `TableFormatter` class and\nrelated subclasses can't do that.\n\nOne way to fix it would be to modify the `print_table()` function to\naccept an additional formats argument.  For example, maybe something\nlike this:\n\n```python\n>>> def print_table(records, fields, formats, formatter):\n        formatter.headings(fields)\n        for r in records:\n            rowdata = [(fmt % getattr(r, fieldname))\n\t               for fieldname,fmt in zip(fields,formats)]\n            formatter.row(rowdata)\n\n>>> import stock, reader\n>>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)\n>>> from tableformat import TextTableFormatter\n>>> formatter = TextTableFormatter()\n>>> print_table(portfolio, \n                ['name','shares','price'],\n                ['%s','%d','%0.2f'],\n                formatter)\n\n      name     shares      price\n---------- ---------- ---------- \n        AA        100      32.20\n       IBM         50      91.10\n       CAT        150      83.44\n      MSFT        200      51.23\n        GE         95      40.37\n      MSFT         50      65.10\n       IBM        100      70.44\n>>>\n```\n\nYes, you could modify `print_table()` like this, but is that the right\nplace to do it?  The whole idea of all of the `TableFormatter` classes\nis that they could be used in different kinds of applications.  Column\nformatting is something that could be useful elsewhere, not just\nin the `print_table()` function.\n\nAnother possible approach might be to change the interface to the\n`TableFormatter` class in some way.   For example, maybe adding a third\nmethod to apply formatting.\n\n```python\nclass TableFormatter:\n    def headings(self, headers):\n        ...\n    def format(self, rowdata):\n        ...\n    def row(self, rowdata):\n        ...\n```\n\nThe problem here is that any time you change the interface on a class,\nyou're going to have to refactor all of the existing code to work with\nit.  Specifically, you'd have to modify all of the already written\n`TableFormatter` subclasses and all of the code written to use them.\nLet's not do that.\n\nAs an alternative, a user could use inheritance to customize a\nspecific formatter in order to inject some formatting into it. For\nexample, try this experiment:\n\n```python\n>>> from tableformat import TextTableFormatter, print_table\n>>> class PortfolioFormatter(TextTableFormatter):\n        def row(self, rowdata):\n            formats = ['%s','%d','%0.2f']\n            rowdata = [(fmt % d) for fmt, d in zip(formats, rowdata)]\n            super().row(rowdata)\n\n>>> formatter = PortfolioFormatter()\n>>> print_table(portfolio, ['name','shares','price'], formatter)\n      name     shares      price\n---------- ---------- ---------- \n        AA        100      32.20\n       IBM         50      91.10\n       CAT        150      83.44\n      MSFT        200      51.23\n        GE         95      40.37\n      MSFT         50      65.10\n       IBM        100      70.44\n>>>\n```\nYes, that works, but it's also a bit clumsy and weird.  The user has\nto pick a specific formatter to customize.  On top of that, they have\nto implement the actual column formatting code themselves.  Surely\nthere is a different way to do this.\n\n## (b) Going Sideways\n\nIn the `tableformat.py` file, add the following class definition:\n\n```python\nclass ColumnFormatMixin:\n    formats = []\n    def row(self, rowdata):\n        rowdata = [(fmt % d) for fmt, d in zip(self.formats, rowdata)]\n        super().row(rowdata)\n```\n\nThis class contains a single method `row()` that applies formatting to\nthe row contents.  A class variable `formats` is used to hold the format\ncodes.  This class is used via multiple inheritance.  For example:\n\n```python\n>>> import stock, reader\n>>> portfolio = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)\n>>> from tableformat import TextTableFormatter, ColumnFormatMixin, print_table\n>>> class PortfolioFormatter(ColumnFormatMixin, TextTableFormatter):\n        formats = ['%s', '%d', '%0.2f']\n\n>>> formatter = PortfolioFormatter()\n>>> print_table(portfolio, ['name','shares','price'], formatter)\n      name     shares      price\n---------- ---------- ---------- \n        AA        100      32.20\n       IBM         50      91.10\n       CAT        150      83.44\n      MSFT        200      51.23\n        GE         95      40.37\n      MSFT         50      65.10\n       IBM        100      70.44\n```\n\nThis whole approach works because the `ColumnFormatMixin` class is\nmeant to be mixed together with another class that provides the\nrequired `row()` method.   \n\nMake another class that makes a formatter print the table headers in all-caps:\n\n```python\nclass UpperHeadersMixin:\n    def headings(self, headers):\n        super().headings([h.upper() for h in headers])\n```\n\nTry it out and notice that the headers are now uppercase:\n\n```python\n>>> from tableformat import TextTableFormatter, UpperHeadersMixin\n>>> class PortfolioFormatter(UpperHeadersMixin, TextTableFormatter):\n        pass\n\n>>> formatter = PortfolioFormatter()\n>>> print_table(portfolio, ['name','shares','price'], formatter)\n      NAME     SHARES      PRICE \n---------- ---------- ---------- \n        AA        100       32.2 \n       IBM         50       91.1 \n       CAT        150      83.44 \n      MSFT        200      51.23 \n        GE         95      40.37 \n      MSFT         50       65.1 \n       IBM        100      70.44 \n>>>\n```\n\nThis is really the whole idea on \"mixins.\"  The creator of a library\ncan provide a basic set of classes such as `TextTableFormatter`,\n`CSVTableFormatter`, and so forth to start.  Then, a collection of\nadd-on classes can be provided to make those classes behave in\ndifferent ways.\n\n## (c) Making it Sane\n\nUsing mixins can be a useful tool for framework builders for reducing \nthe amount of code that needs to be written.  However, forcing users\nto remember how to properly compose classes and use multiple inheritance can\nfry their brains.  In [Exercise 3.5](ex3_5.md), you wrote a \nfunction `create_formatter()` that made it easier to create a custom\nformatter.  Take that function and extend it to understand a few optional\narguments related to the mixin classes. For example:\n\n```python\n>>> from tableformat import create_formatter\n>>> formatter = create_formatter('csv', column_formats=['\"%s\"','%d','%0.2f'])\n>>> print_table(portfolio, ['name','shares','price'], formatter)\nname,shares,price\n\"AA\",100,32.20\n\"IBM\",50,91.10\n\"CAT\",150,83.44\n\"MSFT\",200,51.23\n\"GE\",95,40.37\n\"MSFT\",50,65.10\n\"IBM\",100,70.44\n\n>>> formatter = create_formatter('text', upper_headers=True)\n>>> print_table(portfolio, ['name','shares','price'], formatter)\n      NAME     SHARES      PRICE \n---------- ---------- ---------- \n        AA        100       32.2 \n       IBM         50       91.1 \n       CAT        150      83.44 \n      MSFT        200      51.23 \n        GE         95      40.37 \n      MSFT         50       65.1 \n       IBM        100      70.44 \n>>>\n```\n\nUnder the covers the `create_formatter()` function will properly compose\nthe classes and return a proper `TableFormatter` instance.\n\n\\[ [Solution](soln3_8.md) | [Index](index.md) | [Exercise 3.7](ex3_7.md) | [Exercise 4.1](ex4_1.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex4_1.md",
    "content": "\\[ [Index](index.md) | [Exercise 3.8](ex3_8.md) | [Exercise 4.2](ex4_2.md) \\]\n\n# Exercise 4.1\n\n*Objectives:*\n\n- Learn more about how objects are represented.\n- Learn how attribute assignment and lookup works.\n- Better understand the role of a class definition\n\n*Files Created:* None\n\n*Files Modified:* None\n\nStart this exercise, by going back to a simple version of the `Stock` class you created. \nAt the interactive prompt, define a \nnew class called `SimpleStock` that looks like this:\n\n```python\n>>> class SimpleStock:\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n        def cost(self):\n            return self.shares * self.price\n\n>>>\n```\n\nOnce you have defined this class, create a few instances.\n\n```python\n>>> goog = SimpleStock('GOOG',100,490.10)\n>>> ibm  = SimpleStock('IBM',50, 91.23)\n>>>\n```\n\n## (a) Representation of Instances\n\nAt the interactive shell, inspect the underlying dictionaries of the two instances you created:\n\n```python\n>>> goog.__dict__\n... look at the output ...\n>>> ibm.__dict__\n... look at the output ...\n>>>\n```\n\n## (b) Modification of Instance Data\n\nTry setting a new attribute on one of the above instances:\n\n```python\n>>> goog.date = \"6/11/2007\"\n>>> goog.__dict__\n... look at output ...\n>>> ibm.__dict__\n... look at output ...\n>>>\n```\n\nIn the above output, you'll notice that the `goog` instance has\na attribute `date` whereas the `ibm` instance does not.\nIt is important to note that Python really doesn't place any\nrestrictions on attributes.  For example, the attributes of an\ninstance are not limited to those set up in the `__init__()`\nmethod.\n\nInstead of setting an attribute, try placing a new value directly into\nthe `__dict__` object:\n\n```python\n>>> goog.__dict__['time'] = '9:45am'\n>>> goog.time\n'9:45am'\n>>>\n```\n\nHere, you really notice the fact that an instance is a layer on top of a dictionary.\n\n\n## (c) The role of classes\n\nThe definitions that make up a class definition are shared by all\ninstances of that class.  Notice, that all instances have a link back\nto their associated class:\n\n```python\n>>> goog.__class__\n... look at output ...\n>>> ibm.__class__\n... look at output ...\n>>> \n```\n\nTry calling a method on the instances:\n\n```python\n>>> goog.cost()\n49010.0\n>>> ibm.cost()\n4561.5\n>>>\n```\n\nNotice that the name 'cost' is not defined in either `goog.__dict__` or `ibm.__dict__`.   Instead, it is being supplied by the\nclass dictionary.  Try this:\n\n```python\n>>> SimpleStock.__dict__['cost']\n... look at output ...\n>>>\n```\n\nTry calling the `cost()` method directly through the dictionary:\n\n```python\n>>> SimpleStock.__dict__['cost'](goog)\n49010.00\n>>> SimpleStock.__dict__['cost'](ibm)\n4561.5\n>>>\n```\n\nNotice how you are calling the function defined in the class definition and how the `self` argument\ngets the instance.\n\nIf you add a new value to the class, it becomes a class variable that's visible to all instances.  Try it:\n\n```python\n>>> SimpleStock.spam = 42\n>>> ibm.spam\n42\n>>> goog.spam\n42\n>>>\n```\n\nObserve that `spam` is not part of the instance dictionary.\n\n```python\n>>> ibm.__dict__\n... look at the output ...\n>>>\n```\n\nInstead, it's part of the class dictionary:\n\n```python\n>>> SimpleStock.__dict__['spam']\n42\n>>>\n```\n\nEssentially this is all a class really is--it's a collection of values shared by instances.\n\n\n\\[ [Solution](soln4_1.md) | [Index](index.md) | [Exercise 3.8](ex3_8.md) | [Exercise 4.2](ex4_2.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex4_2.md",
    "content": "\\[ [Index](index.md) | [Exercise 4.1](ex4_1.md) | [Exercise 4.3](ex4_3.md) \\]\n\n# Exercise 4.2\n\n*Objectives:*\n\n- Learn more about the behavior of inheritance\n- Understand the behavior of super().\n- More cooperative inheritance.\n\n*Files Created:* `validate.py`\n\n\n## (a) The directions of inheritance\n\nPython has two different \"directions\" of inheritance.  The first is\nfound in the concept of \"single inheritance\" where a series\nof classes inherit from a single parent. For example, try this example:\n\n```python\n>>> class A:\n        def spam(self):\n            print('A.spam')\n\n>>> class B(A):\n        def spam(self):\n            print('B.spam')\n            super().spam()\n\n>>> class C(B):\n        def spam(self):\n            print('C.spam')\n            super().spam()\n\n\n>>> C.__mro__\n(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)\n>>> c = C()\n>>> c.spam()\nC.spam\nB.spam\nA.spam\n>>> \n```\n\nObserve that the `__mro__` attribute of class `C` encodes all of its ancestors in\norder.  When you invoke the `spam()` method, it walks the MRO class-by-class up\nthe hierarchy.\n\nWith multiple inheritance, you get a different kind of inheritance that\nallows different classes to be composed together.   Try this example:\n\n```\n>>> class Base:\n        def spam(self):\n            print('Base.spam')\n\n>>> class X(Base):\n        def spam(self):\n            print('X.spam')\n            super().spam()\n\n>>> class Y(Base):\n        def spam(self):\n            print('Y.spam')\n            super().spam()\n\n>>> class Z(Base):\n        def spam(self):\n            print('Z.spam')\n            super().spam()\n\n>>>\n```\n\nNotice that all of the classes above inherit from a common parent `Base`.\nHowever, the classes `X`, `Y`, and `Z` are not directly related \nto each other (there is no inheritance chain linking those classes together).\n\nHowever, watch what happens in multiple inheritance:\n\n```python\n>>> class M(X,Y,Z):\n        pass\n\n>>> M.__mro__\n(<class '__main__.M'>, <class '__main__.X'>, <class '__main__.Y'>, <class '__main__.Z'>, <class '__main__.Base'>, <class 'object'>)\n>>> m = M()\n>>> m.spam()\nX.spam\nY.spam\nZ.spam\nBase.spam\n>>>\n```\n\nHere, you see all of the classes stack together in the order supplied by the subclass.\nSuppose the subclass rearranges the class order:\n\n```python\n>>> class N(Z,Y,X):\n        pass\n\n>>> N.__mro__\n(<class '__main__.N'>, <class '__main__.Z'>, <class '__main__.Y'>, <class '__main__.X'>, <class '__main__.Base'>, <class 'object'>)\n>>> n = N()\n>>> n.spam()\nZ.spam\nY.spam\nX.spam\nBase.spam\n>>>\n```\n\nHere, you see the order of the parents flip around.  Carefully pay attention to what `super()`\nis doing in both cases.  It doesn't delegate to the immediate parent of each class--instead,\nit moves to the next class on the MRO.   Not only that, the exact order is controlled\nby the child. This is pretty weird.\n\nAlso notice that the common parent `Base` serves to terminate the chain of `super()` operations.\nSpecifically, the `Base.spam()` method does not call any further methods. It also appears at\nthe end of the MRO since it is the parent to all of the classes being composed together.\n\n## (b) Build a Value Checker\n\nIn [Exercise 3.4](ex3_4.md), you added some properties to the `Stock` class that\nchecked attributes for different types and values (e.g., shares had to be a positive\ninteger).  Let's play with that idea a bit.  Start by creating a file `validate.py` and\ndefining the following base class:\n\n```python\n# validate.py\nclass Validator:\n    @classmethod\n    def check(cls, value):\n        return value\n```\n\nNow, let's make some classes for type checking:\n\n```python\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'Expected {cls.expected_type}')\n        return super().check(value)\n\nclass Integer(Typed):\n    expected_type = int\n\nclass Float(Typed):\n    expected_type = float\n\nclass String(Typed):\n    expected_type = str\n```\n\nHere's how you use these classes (Note: the use of `@classmethod` allows us to \navoid the extra step of creating instances which we don't really need):\n\n```python\n>>> Integer.check(10)\n10\n>>> Integer.check('10')\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in check\n    raise TypeError(f'Expected {cls.expected_type}')\nTypeError: Expected <class 'int'>\n>>> String.check('10')\n'10'\n>>>\n```\n\nYou could use the validators in a function. For example:\n\n```python\n>>> def add(x, y):\n        Integer.check(x)\n        Integer.check(y)\n        return x + y\n\n>>> add(2, 2)\n4\n>>> add('2', '3')\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"<stdin>\", line 2, in add\n  File \"validate.py\", line 11, in check\n    raise TypeError(f'Expected {cls.expected_type}')\nTypeError: Expected <class 'int'>\n>>>\n```\n\nNow, make some more classes for different kinds of domain checking:\n\n```python\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('Expected >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('Must be non-empty')\n        return super().check(value)\n```\n\nWhere is all of this going?   Let's start composing classes together with multiple inheritance like toy blocks:\n\n```python\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n```\n\nEssentially, you're taking existing validators and composing them\ntogether into new ones. Madness!  However, let's use them to validate\nsome things now:\n\n```python\n>>> PositiveInteger.check(10)\n10\n>>> PositiveInteger.check('10')\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n    raise TypeError(f'Expected {cls.expected_type}')\nTypeError: Expected <class 'int'>\n>>> PositiveInteger.check(-10)\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n    raise ValueError('Expected >= 0')\nValueError: Must be >= 0\n\n\n>>> NonEmptyString.check('hello')\n'hello'\n>>> NonEmptyString.check('')\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n    raise ValueError('Must be non-empty')\nValueError: Must be non-empty\n>>>\n```\n\nAt this point, your head is probably fully exploded.  However, the problem of composing\ndifferent bits of code together is one that arises in real-world programs. Cooperative\nmultiple inheritance is one of the tools that can be used to organize it. \n\n## (c) Using your validators\n\nYour validators can be used to add value checking to functions and classes.  For\nexample, perhaps the validators could be used in the properties of `Stock`:\n\n```python\nclass Stock:\n    ...\n    @property\n    def shares(self):\n        return self._shares\n\n    @shares.setter\n    def shares(self, value):\n        self._shares = PositiveInteger.check(value)\n    ...\n```\n\nCopy the `Stock` class from `stock.py` change it to use the validators in the property\ncode for `shares` and `price`.\n\n\\[ [Solution](soln4_2.md) | [Index](index.md) | [Exercise 4.1](ex4_1.md) | [Exercise 4.3](ex4_3.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex4_3.md",
    "content": "\\[ [Index](index.md) | [Exercise 4.2](ex4_2.md) | [Exercise 4.4](ex4_4.md) \\]\n\n# Exercise 4.3\n\n*Objectives:*\n\n- Learn about descriptors\n\n*Files Created:* `descrip.py`\n\n*Files Modified:* `validate.py`\n\n## (a) Descriptors in action\n\nEarlier, you created a class `Stock` that made use of\nslots, properties, and other features.   All of these features\nare implemented using the descriptor protocol.  See it in\naction by trying this simple experiment.\n\nFirst, create a stock object, and try looking up a few attributes:\n\n```python\n>>> s = Stock('GOOG', 100, 490.10)\n>>> s.name\n'GOOG'\n>>> s.shares\n100\n>>>\n```\n\nNow, notice that these attributes are in the class dictionary.\n\n```python\n>>> Stock.__dict__.keys()\n['sell', '__module__', '__weakref__', 'price', '_price', 'shares', '_shares',\n'__slots__', 'cost', '__repr__', '__doc__', '__init__']\n>>> \n```\n\nTry these steps which illustrate how descriptors get and set values on an instance:\n\n```python\n>>> q = Stock.__dict__['shares']\n>>> q.__get__(s)\n100\n>>> q.__set__(s,75)\n>>> s.shares\n75\n>>> q.__set__(s, '75')\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"stock.py\", line 23, in shares\n    raise TypeError('Expected an integer')\nTypeError: Expected an integer\n>>>\n```\n\nThe execution of `__get__()` and `__set__()` occurs automatically whenever you access instances.\n\n\n## (b) Make your own descriptor\n\nDefine the descriptor class from the notes:\n\n```python\n# descrip.py\n\nclass Descriptor:\n    def __init__(self, name):\n        self.name = name\n    def __get__(self, instance, cls):\n        print('%s:__get__' % self.name)\n    def __set__(self, instance, value):\n        print('%s:__set__ %s' % (self.name, value))\n    def __delete__(self, instance):\n        print('%s:__delete__' % self.name)\n```\n\nNow, try defining a simple class that uses this descriptor:\n\n```python\n>>> class Foo:\n        a = Descriptor('a')\n        b = Descriptor('b')\n        c = Descriptor('c')\n\n>>> f = Foo()\n>>> f\n<__main__.Foo object at 0x38e130> <class __main__.Foo>\n>>> f.a\na:__get__\n>>> f.b\nb:__get__\n>>> f.a = 23\na:__set__ 23\n>>> del f.a\na:__delete__\n>>>\n```\n\nPonder the fact that you have captured the dot-operator for a\nspecific attribute.\n\n## (c) From Validators to Descriptors\n\nIn the previous exercise, you wrote a series of classes that could perform checking.\nFor example:\n\n```python\n>>> PositiveInteger.check(10)\n10\n>>> PositiveInteger.check('10')\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n    raise TypeError('Expected %s' % cls.expected_type)\nTypeError: expected <class 'int'>\n>>> PositiveInteger.check(-10)\n```\n\nYou can extend this to descriptors by making a simple change to the `Validator` base\nclass.  Change it to the following:\n\n```python\n# validate.py\n\nclass Validator:\n    def __init__(self, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance,\tvalue):\n        instance.__dict__[self.name] = self.check(value)\n```\n\nNote: The lack of the `__get__()` method in the descriptor means that\nPython will use its default implementation of attribute lookup.  This\nrequires that the supplied name matches the name used in the instance\ndictionary.\n\nNo other changes should be necessary.   Now, try modifying the `Stock` class to\nuse the validators as descriptors like this:\n\n```python\nclass Stock:\n    name   = String('name')\n    shares = PositiveInteger('shares')\n    price  = PositiveFloat('price')\n\n    def __init__(self,name,shares,price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n```\n\nYou'll find that your class works the same way as before, involves much\nless code, and gives you all of the desired checking:\n\n```python\n>>> s = Stock('GOOG', 100, 490.10)\n>>> s.name\n'GOOG'\n>>> s.shares\n100\n>>> s.shares = 75\n>>> s.shares = '75'\n... TypeError ...\n>>> s.shares = -50\n... ValueError ...\n>>>\n```\n\nThis is pretty cool.  Descriptors have allowed you to greatly simplify the implementation\nof the `Stock` class.  This is the real power of descriptors--you get low level control\nover the dot and can use it to do amazing things.\n\n## (d) Fixing the Names\n\nOne annoying thing about descriptors is the redundant name specification. For example:\n\n```python\nclass Stock:\n    ...\n    shares = PositiveInteger('shares')\n    ...\n```\n\nWe can fix that.  Change the top-level `Validator` class to include a `__set_name__()` method\nlike this:\n\n```python\n# validate.py\n\nclass Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance,\tvalue):\n        instance.__dict__[self.name] = self.check(value)\n```\n\nNow, try rewriting your `Stock` class so that it looks like this:\n\n```python\nclass Stock:\n    name   = String()\n    shares = PositiveInteger()\n    price  = PositiveFloat()\n\n    def __init__(self,name,shares,price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n```\n\nAh, much nicer.   Be aware that this ability to set the name is a Python 3.6\nfeature however.  It won't work on older versions.\n\n\\[ [Solution](soln4_3.md) | [Index](index.md) | [Exercise 4.2](ex4_2.md) | [Exercise 4.4](ex4_4.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex4_4.md",
    "content": "\\[ [Index](index.md) | [Exercise 4.3](ex4_3.md) | [Exercise 5.1](ex5_1.md) \\]\n\n# Exercise 4.4\n\n*Objectives:*\n\n- Learn about customizing attribute access\n- Delegation vs. inheritance\n\n## (a) Slots vs. setattr\n\nIn previous exercises, `__slots__` was used to list the instance\nattributes on a class.  The primary purpose of slots is to optimize\nthe use of memory.  A secondary effect is that it strictly limits the\nallowed attributes to those listed.  A downside of slots is that it\noften interacts strangely with other parts of Python (for example,\nclasses using slots can't be used with multiple inheritance).  For\nthat reason, you really shouldn't use slots except in special cases.\n\nIf you really wanted to limit the set of allowed attributes, an\nalternate way to do it would be to define a `__setattr__()` method.\nTry this experiment:\n\n```python\n>>> class Stock:\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n        def __setattr__(self, name, value):\n            if name not in { 'name', 'shares', 'price' }:\n                raise AttributeError('No attribute %s' % name)\n            super().__setattr__(name, value)\n\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s.name\n'GOOG'\n>>> s.shares = 75\n>>> s.share = 50\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"<stdin>\", line 8, in __setattr__\nAttributeError: No attribute share\n>>> \n```\n\nIn this example, there are no slots, but the `__setattr__()` method still restricts\nattributes to those in a predefined set.   You'd probably need to\nthink about how this approach might interact with inheritance (e.g., if subclasses wanted\nto add new attributes, they'd probably need to redefine `__setattr__()` to make it work).\n\n## (b) Proxies\n\nA proxy class is a class that wraps around an existing class and provides a similar interface.\nDefine the following class which makes a read-only layer around an existing object:\n\n```python\n>>> class Readonly:\n        def __init__(self, obj):\n            self.__dict__['_obj'] = obj\n        def __setattr__(self, name, value):\n            raise AttributeError(\"Can't set attribute\")\n        def __getattr__(self, name):\n            return getattr(self._obj, name)\n\n>>>\n```\n\nTo use the class, you simply wrap it around an existing instance:\n\n```python\n>>> from stock import Stock\n>>> s = Stock('GOOG', 100, 490.1)\n>>> p = Readonly(s)\n>>> p.name\n'GOOG'\n>>> p.shares\n100\n>>> p.cost\n49010.0\n>>> p.shares = 50\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"<stdin>\", line 8, in __setattr__\nAttributeError: Can't set attribute\n>>> \n```\n\n## (c) Delegation as an alternative to inheritance\n\nDelegation is sometimes used as an alternative to inheritance. The idea is almost the\nsame as the proxy class you defined in part (b).  Try defining the following class:\n\n```python\n>>> class Spam:\n        def a(self):\n            print('Spam.a')\n        def b(self):\n            print('Spam.b')\n\n>>>\n```\n\nNow, make a class that wraps around it and redefines some of the methods:\n\n```python\n>>> class MySpam:\n        def __init__(self):\n            self._spam = Spam()\n        def a(self):\n            print('MySpam.a')\n            self._spam.a()\n        def c(self):\n            print('MySpam.c')\n        def __getattr__(self, name):\n            return getattr(self._spam, name)\n\n>>> s = MySpam()\n>>> s.a()\nMySpam.a\nSpam.a\n>>> s.b()\nSpam.b\n>>> s.c()\nMySpam.c\n>>>\n```\n\nCarefully notice that the resulting class looks very similar to\ninheritance.  For example the `a()` method is doing something similar\nto the `super()` call.  The method `b()` is picked up via the\n`__getattr__()` method which delegates to the internally held `Spam`\ninstance.\n\n**Discussion**\n\nThe `__getattr__()` method is commonly defined on classes that act as\nwrappers around other objects.  However, you have to be aware that the\nprocess of wrapping another object in this manner often introduces\nother complexities.  For example, the wrapper object might break\ntype-checking if any other part of the application is using the\n`isinstance()` function. \n\nDelegating methods through `__getattr__()` also doesn't work with special\nmethods such as `__getitem__()`, `__enter__()`, and so forth.   If a class\nmakes extensive use of such methods, you'll have to provide similar functions\nin your wrapper class. \n\n\n\\[ [Solution](soln4_4.md) | [Index](index.md) | [Exercise 4.3](ex4_3.md) | [Exercise 5.1](ex5_1.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex5_1.md",
    "content": "\\[ [Index](index.md) | [Exercise 4.4](ex4_4.md) | [Exercise 5.2](ex5_2.md) \\]\n\n# Exercise 5.1\n\n*Objectives:*\n\n- Explore a few definitional aspects of functions/methods\n- Making functions more flexible\n- Type hints\n\nIn [Exercise 2.6](ex2_6.md) you wrote a `reader.py` module that\nhad a function for reading a CSV into a list of dictionaries.  For example:\n\n```python\n>>> import reader\n>>> port = reader.read_csv_as_dicts('Data/portfolio.csv', [str,int,float])\n>>>\n```\n\nWe later expanded to that code to work with instances in\n[Exercise 3.3](ex3_3.md): \n\n```python\n>>> import reader\n>>> from stock import Stock\n>>> port = reader.read_csv_as_instances('Data/portfolio.csv', Stock)\n>>>\n```\n\nEventually the code was refactored into a collection of classes\ninvolving inheritance in [Exercise 3.7](ex3_7.md).  However,\nthe code has become rather complex and convoluted.\n\n## (a) Back to Basics\n\nStart by reverting the changes related to class definitions.  Rewrite\nthe `reader.py` file so that it contains the two basic functions that\nyou had before you messed it up with classes:\n\n```python\n# reader.py\n\nimport csv\n\ndef read_csv_as_dicts(filename, types):\n    '''\n    Read CSV data into a list of dictionaries with optional type conversion\n    '''\n    records = []\n    with open(filename) as file:\n        rows = csv.reader(file)\n        headers = next(rows)\n        for row in rows:\n            record = { name: func(val) \n                       for name, func, val in zip(headers, types, row) }\n            records.append(record)\n    return records\n\ndef read_csv_as_instances(filename, cls):\n    '''\n    Read CSV data into a list of instances\n    '''\n    records = []\n    with open(filename) as file:\n        rows = csv.reader(file)\n        headers = next(rows)\n        for row in rows:\n            record = cls.from_row(row)\n            records.append(record)\n    return records\n```\n\nMake sure the code still works as it did before:\n\n```python\n>>> import reader\n>>> port = reader.read_csv_as_dicts('Data/portfolio.csv', [str, int, float])\n>>> port\n[{'name': 'AA', 'shares': 100, 'price': 32.2}, {'name': 'IBM', 'shares': 50, 'price': 91.1}, \n {'name': 'CAT', 'shares': 150, 'price': 83.44}, {'name': 'MSFT', 'shares': 200, 'price': 51.23}, \n {'name': 'GE', 'shares': 95, 'price': 40.37}, {'name': 'MSFT', 'shares': 50, 'price': 65.1}, \n {'name': 'IBM', 'shares': 100, 'price': 70.44}]\n>>> import stock\n>>> port = reader.read_csv_as_instances('Data/portfolio.csv', stock.Stock)\n>>> port\n[Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44), \n Stock('MSFT', 200, 51.23), Stock('GE', 95, 40.37), Stock('MSFT', 50, 65.1), \n Stock('IBM', 100, 70.44)]\n>>>\n```\n\n## (b) Thinking about Flexibility\n\nRight now, the two functions in `reader.py` are hard-wired to work\nwith filenames that are passed directly to `open()`.  Refactor the\ncode so that it works with any iterable object that produces lines.\nTo do this, create two new functions `csv_as_dicts(lines, types)` and\n`csv_as_instances(lines, cls)` that convert any iterable sequence of\nlines.  For example:\n\n```python\n>>> file = open('Data/portfolio.csv')\n>>> port = reader.csv_as_dicts(file, [str, int, float])\n>>> port\n[{'name': 'AA', 'shares': 100, 'price': 32.2}, {'name': 'IBM', 'shares': 50, 'price': 91.1}, \n {'name': 'CAT', 'shares': 150, 'price': 83.44}, {'name': 'MSFT', 'shares': 200, 'price': 51.23}, \n {'name': 'GE', 'shares': 95, 'price': 40.37}, {'name': 'MSFT', 'shares': 50, 'price': 65.1}, \n {'name': 'IBM', 'shares': 100, 'price': 70.44}]\n>>>\n```\n\nThe whole point of doing this is to make it possible to work with different\nkinds of input sources.  For example:\n\n```python\n>>> import gzip\n>>> import stock\n>>> file = gzip.open('Data/portfolio.csv.gz', 'rt')\n>>> port = reader.csv_as_instances(file, stock.Stock)\n>>> port\n[Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44), \n Stock('MSFT', 200, 51.23), Stock('GE', 95, 40.37), Stock('MSFT', 50, 65.1), \n Stock('IBM', 100, 70.44)]\n>>>\n```\n\nTo maintain backwards compatibility with older code, write functions\n`read_csv_as_dicts()` and `read_csv_as_instances()` that take a\nfilename as before.  These functions should call `open()` on the\nsupplied filename and use the new `csv_as_dicts()` or\n`csv_as_instances()` functions on the resulting file.\n\n## (c) Design Challenge: CSV Headers\n\nThe code assumes that the first line of CSV data always contains\ncolumn headers.  However, this isn't always the case. For example, the\nfile `Data/portfolio_noheader.csv` contains data, but no column\nheaders.\n\nHow would you refactor the code to accommodate missing column headers, having\nthem supplied manually by the caller instead?\n\n## (d) API Challenge: Type hints\n\nFunctions can have optional type-hints attached to arguments and return values.\nFor example:\n\n```python\ndef add(x:int, y:int) -> int:\n    return x + y\n```\n\nThe `typing` module has additional classes for expressing more complex kinds of\ntypes including containers.  For example:\n\n```python\nfrom typing import List\n\ndef sum_squares(nums: List[int]) -> int:\n    total = 0\n    for n in nums:\n        total += n*n\n    return total\n```\n\nYour challenge: Modify the code in `reader.py` so that all functions\nhave type hints.  Try to make the type-hints as accurate as possible.\nTo do this, you may need to consult the documentation for the\n[typing module](https://docs.python.org/3/library/typing.html).\n\n\\[ [Solution](soln5_1.md) | [Index](index.md) | [Exercise 4.4](ex4_4.md) | [Exercise 5.2](ex5_2.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex5_2.md",
    "content": "\\[ [Index](index.md) | [Exercise 5.1](ex5_1.md) | [Exercise 5.3](ex5_3.md) \\]\n\n# Exercise 5.2\n\n*Objectives:*\n\n- Returning values from functions\n\nIn this exercise, we briefly look at problems related to returning values from functions.\nAt first glance, it seems like this should be straightforward, but there are some\nsubtle problems that arise.\n\n## (a) Returning Multiple Values\n\nSuppose you were writing code to parse configuration files consisting of lines like this:\n\n    name=value\n\nWrite a function `parse_line(line)` that takes such a line and returns both the associated name and \nvalue.  The common convention for returning multiple values is to return them in a tuple.  For example:\n\n```python\n>>> parse_line('email=guido@python.org')\n('email', 'guido@python.org')\n>>> name, val = parse_line('email=guido@python.org')\n>>> name\n'email'\n>>> val\n'guido@python.org'\n>>>\n```\n\n## (b) Returning Optional Values\n\nSometimes a function might return an optional value--possibly as a mechanism for indicating \nsuccess or failure.  The most common convention is to use `None` as a representation for \na missing value.  Modify the `parse_line()` function above so that it either returns a tuple \non success or `None` on bad data.  For example:\n\n```python\n>>> parse_line('email=guido@python.org')\n('email', 'guido@python.org')\n>>> parse_line('spam')       # Returns None\n>>>\n```\n\nDesign discussion:  Would it be better for the `parse_line()` function to raise an exception\non malformed data?\n\n## (c) Futures\n\nSometimes Python code executes concurrently via threads or processes.  To illustrate, try\nthis example:\n\n```python\n>>> import time\n>>> def worker(x, y):\n        print('About to work')\n        time.sleep(20)\n        print('Done')\n        return x + y\n\n>>> worker(2, 3)     # Normal function call\nAbout to work  \nDone\n5\n>>>\n```\n\nNow, launch `worker()` into a separate thread:\n\n```python\n>>> import threading\n>>> t = threading.Thread(target=worker, args=(2, 3))\n>>> t.start()\nAbout to work\n>>>\nDone\n```\n\nCarefully notice that the result of the calculation appears nowhere. Not only that, you don't even\nknow when it's going to be completed.   There is a certain coordination problem here. The\nconvention for handling this case is to wrap the result of a function in a `Future`.  A\n`Future` represents a future result. Here's how it works:\n\n```python\n>>> from concurrent.futures import Future\n>>> # Wrapper around the function to use a future\n>>> def do_work(x, y, fut):\n        fut.set_result(worker(x,y))\n\n>>> fut = Future()\n>>> t = threading.Thread(target=do_work, args=(2, 3, fut))\n>>> t.start()\nAbout to work  \n>>> result = fut.result()\nDone\n>>> result\n5\n>>>\n```\n\nYou'll see this kind of pattern a lot of if working with thread pools, processes, and other\nconstructs.  For example:\n\n```python\n>>> from concurrent.futures import ThreadPoolExecutor\n>>> pool = ThreadPoolExecutor()\n>>> fut = pool.submit(worker, 2, 3)\nAbout to work\n>>> fut\n<Future at 0x102157080 state=running>\n>>> fut.result()\nDone\n5\n>>>\n```\n\n\\[ [Solution](soln5_2.md) | [Index](index.md) | [Exercise 5.1](ex5_1.md) | [Exercise 5.3](ex5_3.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex5_3.md",
    "content": "\\[ [Index](index.md) | [Exercise 5.2](ex5_2.md) | [Exercise 5.4](ex5_4.md) \\]\n\n# Exercise 5.3\n\n*Objectives:*\n\n- Higher order functions\n\n*Files Modified:* `reader.py`\n\n## (a) Using higher-order functions\n\nAt the moment, the `reader.py` program consists of two core functions, `csv_as_dicts()` and\n`csv_as_instances()`.   The code in these two functions is almost identical.  For example:\n\n```python\ndef csv_as_dicts(lines, types, *, headers=None):\n    '''\n    Convert lines of CSV data into a list of dictionaries\n    '''\n    records = []\n    rows = csv.reader(lines)\n    if headers is None:\n        headers = next(rows)\n    for row in rows:\n        record = { name: func(val)\n                   for name, func, val in zip(headers, types, row) }\n        records.append(record)\n    return records\n\ndef csv_as_instances(lines, cls, *, headers=None):\n    '''\n    Convert lines of CSV data into a list of instances\n    '''\n    records = []\n    rows = csv.reader(lines)\n    if headers is None:\n        headers = next(rows)\n    for row in rows:\n        record = cls.from_row(row)\n        records.append(record)\n    return records\n```\n\nUnify the core of these functions into a single function `convert_csv()` that accepts a user-defined\nconversion function as an argument.  For example:\n\n```python\n>>> def make_dict(headers, row):\n        return dict(zip(headers, row))\n\n>>> lines = open('Data/portfolio.csv')\n>>> convert_csv(lines, make_dict)\n[{'name': 'AA', 'shares': '100', 'price': '32.20'}, {'name': 'IBM', 'shares': '50', 'price': '91.10'}, \n {'name': 'CAT', 'shares': '150', 'price': '83.44'}, {'name': 'MSFT', 'shares': '200', 'price': '51.23'}, \n {'name': 'GE', 'shares': '95', 'price': '40.37'}, {'name': 'MSFT', 'shares': '50', 'price': '65.10'}, \n {'name': 'IBM', 'shares': '100', 'price': '70.44'}]\n>>>\n```\n\nRewrite the `csv_as_dicts()` and `csv_as_instances()` functions in terms of the new `convert_csv()`\nfunction.\n\n## (b) Mapping\n\nOne of the most common operations in functional programming is the `map()` operation that maps a function\nto the values in a sequence.  Python has a built-in `map()` function that does this.  For\nexample:\n\n```python\n>>> nums = [1,2,3,4]\n>>> squares = map(lambda x: x*x, nums)\n>>> for n in squares:\n        print(n)\n\n1\n4\n9\n16\n>>>\n```\n\n`map()` produces an iterator so if you want a list, you'll need to create it explicitly:\n\n```python\n>>> squares = list(map(lambda x: x*x, nums))\n>>> squares\n[1, 4, 9, 16]\n>>>\n```\n\nTry to use `map()` in your `convert_csv()` function.\n\n\\[ [Solution](soln5_3.md) | [Index](index.md) | [Exercise 5.2](ex5_2.md) | [Exercise 5.4](ex5_4.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex5_4.md",
    "content": "\\[ [Index](index.md) | [Exercise 5.3](ex5_3.md) | [Exercise 5.5](ex5_5.md) \\]\n\n# Exercise 5.4\n\n*Objectives:*\n\n- Learn more about closures\n\nIn this section, we look briefly at a few of the more unusual aspects of\nclosures.\n\n## (a) Closures as a data structure\n\nOne potential use of closures is as a tool for data encapsulation.  Try this\nexample:\n\n```python\ndef counter(value):\n    def incr():\n        nonlocal value\n        value += 1\n        return value\n\n    def decr():\n        nonlocal value\n        value -= 1\n        return value\n\n    return incr, decr\n```\n\nThis code defines two inner functions that manipulate a value.  Try it out:\n\n```python\n>>> up, down = counter(0)\n>>> up()\n1\n>>> up()\n2\n>>> up()\n3\n>>> down()\n2\n>>> down()\n1\n>>>\n```\n\nNotice how there is no class definition involved here.  Moreover,\nthere is no global variable either.  Yet, the `up()` and `down()`\nfunctions are manipulating some \"behind the scenes\" value.  It's\nfairly magical.\n\n## (b) Closures as a code generator\n\nIn [Exercise 4.3](ex4_3.md), you developed a collection of\ndescriptor classes that allowed type-checking of object attributes.\nFor example:\n\n```python\n\nclass Stock:\n    name = String()\n    shares = Integer()\n    price = Float()\n```\n\nThis kind of thing can also be implemented using closures.  Define a file\n``typedproperty.py`` and put the following code in it:\n\n```python\n# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n\n    @property\n    def value(self):\n        return getattr(self, private_name)\n\n    @value.setter\n    def value(self, val):\n        if not isinstance(val, expected_type):\n            raise TypeError(f'Expected {expected_type}')\n        setattr(self, private_name, val)\n   \n    return value\n```\n\nThis look pretty wild, but the function is effectively making code.  You'd use it in\na class definition like this:\n\n```python\nfrom typedproperty import typedproperty\n\nclass Stock:\n    name = typedproperty('name', str)\n    shares = typedproperty('shares', int)\n    price = typedproperty('price', float)\n\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n```\n\nVerify that this class performs type-checking in the same way as the\ndescriptor code.\n\nAdd function `String()`, `Integer()`, and `Float()` to the `typedproperty.py` file\nso that you can write the following code:\n\n```python\nfrom typedproperty import String, Integer, Float\n\nclass Stock:\n    name = String('name')\n    shares = Integer('shares')\n    price = Float('price')\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n```\n\n## (c) Challenge: Eliminating names\n\nModify the `typedproperty.py` code so that attribute names are no-longer required:\n\n```python\nfrom typedproperty import String, Integer, Float\n\nclass Stock:\n    name = String()\n    shares = Integer()\n    price = Float()\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n```\n\nHint: To do this, recall the `__set_name__()` method of descriptor objects that \ngets called when descriptors are placed in a class definition.\n\n\\[ [Solution](soln5_4.md) | [Index](index.md) | [Exercise 5.3](ex5_3.md) | [Exercise 5.5](ex5_5.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex5_5.md",
    "content": "\\[ [Index](index.md) | [Exercise 5.4](ex5_4.md) | [Exercise 5.6](ex5_6.md) \\]\n\n# Exercise 5.5\n\n*Objectives:*\n\n- Learn more about exception handling and logging\n\n*Files Modified:* `reader.py`\n\nIn the `reader.py` file, there is a central function `convert_csv()` that does\nmost of the work.  This function crashes if you run it on data with missing or\nbad data.  For example:\n\n```python\n>>> port = read_csv_as_dicts('Data/missing.csv', types=[str, int, float])\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"reader.py\", line 24, in read_csv_as_dicts\n    return csv_as_dicts(file, types, headers=headers)\n  File \"reader.py\", line 13, in csv_as_dicts\n    lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) })\n  File \"reader.py\", line 9, in convert_csv\n    return list(map(lambda row: converter(headers, row), rows))\n  File \"reader.py\", line 9, in <lambda>\n    return list(map(lambda row: converter(headers, row), rows))\n  File \"reader.py\", line 13, in <lambda>\n    lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) })\n  File \"reader.py\", line 13, in <dictcomp>\n    lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) })\nValueError: invalid literal for int() with base 10: ''\n>>> \n```\n\n## (a) Catching Exceptions\n\nInstead of crashing on bad data, modify the code to issue a warning message\ninstead. The final result should be a list of the rows that were successfully\nconverted. For example:\n\n```python\n>>> port = read_csv_as_dicts('Data/missing.csv', types=[str, int, float])\nRow 4: Bad row: ['C', '', '53.08']\nRow 7: Bad row: ['DIS', '50', 'N/A']\nRow 8: Bad row: ['GE', '', '37.23']\nRow 13: Bad row: ['INTC', '', '21.84']\nRow 17: Bad row: ['MCD', '', '51.11']\nRow 19: Bad row: ['MO', '', '70.09']\nRow 22: Bad row: ['PFE', '', '26.40']\nRow 26: Bad row: ['VZ', '', '42.92']\n>>> len(port)\n20\n>>>\n```\n\nNote:  Making this change may be a bit tricky because of your previous use of the `map()`\nbuilt-in function. You may have to abandon that approach since there's no easy way to catch\nand handle exceptions in `map()`.\n\n## (b) Logging\n\nModify the code so that warning messages are issued using the `logging` module.  In \naddition, give optional debugging information indicating the reason for failure.\nFor example:\n\n```python\n>>> import reader\n>>> import logging\n>>> logging.basicConfig(level=logging.DEBUG)\n>>> port = reader.read_csv_as_dicts('Data/missing.csv', types=[str, int, float])\nWARNING:reader:Row 4: Bad row: ['C', '', '53.08']\nDEBUG:reader:Row 4: Reason: invalid literal for int() with base 10: ''\nWARNING:reader:Row 7: Bad row: ['DIS', '50', 'N/A']\nDEBUG:reader:Row 7: Reason: could not convert string to float: 'N/A'\nWARNING:reader:Row 8: Bad row: ['GE', '', '37.23']\nDEBUG:reader:Row 8: Reason: invalid literal for int() with base 10: ''\nWARNING:reader:Row 13: Bad row: ['INTC', '', '21.84']\nDEBUG:reader:Row 13: Reason: invalid literal for int() with base 10: ''\nWARNING:reader:Row 17: Bad row: ['MCD', '', '51.11']\nDEBUG:reader:Row 17: Reason: invalid literal for int() with base 10: ''\nWARNING:reader:Row 19: Bad row: ['MO', '', '70.09']\nDEBUG:reader:Row 19: Reason: invalid literal for int() with base 10: ''\nWARNING:reader:Row 22: Bad row: ['PFE', '', '26.40']\nDEBUG:reader:Row 22: Reason: invalid literal for int() with base 10: ''\nWARNING:reader:Row 26: Bad row: ['VZ', '', '42.92']\nDEBUG:reader:Row 26: Reason: invalid literal for int() with base 10: ''\n>>>\n```\n\n\\[ [Solution](soln5_5.md) | [Index](index.md) | [Exercise 5.4](ex5_4.md) | [Exercise 5.6](ex5_6.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex5_6.md",
    "content": "\\[ [Index](index.md) | [Exercise 5.5](ex5_5.md) | [Exercise 6.1](ex6_1.md) \\]\n\n# Exercise 5.6\n\n*Objectives:*\n\n- Learn how to use Python's unittest module\n\n*Files Created:* `teststock.py`\n\nIn this exercise, you will explore the basic mechanics of using\nPython's `unittest` modules.\n\n## (a) Preliminaries\n\nIn previous exercises, you created a file `stock.py` that contained\na `Stock` class.   In a separate file, `teststock.py`, define the following\ntesting code:\n\n```python\n# teststock.py\n\nimport unittest\nimport stock\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\nif __name__ == '__main__':\n    unittest.main()\n```\n\nMake sure you can run the file:\n\n```\n bash % python3 teststock.py\n .\n ------------------------------------------------------------------```\n Ran 1 tests in 0.001s\n\n OK\n bash %\n```\n\n## (b) Unit testing\n\nUsing the code in `teststock.py` as a guide, extend the `TestStock` class\nwith tests for the following:\n\n- Test that you can create a `Stock` using keyword arguments such as `Stock(name='GOOG',shares=100,price=490.1)`.\n- Test that the `cost` property returns a correct value\n- Test that the `sell()` method correctly updates the shares.\n- Test that the `from_row()` class method creates a new instance from good data.\n- Test that the `__repr__()` method creates a proper representation string.\n- Test the comparison operator method `__eq__()`\n\n## (c) Unit tests with expected errors\n\nSuppose you wanted to write a unit test that checks for an exception.\nHere is how you can do it:\n\n```python\nclass TestStock(unittest.TestCase):\n    ...\n    def test_bad_shares(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n             s.shares = '50'\n    ...\n```\n\nUsing this test as a guide, write unit tests for the following failure modes:\n\n- Test that setting `shares` to a string raises a `TypeError`\n- Test that setting `shares` to a negative number raises a `ValueError`\n- Test that setting `price` to a string raises a `TypeError`\n- Test that setting `price` to a negative number raises a `ValueError`\n- Test that setting a non-existent attribute `share` raises an `AttributeError`\n\nIn total, you should have around a dozen unit tests when you're done.\n\n**Important Note**\n\nFor later use in the course, you will want to have a fully working\n`stock.py` and `teststock.py` file.  Save your work in progress if you\nhave to, but you are strongly encouraged to copy the code from\n`Solutions/5_6` if things are still broken at this point.\n\nWe're going to use the `teststock.py` file as a tool for improving the `Stock` code\nlater.  You'll want it on hand to make sure that the new code behaves the same way\nas the old code.\n\n\\[ [Solution](soln5_6.md) | [Index](index.md) | [Exercise 5.5](ex5_5.md) | [Exercise 6.1](ex6_1.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex6_1.md",
    "content": "\\[ [Index](index.md) | [Exercise 5.6](ex5_6.md) | [Exercise 6.2](ex6_2.md) \\]\n\n# Exercise 6.1\n\n*Objectives:*\n\n- Learn more about function argument passing conventions\n\n*Files Created:* `structure.py`, `stock.py`\n\n\n**IMPORTANT NOTE**\n\nThis exercise is going to start a long road of rewriting the `stock.py` file in a more\nsane way.   Before doing anything, copy your work in `stock.py` to a new file\n`orig_stock.py`.  \n\nWe're going to recreate the `Stock` class from scratch using some new techniques.\nMake sure you have your unit tests from [Exercise 5.4](ex5_4.md) handy. You'll want those.\n\nIf you define a function, you probably already know that it can be\ncalled using a mix of positional or keyword arguments.  For example:\n\n```python\n>>> def foo(x, y, z):\n        return x + y + z\n\n>>> foo(1, 2, 3)\n6\n>>> foo(1, z=3, y=2)\n6\n>>>\n```\n\nYou may also know that you can pass sequences and dictionaries as\nfunction arguments using the * and ** syntax.  For example:\n\n```python\n>>> args = (1, 2, 3)\n>>> foo(*args)\n6\n>>> kwargs = {'y':2, 'z':3 }\n>>> foo(1,**kwargs)\n6\n>>>\n```\n\nIn addition to that, you can write functions that accept any number of\npositional or keyword arguments using the * and ** syntax.  For\nexample:\n\n```python\n>>> def foo(*args):\n        print(args)\n\n>>> foo(1,2)\n(1, 2)\n>>> foo(1,2,3,4,5)\n(1, 2, 3, 4, 5)\n>>> foo()\n()\n>>>\n>>> def bar(**kwargs):\n        print(kwargs)\n\n>>> bar(x=1,y=2)\n{'y': 2, 'x': 1}\n>>> bar(x=1,y=2,z=3)\n{'y': 2, 'x': 1, 'z': 3}\n>>> bar()\n{}\n>>> \n```\n\nVariable argument functions are sometimes useful as a technique for\nreducing or simplifying the amount of code you need to type.  In this\nexercise, we'll explore that idea for simple data structures.\n\n## (a) Simplified Data Structures\n\nIn earlier exercises, you defined a class representing a stock like\nthis:\n\n```python\nclass Stock:\n    def __init__(self,name,shares,price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n```\n\nFocus on the `__init__()` method---isn't that a lot of\ncode to type each time you want to populate a structure?   What if you\nhad to define dozens of such structures in your program?\n\nIn a file `structure.py`, define a base class\n`Structure` that allows the user to define simple\ndata structures as follows:\n\n```python\nclass Stock(Structure):\n    _fields = ('name','shares','price')\n\nclass Date(Structure):\n    _fields = ('year', 'month', 'day')\n```\n  \nThe `Structure` class should define an `__init__()`\nmethod that takes any number of arguments and which looks for the\npresence of a `_fields` class variable.  Have the method\npopulate the instance from the attribute names in `_fields`\nand values passed to `__init__()`.\n\nHere is some sample code to test your implementation:\n\n```python\n>>> s = Stock('GOOG',100,490.1)\n>>> s.name\n'GOOG'\n>>> s.shares\n100\n>>> s.price\n490.1\n>>> s = Stock('AA',50)\nTraceback (most recent call last):\n...\nTypeError: Expected 3 arguments\n>>>\n```\n\n## (b) Making a Useful Representation\n\nModify the `Structure` class so that it produces a nice \nrepresentation when `repr()` is used.  For example:\n\n```python\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s\nStock('GOOG',100,490.1)\n>>>\n```\n\n## (c) Restricting Attribute Names\n\nGive the `Structure` class a `__setattr__()` method that restricts\nthe allowed set of attributes to those listed in the `_fields` variable.\nHowever, it should still allow any \"private\" attribute (e.g., name starting\nwith `_` to be set). \n\nFor example:\n\n```python\n>>> s = Stock('GOOG',100,490.1)\n>>> s.shares = 50\n>>> s.share = 50\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"structure.py\", line 13, in __setattr__\n    raise AttributeError('No attribute %s' % name)\nAttributeError: No attribute share\n>>> s._shares = 100     # Private attribute. OK\n>>>\n```\n\n## (d) Starting Over\n\nCreate a new file `stock.py` (or delete all of your previous code). Start over by defining `Stock` as follows:\n\n```python\n# stock.py\n\nfrom structure import Structure\n\nclass Stock(Structure):\n    _fields = ('name', 'shares', 'price')\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n```\n\nOnce you've done this, run your `teststock.py` unit tests.   You should get a lot of failures, but at least a\nhandful of the tests should pass.\n\n\\[ [Solution](soln6_1.md) | [Index](index.md) | [Exercise 5.6](ex5_6.md) | [Exercise 6.2](ex6_2.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex6_2.md",
    "content": "\\[ [Index](index.md) | [Exercise 6.1](ex6_1.md) | [Exercise 6.3](ex6_3.md) \\]\n\n# Exercise 6.2\n\n*Objectives:*\n\n- Learn more about scoping rules\n- Learn some scoping tricks\n\n*Files modified:* `structure.py`, `stock.py`\n\nIn the last exercise, you created a class `Structure` that made it easy to define\ndata structures.  For example:\n\n```python\nclass Stock(Structure):\n    _fields = ('name','shares','price')\n```\n\nThis works fine except that a lot of things are pretty weird about the `__init__()`\nfunction.   For example, if you ask for help using `help(Stock)`, you don't get \nany kind of useful signature.  Also, keyword argument passing doesn't work. For\nexample:\n\n```python\n>>> help(Stock)\n... look at output ...\n\n>>> s = Stock(name='GOOG', shares=100, price=490.1)\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: __init__() got an unexpected keyword argument 'price'\n>>> \n```\n\nIn this exercise, we're going to look at a different approach to the problem.\n\n## (a) Show me your locals\n\nFirst, try an experiment by defining the following class:\n\n```python\n>>> class Stock:\n        def __init__(self, name, shares, price):\n            print(locals())\n\n>>>\n```\n\nNow, try running this:\n\n```python\n>>> s = Stock('GOOG', 100, 490.1)\n{'self': <__main__.Stock object at 0x100699b00>, 'price': 490.1, 'name': 'GOOG', 'shares': 100}\n>>>\n```\n\nNotice how the locals dictionary contains all of the arguments passed\nto `__init__()`. That's interesting.  Now, define the following function\nand class definitions:\n\n```python\n>>> def _init(locs):\n        self = locs['self']\n        for name, val in locs.items():\n            if name == 'self': continue\n            setattr(self, name, val)\n\n>>> class Stock:\n        def __init__(self, name, shares, price):\n            _init(locals())\n```\n\nIn this code, the `_init()` function is used to automatically\ninitialize an object from a dictionary of passed local variables.\nYou'll find that `help(Stock)` and keyword arguments work perfectly.\n\n```python\n>>> s = Stock(name='GOOG', price=490.1, shares=50)\n>>> s.name\n'GOOG'\n>>> s.shares\n50\n>>> s.price\n490.1\n>>>\n```\n\n## (b) Frame Hacking\n\nOne complaint about the last part is that the `__init__()` function\nnow looks pretty weird with that call to `locals()` inserted into it.\nYou can get around that though if you're willing to do a bit of stack\nframe hacking.  Try this variant of the `_init()` function:\n\n```python\n>>> import sys\n>>> def _init():\n        locs = sys._getframe(1).f_locals   # Get callers local variables\n        self = locs['self']\n        for name, val in locs.items():\n            if name == 'self': continue\n            setattr(self, name, val)\n>>>\n```\n\nIn this code, the local variables are extracted from the stack frame of the caller.\nHere is a modified class definition:\n\n```python\n>>> class Stock:\n        def __init__(self, name, shares, price):\n            _init()\n\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s.name\n'GOOG'\n>>> s.shares\n100\n>>>\n```\n\nAt this point, you're probably feeling rather disturbed.  Yes, you just wrote a function that reached\ninto the stack frame of another function and examined its local variables.    \n\n## (c) Putting it Together\n\nTaking the ideas in the first two parts, delete the `__init__()` method that was originally part of the\n`Structure` class.  Next, add an `_init()` method like this:\n\n```python\n# structure.py\nimport sys\n\nclass Structure:\n    ...\n    @staticmethod\n    def _init():\n        locs = sys._getframe(1).f_locals\n        self = locs['self']\n        for name, val in locs.items():\n            if name == 'self': continue\n            setattr(self, name, val)\n    ...\n```\n\nNote: The reason this is defined as a `@staticmethod` is that the `self` argument\nis obtained from the locals--there's no need to additionally have it passed as\nan argument to the method itself (admittedly this is a bit subtle).\n\nNow, modify your `Stock` class so that it looks like the following:\n\n```python\n# stock.py\nfrom structure import Structure\n\nclass Stock(Structure):\n    _fields = ('name','shares','price')\n    def __init__(self, name, shares, price):\n        self._init()\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, shares):\n        self.shares -= shares\n```\n\nVerify that the class works properly, supports keyword arguments, and has a \nproper help signature.\n\n```python\n>>> s = Stock(name='GOOG', price=490.1, shares=50)\n>>> s.name\n'GOOG'\n>>> s.shares\n50\n>>> s.price\n490.1\n>>> help(Stock)\n... look at the output ...\n>>>\n```\n\nRun your unit tests in `teststock.py` again.  You should see at least one more test pass. Yay!\n\nAt this point, it's going to look like we just took a giant step backwards.  Not\nonly do the classes need the `__init__()` method, they also need the `_fields`\nvariable for some of the other methods to work (`__repr__()` and `__setattr__()`).  Plus,\nthe use of `self._init()` looks pretty hacky.    We'll work on this, but be patient.\n\n\\[ [Solution](soln6_2.md) | [Index](index.md) | [Exercise 6.1](ex6_1.md) | [Exercise 6.3](ex6_3.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex6_3.md",
    "content": "\\[ [Index](index.md) | [Exercise 6.2](ex6_2.md) | [Exercise 6.4](ex6_4.md) \\]\n\n# Exercise 6.3\n\n*Objectives:*\n\n- Learn how to inspect the internals of functions\n\n*Files Modified:* `structure.py`\n\n## (a) Inspecting functions\n\nDefine a simple function:\n\n```python\n>>> def add(x,y):\n       'Adds two things'\n       return x+y\n\n>>>\n```\n\nDo a `dir()` on the function to look at its attributes.\n\n```python\n>>> dir(add)\n... look at the result ...\n>>>\n```\n\nGet some basic information such as the function name, defining module name, and documentation string.\n\n```python\n>>> add.__name__\n'add'\n>>> add.__module__\n'__main__'\n>>> add.__doc__\n'Adds two things'\n>>>\n```\n\nThe `__code__` attribute of a function has low-level information about\nthe function implementation.  See if you can look at this and\ndetermine the number of required arguments and names of local\nvariables.\n\n## (b) Using the inspect module\n\nUse the inspect module to get calling information about the function:\n\n```python\n>>> import inspect\n>>> sig = inspect.signature(add)\n>>> sig\n<Signature (x, y)>\n>>> sig.parameters\nmappingproxy(OrderedDict([('x', <Parameter \"x\">), ('y', <Parameter \"y\">)]))\n>>> tuple(sig.parameters)\n('x', 'y')\n>>>\n```\n\n## (c) Putting it Together\n\nIn [Exercise 6.1](ex6_1.md), you created a class `Structure`\nthat defined a generalized `__init__()`, `__setattr__()`, and `__repr__()`\nmethod.   That class required a user to define a `_fields` class\nvariable like this:\n\n```python\nclass Stock(Structure):\n    _fields = ('name','shares','price')\n```\n\nThe problem with this class is that the `__init__()` function didn't\nhave a useful argument signature for the purposes of help and\nkeyword argument passing.  In [Exercise 6.2](ex6_2.md), you \ndid a sneaky trick involving a special `self._init()` function. For example:\n\n```python\nclass Stock(Structure):\n    _fields = ('name', 'shares', 'price')\n    def __init__(self, name, shares, price):\n        self._init()\n    ...\n```\n\nThis gave a useful signature, but now the class is just weird because\nthe user has to provide both the `_fields` variable and the `__init__()` method.\n\nYour task is to eliminate the `_fields` variable using some function\ninspection techniques.  First, notice that you can get the argument\nsignature from `Stock` as follows:\n\n```python\n>>> import inspect\n>>> sig = inspect.signature(Stock)\n>>> tuple(sig.parameters)\n('name', 'shares', 'price')\n>>>\n```\n\nPerhaps you could set the `_fields` variable from the argument signature\nof `__init__()`.   Add a class method `set_fields(cls)` to `Structure` that\ninspects the `__init__()` function, and sets the `_fields`\nvariable appropriately.   You should use your new function like this:\n\n```python\nclass Stock(Structure):\n    def __init__(self, name, shares, price):\n        self._init()\n\n    ...\n\nStock.set_fields()\n```\n\nThe resulting class should work the same way as before:\n\n```python\n>>> s = Stock(name='GOOG', shares=100, price=490.1)\n>>> s\nStock('GOOG',100,490.1)\n>>> s.shares = 50\n>>> s.share = 50\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"structure.py\", line 12, in __setattr__\n    raise AttributeError('No attribute %s' % name)\nAttributeError: No attribute share\n>>> \n```\n\nVerify the slightly modified `Stock` class with your unit tests again.  There will still\nbe failures, but nothing should change from the previous exercise.\n\nAt this point, it's all still a bit \"hacky\", but you're making \nprogress.  You have a Stock structure class with a useful `__init__()`\nfunction, there is a useful representation string, and the\n`__setattr__()` method restricts the set of attribute names.   The\nextra step of having to invoke `set_fields()` is a bit odd, but we'll\nget back to that.\n\n\\[ [Solution](soln6_3.md) | [Index](index.md) | [Exercise 6.2](ex6_2.md) | [Exercise 6.4](ex6_4.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex6_4.md",
    "content": "\\[ [Index](index.md) | [Exercise 6.3](ex6_3.md) | [Exercise 6.5](ex6_5.md) \\]\n\n# Exercise 6.4\n\n*Objectives:*\n\n- Learn to create code with `exec()`\n\n## (a) Experiment with exec()\n\nDefine a fragment of Python code in a string and try running it:\n\n```python\n>>> code = '''\nfor i in range(n):\n    print(i, end=' ')\n'''\n>>> n = 10\n>>> exec(code)\n0 1 2 3 4 5 6 7 8 9\n>>>\n```\n\nThat's interesting, but executing random code fragments is not\nespecially useful. A more interesting use of `exec()` is in making\ncode such as functions, methods, or classes.  Try this example in\nwhich we make an `__init__()` function for a class.\n\n```python\n>>> class Stock:\n        _fields = ('name', 'shares', 'price')\n\n>>> argstr = ','.join(Stock._fields)\n>>> code = f'def __init__(self, {argstr}):\\n'\n>>> for name in Stock._fields:\n        code += f'    self.{name} = {name}\\n'\n>>> print(code)\ndef __init__(self, name,shares,price):\n    self.name = name\n    self.shares = shares\n    self.price = price\n\n>>> locs = { }\n>>> exec(code, locs)\n>>> Stock.__init__ = locs['__init__']\n\n>>> # Now try the class\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s.name\n'GOOG'\n>>> s.shares\n100\n>>> s.price\n490.1\n>>> \n```\n\nIn this example, an `__init__()` function is made directly from the `_fields` variable.  \nThere are no weird hacks involving a special `_init()` method or stack frames.\n\n## (b) Creating an `__init__()` function\n\nIn [Exercise 6.3](ex6_3.md), you wrote code that inspected the\nsignature of the `__init__()` method to set the attribute names\nin a `_fields` class variable.   For example:\n\n```python\nclass Stock(Structure):\n    def __init__(self, name, shares, price):\n        self._init()\n\nStock.set_fields()\n```\n\nInstead of inspecting the `__init__()` method, write a class method\n`create_init(cls)` that creates an `__init__()` method from the value of\n`_fields`.  Use the `exec()` function to do this as shown above. \nHere's how a user will use it:\n\n```python\nclass Stock(Structure):\n    _fields = ('name', 'shares', 'price')\n\nStock.create_init()\n```\n\nThe resulting class should work exactly the same way as before:\n\n```python\n>>> s = Stock(name='GOOG', shares=100, price=490.1)\n>>> s\nStock('GOOG',100,490.1)\n>>> s.shares = 50\n>>> s.share = 50\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"structure.py\", line 12, in __setattr__\n    raise AttributeError('No attribute %s' % name)\nAttributeError: No attribute share\n>>> \n```\n\nModify the `Stock` class in progress to use the `create_init()` function as shown.  \nVerify with your unit tests as before.\n\nWhile you're at it, get rid of the `_init()` and `set_fields()`\nmethods on the `Structure` class--that approach was kind of weird.\n\n## (c) Named Tuples\n\nIn [Exercise 2.1](ex2_1.md), you experimented with `namedtuple` objects\nin the `collections` module.  Just to refresh your memory, here is how\nthey worked:\n\n```python\n>>> from collections import namedtuple\n>>> Stock = namedtuple('Stock', ['name', 'shares', 'price'])\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s.name\n'GOOG'\n>>> s.shares\n100\n>>> s[1]\n100\n>>>\n```\n\nUnder the covers, the `namedtuple()` function is creating code as a string\nand executing it using `exec()`.   Look at the code and marvel:\n\n```python\n>>> import inspect\n>>> print(inspect.getsource(namedtuple))\n... look at the output ...\n>>>\n```\n\n\\[ [Solution](soln6_4.md) | [Index](index.md) | [Exercise 6.3](ex6_3.md) | [Exercise 6.5](ex6_5.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex6_5.md",
    "content": "\\[ [Index](index.md) | [Exercise 6.4](ex6_4.md) | [Exercise 7.1](ex7_1.md) \\]\n\n# Exercise 6.5\n\n*Objectives:*\n\n- Learn how to define a proper callable object\n\nFiles Modified :  `validate.py`\n\nBack in [Exercise 4.3](ex4_3.md), you created a series of `Validator` classes\nfor performing different kinds of type and value checks.  For example:\n\n```python\n>>> from validate import Integer\n>>> Integer.check(1)\n>>> Integer.check('hello')\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"validate.py\", line 21, in check\n    raise TypeError(f'Expected {cls.expected_type}')\nTypeError: Expected <class 'int'>\n>>>\n```\n\nYou could use the validators in functions like this:\n\n```python\n>>> def add(x, y):\n        Integer.check(x)\n        Integer.check(y)\n        return x + y\n\n>>>\n```\n\nIn this exercise, we're going to take it just one step further. \n\n## (a) Creating a Callable Object\n\nIn the file `validate.py`, start by creating a class like this:\n\n```python\n# validate.py\n...\n\nclass ValidatedFunction:\n    def __init__(self, func):\n        self.func = func\n\n    def __call__(self, *args, **kwargs):\n        print('Calling', self.func)\n        result = self.func(*args, **kwargs)\n        return result\n```\n\nTest the class by applying it to a function:\n\n```python\n>>> def add(x, y):\n        return x + y\n\n>>> add = ValidatedFunction(add)\n>>> add(2, 3)\nCalling <function add at 0x1014df598>\n5\n>>>\n```\n\n## (b) Enforcement\n\nModify the `ValidatedFunction` class so that it enforces value checks\nattached via function annotations.  For example:\n\n```python\n>>> def add(x: Integer, y:Integer):\n        return x + y\n>>> add = ValidatedFunction(add)\n>>> add(2,3)\n5\n>>> add('two','three')\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"validate.py\", line 67, in __call__\n    self.func.__annotations__[name].check(val)\n  File \"validate.py\", line 21, in check\n    raise TypeError(f'Expected {cls.expected_type}')\nTypeError: expected <class 'int'>\n>>>>\n```\n\nHint: To do this, play around with signature binding. Use the `bind()`\nmethod of `Signature` objects to bind function arguments to argument\nnames.  Then cross reference this information with the\n`__annotations__` attribute to get the different validator classes.\n\nKeep in mind, you're making an object that looks like a function, but\nit's really not.  There is magic going on behind the scenes.\n\n## (c) Use as a Method (Challenge)\n\nA custom callable often presents problems if used as a custom method.\nFor example, try this:\n\n```python\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n    \n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares:Integer):\n        self.shares -= nshares\n    sell = ValidatedFunction(sell)     # Fails\n```\n\nYou'll find that the wrapped `sell()` fails miserably:\n\n```python\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s.sell(10)\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"validate.py\", line 64, in __call__\n    bound = self.signature.bind(*args, **kwargs)\n  File \"/usr/local/lib/python3.6/inspect.py\", line 2933, in bind\n    return args[0]._bind(args[1:], kwargs)\n  File \"/usr/local/lib/python3.6/inspect.py\", line 2848, in _bind\n    raise TypeError(msg) from None\nTypeError: missing a required argument: 'nshares'\n>>> \n```\n\nBonus: Figure out why it fails--but don't spend too much time fooling around with it.\n\n\\[ [Solution](soln6_5.md) | [Index](index.md) | [Exercise 6.4](ex6_4.md) | [Exercise 7.1](ex7_1.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex7_1.md",
    "content": "\\[ [Index](index.md) | [Exercise 6.5](ex6_5.md) | [Exercise 7.2](ex7_2.md) \\]\n\n# Exercise 7.1\n\n*Objectives:*\n\n- Learn how to define simple decorator functions.\n\n*Files Created:* `logcall.py`\n\n*Files Modified:* `validate.py`\n\n## (a) Your First Decorator\n\nTo start with decorators, write a _very_ simple decorator\nfunction that simply prints out a message each time a function is\ncalled.  Create a file `logcall.py` and define the following\nfunction:\n\n```python\n# logcall.py\n\ndef logged(func):\n    print('Adding logging to', func.__name__)\n    def wrapper(*args, **kwargs):\n        print('Calling', func.__name__)\n        return func(*args, **kwargs)\n    return wrapper\n```\n\nNow, make a separate file `sample.py` and apply it to a\nfew function definitions:\n\n```python\n# sample.py\n\nfrom logcall import logged\n\n@logged\ndef add(x,y):\n    return x+y\n\n@logged\ndef sub(x,y):\n    return x-y\n```\n\nTest your code as follows:\n\n```python\n>>> import sample\nAdding logging to add\nAdding logging to sub\n>>> sample.add(3,4)\nCalling add\n7\n>>> sample.sub(2,3)\nCalling sub\n-1\n>>> \n```\n\n## (b) A Real Decorator\n\nIn [Exercise 6.5](ex6_5.md), you created a callable class `ValidatedFunction` that\nenforced type annotations.  Rewrite this class as a decorator function called `validated`.\nIt should allow you to write code like this:\n\n```python\nfrom validate import Integer, validated\n\n@validated\ndef add(x: Integer, y:Integer) -> Integer:\n    return x + y\n\n@validated\ndef pow(x: Integer, y:Integer) -> Integer:\n    return x ** y\n```\n\nHere's how the decorated functions should work:\n\n```python\n>>> add(2, 3)\n5\n>>> add('2', '3')\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"validate.py\", line 75, in wrapper\n    raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\nTypeError: Bad Arguments\n    x: Expected <class 'int'>\n    y: Expected <class 'int'>\n\n>>> pow(2, 3)\n8\n>>> pow(2, -1)\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"validate.py\", line 83, in wrapper\n    raise TypeError(f'Bad return: {e}') from None\nTypeError: Bad return: Expected <class 'int'>\n>>>\n```\n\nYour decorator should try to patch up the exceptions so that they\nshow more useful information as shown.  Also, the `@validated`\ndecorator should work in classes (you don't need to do anything special).\n\n```python\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n    \n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    @validated\n    def sell(self, nshares:PositiveInteger):\n        self.shares -= nshares\n```\n\nNote:  This part doesn't involve a lot of code, but there are a lot of low-level\nfiddly bits.  The solution will look almost the same as for Exercise 6.5.  Don't\nbe shy about looking at solution code though.\n\n\\[ [Solution](soln7_1.md) | [Index](index.md) | [Exercise 6.5](ex6_5.md) | [Exercise 7.2](ex7_2.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex7_2.md",
    "content": "\\[ [Index](index.md) | [Exercise 7.1](ex7_1.md) | [Exercise 7.3](ex7_3.md) \\]\n\n# Exercise 7.2\n\n*Objectives:*\n\n- Decorator chaining\n- Defining decorators that accept arguments.\n\n*Files Modified:* `logcall.py`, `validate.py`\n\n## (a) Copying Metadata\n\nWhen a function gets wrapped by a decorator, you often lose\ninformation about the name of the function, documentation strings, and\nother details.  Verify this:\n\n```python\n>>> @logged\n    def add(x,y):\n        'Adds two things'\n        return x+y\n\n>>> add\n<function wrapper at 0x4439b0>\n>>> help(add)\n... look at the output ...\n>>>\n```\n\nFix the definition of the `logged` decorator so that it copies\nfunction metadata properly.  To do this, use the `@wraps(func)`\ndecorator as shown in the notes.\n\nAfter you're done, make sure the decorator preserves the function name\nand doc string.\n\n```python\n>>> @logged\n    def add(x,y):\n        'Adds two things'\n        return x+y\n\n>>> add\n<function add at 0x4439b0>\n>>> add.__doc__\n'Adds two things'\n>>>\n```\n\nFix the `@validated` decorator you wrote earlier so that it also preserves\nmetadata using `@wraps(func)`.\n\n## (b) Your first decorator with arguments\n\nThe `@logged` decorator you defined earlier always just\nprints a simple message with the function name.\nSuppose that you wanted the user to be able to specify a \ncustom message of some sort.\n\nDefine a new decorator `@logformat(fmt)` that accepts\na format string as an argument and uses `fmt.format(func=func)` to\nformat a supplied function into a log message:\n\n```python\n# sample.py\n...\nfrom logcall import logformat\n\n@logformat('{func.__code__.co_filename}:{func.__name__}')\ndef mul(x,y):\n    return x*y\n```\n\nTo do this, you need to define a decorator that takes an argument.\nThis is what it should look like when you test it:\n\n```python\n>>> import sample\nAdding logging to add\nAdding logging to sub\nAdding logging to mul\n>>> sample.add(2,3)\nCalling add\n5\n>>> sample.mul(2,3)\nsample.py:mul\n6\n>>>\n```\n\nTo further simplify the code, show how you can define the original `@logged` decorator\nusing the the `@logformat` decorator. \n\n## (c) Multiple decorators and methods\n\nThings can get a bit dicey when decorators are applied to methods in a\nclass.  Try applying your `@logged` decorator to the methods in the\nfollowing class.\n\n```python\nclass Spam:\n    @logged\n    def instance_method(self):\n        pass\n\n    @logged\n    @classmethod\n    def class_method(cls):\n        pass\n\n    @logged\n    @staticmethod\n    def static_method():\n        pass\n\n    @logged\n    @property\n    def property_method(self):\n        pass\n```\n\nDoes it even work at all? (hint: no). Is there any way to fix the code so\nthat it works?  For example, can you make it so the following example\nworks?\n\n```python\n>>> s = Spam()\n>>> s.instance_method()\ninstance_method\n>>> Spam.class_method()\nclass_method\n>>> Spam.static_method()\nstatic_method\n>>> s.property_method\nproperty_method\n>>>\n```\n\n## (d) Validation (Redux)\n\nIn the last exercise, you wrote a `@validated` decorator that enforced\ntype annotations. For example:\n\n```python\n@validated\ndef add(x: Integer, y:Integer) -> Integer:\n    return x + y\n```\n\nMake a new decorator `@enforce()` that enforces types specified\nvia keyword arguments to the decorator instead.  For example:\n\n```python\n@enforce(x=Integer, y=Integer, return_=Integer)\ndef add(x, y):\n    return x + y\n```\n\nThe resulting behavior of the decorated function should be identical.\nNote: Make the `return_` keyword specify the return type.  `return` is\na Python reserved word so you have to pick a slightly different name.\n\n\n**Discussion**\n\nWriting robust decorators is often a lot harder than it looks.\nRecommended 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)\n\n\\[ [Solution](soln7_2.md) | [Index](index.md) | [Exercise 7.1](ex7_1.md) | [Exercise 7.3](ex7_3.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex7_3.md",
    "content": "\\[ [Index](index.md) | [Exercise 7.2](ex7_2.md) | [Exercise 7.4](ex7_4.md) \\]\n\n# Exercise 7.3\n\n*Objectives:*\n\n- Learn about class decorators\n- Descriptors revisited\n\n*Files Modified:* `validate.py`, `structure.py`\n\nThis exercise is going to pull together a bunch of topics we've\ndeveloped over the last few days. Hang on to your hat.\n\n## (a) Descriptors Revisited\n\nIn [Exercise 4.3](ex4_3.md) you defined some descriptors that\nallowed a user to define classes with type-checked attributes like\nthis:\n\n```python\nfrom validate import String, PositiveInteger, PositiveFloat\n\nclass Stock:\n    name   = String()\n    shares = PositiveInteger()\n    price  = PositiveFloat()\n    ...\n```\n\nModify your `Stock` class so that it includes the above descriptors\nand now looks like this (see [Exercise 6.4](ex6_4.md)):\n\n```python\n# stock.py\n\nfrom structure import Structure\nfrom validate import String, PositiveInteger, PositiveFloat\n\nclass Stock(Structure):\n    _fields = ('name', 'shares', 'price')\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\nStock.create_init()\n```\n\nRun the unit tests in `teststock.py`.  You should see a significant\nnumber of tests passing with the addition of type checking.\nExcellent.\n\n## (b) Using Class Decorators to Fill in Details\n\nAn annoying aspect of the above code is there are extra details such as \n`_fields` variable and the final step of `Stock.create_init()`.  A lot\nof this could be packaged into a class decorator instead.\n\nIn the file `structure.py`, make a class decorator `@validate_attributes`\nthat examines the class body for instances of Validators and fills in\nthe `_fields` variable.  For example:\n\n```python\n# structure.py\n\nfrom validate import Validator\n\ndef validate_attributes(cls):\n    validators = []\n    for name, val in vars(cls).items():\n        if isinstance(val, Validator):\n            validators.append(val)\n    cls._fields = [val.name for val in validators]\n    return cls\n```\n\nThis code relies on the fact that class dictionaries are ordered\nstarting in Python 3.6.  Thus, it will encounter the different\n`Validator` descriptors in the order that they're listed.  Using this\norder, you can then fill in the `_fields` variable.   This allows\nyou to write code like this:\n\n```python\n# stock.py\n\nfrom structure import Structure, validate_attributes\nfrom validate import String, PositiveInteger, PositiveFloat\n\n@validate_attributes\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\nStock.create_init()\n```\n\nOnce you've got this working, modify the `@validate_attributes`\ndecorator to additionally perform the final step of calling\n`Stock.create_init()`.   This will reduce the class to the\nfollowing:\n\n```python\n# stock.py\n\nfrom structure import Structure, validate_attributes\nfrom validate import String, PositiveInteger, PositiveFloat\n\n@validate_attributes\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n```\n\n## (c) Applying Decorators via Inheritance\n\nHaving to specify the class decorator itself is kind of annoying.  Modify the\n`Structure` class with the following `__init_subclass__()` method:\n\n```python\n# structure.py\n\nclass Structure:\n    ...\n    @classmethod\n    def __init_subclass__(cls):\n        validate_attributes(cls)\n```\n\nOnce you've made this change, you should be able to drop the decorator entirely and\nsolely rely on inheritance. It's inheritance plus some hidden magic!\n\n```python\n# stock.py\n\nfrom structure import Structure\nfrom validate import String, PositiveInteger, PositiveFloat\n\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n```\n\nNow, the code is really starting to go places. In fact, it almost\nlooks normal.  Let's keep pushing it.\n\n## (d) Row Conversion\n\nOne missing feature from the `Structure` class is a `from_row()` method that\nallows it to work with earlier CSV reading code.   Let's fix that.  Give the\n`Structure` class a `_types` class variable and the following class method:\n\n```python\n# structure.py\n\nclass Structure:\n    _types = ()\n    ...\n    @classmethod\n    def from_row(cls, row):\n        rowdata = [ func(val) for func, val in zip(cls._types, row) ]\n        return cls(*rowdata)\n    ...\n```\n\nModify the `@validate_attributes` decorator so that it examines the\nvarious validators for an `expected_type` attribute and uses it to \nfill in the `_types` variable above.\n\nOnce you've done this, you should be able to do things like this:\n\n```python\n>>> s = Stock.from_row(['GOOG', '100', '490.1'])\n>>> s\nStock('GOOG', 100, 490.1)\n>>> import reader\n>>> port = reader.read_csv_as_instances('Data/portfolio.csv', Stock)\n>>>\n```\n\n## (e) Method Argument Checking\n\nRemember that `@validated` decorator you wrote in the last part?\nLet's modify the `@validate_attributes` decorator so that any method\nin the class with annotations gets wrapped by `@validated`\nautomatically.  This allows you to put enforced annotations on methods\nsuch as the `sell()` method:\n\n```python\n# stock.py\n\nfrom structure import Structure\nfrom validate import String, PositiveInteger, PositiveFloat\n\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares: PositiveInteger):\n        self.shares -= nshares\n```\n\nYou'll find that `sell()` now enforces the argument.\n\n```python\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s.sell(25)\n>>> s.sell(-25)\nTraceback (most recent call last):\n  ...\nTypeError: Bad Arguments\n  nshares: must be >= 0\n>>>\n```\n\nYes, this starting to get very interesting now.  The combination of a class decorator and\ninheritance is a powerful force.\n\n\\[ [Solution](soln7_3.md) | [Index](index.md) | [Exercise 7.2](ex7_2.md) | [Exercise 7.4](ex7_4.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex7_4.md",
    "content": "\\[ [Index](index.md) | [Exercise 7.3](ex7_3.md) | [Exercise 7.5](ex7_5.md) \\]\n\n# Exercise 7.4\n\n*Objectives:*\n\n- Learn about the low-level steps involved in creating a class\n\n*Files Modified:* `validate.py`, `structure.py`\n\nIn this exercise, we look at the mechanics of how classes are actually\ncreated. \n\n## (a) Class creation\n\nRecall, from earlier exercises, we defined a simple class\n`Stock` that looked like this:\n\n```python\nclass Stock:\n    def __init__(self,name,shares,price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n    def cost(self):\n        return self.shares*self.price\n    def sell(self,nshares):\n        self.shares -= nshares\n```\n\nWhat we're going to do here is create the class manually.  Start out\nby just defining the methods as normal Python functions.\n\n```python\n>>> def __init__(self,name,shares,price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n>>> def cost(self):\n        return self.shares*self.price\n\n>>> def sell(self,nshares):\n        self.shares -= nshares\n\n>>>\n```\n\nNext, make a methods dictionary:\n\n```python\n>>> methods = {\n         '__init__' : __init__,\n         'cost' : cost,\n         'sell' : sell } \n\n>>>\n```\n\nFinally, create the `Stock` class object:\n\n```python\n>>> Stock = type('Stock',(object,),methods)\n>>> s = Stock('GOOG',100,490.10)\n>>> s.name\n'GOOG'\n>>> s.cost()\n49010.0\n>>> s.sell(25)\n>>> s.shares\n75\n>>>\n```\n\nCongratulations, you just created a class.   A class is really nothing\nmore than a name, a tuple of base classes, and a dictionary holding\nall of the class contents. `type()` is a constructor that\ncreates a class for you if you supply these three parts.\n\n## (b) Typed structures\n\nIn the `structure.py` file, define the following function:\n\n```python\n# structure.py\n\n...\ndef typed_structure(clsname, **validators):\n    cls = type(clsname, (Structure,), validators)\n    return cls\n```\n\nThis function is somewhat similar to the `namedtuple()` function in that it creates a class. Try it out:\n\n```python\n>>> from validate import String, PositiveInteger, PositiveFloat\n>>> from structure import typed_structure\n>>> Stock = typed_structure('Stock', name=String(), shares=PositiveInteger(), price=PositiveFloat())\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s.name\n'GOOG'\n>>> s\nStock('GOOG', 100, 490.1)\n>>>\n```\n\nYou might find the seams of your head starting to pull apart about now.\n\n## (c) Making a lot of classes\n\n\nThere are other situations where direct usage of the `type()` constructor might be advantageous.\nConsider this bit of code:\n\n```python\n# validate.py\n...\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        super().check(value)\n\nclass Integer(Typed):\n    expected_type = int\n\nclass Float(Typed):\n    expected_type = float\n\nclass String(Typed):\n    expected_type = str\n...\n```\n\nWow is the last part of that annoying and repetitive.  Change it\nto use a table of desired type classes like this:\n\n```python\n# validate.py\n...\n\n_typed_classes = [\n    ('Integer', int),\n    ('Float', float),\n    ('String', str) ]\n\nglobals().update((name, type(name, (Typed,), {'expected_type':ty}))\n                 for name, ty in _typed_classes)\n```\n\nNow, if you want to have more type classes, you just add them to the\ntable:\n\n```python\n_typed_classes = [\n    ('Integer', int),\n    ('Float', float),\n    ('Complex', complex),\n    ('Decimal', decimal.Decimal),\n    ('List', list),\n    ('Bool', bool),\n    ('String', str) ]\n```\n\nAdmit it, that's kind of cool and saves a lot of typing (at the keyboard).\n\n\\[ [Solution](soln7_4.md) | [Index](index.md) | [Exercise 7.3](ex7_3.md) | [Exercise 7.5](ex7_5.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex7_5.md",
    "content": "\\[ [Index](index.md) | [Exercise 7.4](ex7_4.md) | [Exercise 7.6](ex7_6.md) \\]\n\n# Exercise 7.5\n\n*Objectives:*\n\n- Create your first metaclass\n\n*Files Created:* `mymeta.py`\n\n## (a) Create your first metaclass\n\nCreate a file called `mymeta.py`\nand put the following code in it (from the slides):\n\n```python\n# mymeta.py\n\nclass mytype(type):\n    @staticmethod\n    def __new__(meta, name, bases, __dict__):\n        print(\"Creating class :\", name)\n        print(\"Base classes   :\", bases)\n        print(\"Attributes     :\", list(__dict__))\n        return super().__new__(meta, name, bases, __dict__)\n\nclass myobject(metaclass=mytype):\n    pass\n```\n\nOnce you've done this, define a class that inherits from\n`myobject` instead of object.  For example:\n\n```python\nclass Stock(myobject):\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n    def cost(self):\n        return self.shares * self.price\n    def sell(self, nshares):\n        self.shares -= nshares\n```\n\nTry running your code and creating instances of `Stock`.  See\nwhat happens. You should see the print statements from your\n`mytype` running once when the `Stock` class is\ndefined.\n\nWhat happens if you inherit from `Stock`?\n\n```python\nclass MyStock(Stock):\n    pass\n```\n\nYou should still see your metaclass at work.  Metaclasses are \"sticky\" in that they\nget applied across an entire inheritance hierarchy.\n\n**Discussion**\n\nWhy would you want to do something like this?\nThe main power of a metaclass is that it gives a programmer the ability\nto capture details about classes just prior to their creation. For\nexample, in the `__new__()` method, you are given all of the\nbasic details including the name of the class, base classes, and\nmethods data.   If you inspect this data, you can perform various\ntypes of diagnostic checks.  If you're more daring, you can modify the\ndata and change what gets placed in the class definition when it is\ncreated.   Needless to say, there are many opportunities for horrible\ndiabolical evil.\n\n\\[ [Solution](soln7_5.md) | [Index](index.md) | [Exercise 7.4](ex7_4.md) | [Exercise 7.6](ex7_6.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex7_6.md",
    "content": "\\[ [Index](index.md) | [Exercise 7.5](ex7_5.md) | [Exercise 8.1](ex8_1.md) \\]\n\n# Exercise 7.6\n\n*Objectives:*\n\n- Metaclasses in action\n- Explode your brain\n\n*Files Modified:* `structure.py`, `validate.py`\n\n## (a) The Final Frontier\n\nIn [Exercise 7.3](ex7_3.md), we made it possible to define type-checked structures as follows:\n\n```python\nfrom validate import String, PositiveInteger, PositiveFloat\nfrom structure import Structure\n\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares: PositiveInteger):\n        self.shares -= nshares\n```\n\nThere are a lot of things going on under the covers.  However, one annoyance\nconcerns all of those type-name imports at the top (e.g., `String`, `PositiveInteger`, etc.).\nThat's just the kind of thing that might lead to a `from validate import *` statement.\nOne interesting thing about a metaclass is that it can be used to control\nthe process by which a class gets defined.  This includes managing the\nenvironment of a class definition itself.  Let's tackle those imports.\n\nThe first step in managing all of the validator names is to collect\nthem.   Go to the file `validate.py` and modify the `Validator` base\nclass with this extra bit of code involving `__init_subclass__()` again:\n\n```python\n# validate.py\n\nclass Validator:\n    ...\n\n    # Collect all derived classes into a dict\n    validators = { }\n    @classmethod\n    def __init_subclass__(cls):\n        cls.validators[cls.__name__] = cls\n```\n\nThat's not much, but it's creating a little namespace of all of the `Validator`\nsubclasses.  Take a look at it:\n\n```python\n>>> from validate import Validator\n>>> Validator.validators\n{'Float': <class 'validate.Float'>,\n 'Integer': <class 'validate.Integer'>,\n 'NonEmpty': <class 'validate.NonEmpty'>,\n 'NonEmptyString': <class 'validate.NonEmptyString'>,\n 'Positive': <class 'validate.Positive'>,\n 'PositiveFloat': <class 'validate.PositiveFloat'>,\n 'PositiveInteger': <class 'validate.PositiveInteger'>,\n 'String': <class 'validate.String'>,\n 'Typed': <class 'validate.Typed'>}\n>>>\n```\n\nNow that you've done that, let's inject this namespace into namespace\nof classes defined from `Structure`. Define the following metaclass:\n\n```python\n# structure.py\n...\n\nfrom validate import Validator\nfrom collections import ChainMap\n\nclass StructureMeta(type):\n    @classmethod\n    def __prepare__(meta, clsname, bases):\n        return ChainMap({}, Validator.validators)\n        \n    @staticmethod\n    def __new__(meta, name, bases, methods):\n        methods = methods.maps[0]\n        return super().__new__(meta, name, bases, methods)\n\nclass Structure(metaclass=StructureMeta):\n    ...\n```\n\nIn this code, the `__prepare__()` method is making a special `ChainMap` mapping that consists\nof an empty dictionary and a dictionary of all of the defined validators.  The empty dictionary\nthat's listed first is going to collect all of the definitions made inside the class body.\nThe `Validator.validators` dictionary is going to make all of the type definitions available \nto for use as descriptors or argument type annotations.\n\nThe `__new__()` method discards extra the validator dictionary and\npasses the remaining definitions onto the type constructor.  It's\ningenious, but it lets you drop the annoying imports:\n\n```python\n# stock.py\n\nfrom structure import Structure\n\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares: PositiveInteger):\n        self.shares -= nshares\n```\n\n## (b) Stare in Amazement\n\nTry running your `teststock.py` unit tests across this new file. Most of them should be\npassing now.   For kicks, try your `Stock` class with some of the earlier code\nfor tableformatting and reading data.  It should all work.\n\n```python\n>>> from stock import Stock\n>>> from reader import read_csv_as_instances\n>>> portfolio = read_csv_as_instances('Data/portfolio.csv', Stock)\n>>> portfolio\n[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)]\n>>> from tableformat import create_formatter, print_table\n>>> formatter = create_formatter('text')\n>>> print_table(portfolio, ['name','shares','price'], formatter)\n      name     shares      price \n---------- ---------- ---------- \n        AA        100       32.2 \n       IBM         50       91.1 \n       CAT        150      83.44 \n      MSFT        200      51.23 \n        GE         95      40.37 \n      MSFT         50       65.1 \n       IBM        100      70.44 \n>>> \n```\n\nAgain, marvel at the final `stock.py` file and observe how clean the\ncode looks.  Just try not think about everything that is happening\nunder the hood with the `Structure` base class.\n\n\\[ [Solution](soln7_6.md) | [Index](index.md) | [Exercise 7.5](ex7_5.md) | [Exercise 8.1](ex8_1.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex8_1.md",
    "content": "\\[ [Index](index.md) | [Exercise 7.6](ex7_6.md) | [Exercise 8.2](ex8_2.md) \\]\n\n# Exercise 8.1\n\n*Objectives:*\n\n- Learn how to customize iteration using generators\n\n*Files Modified:* `structure.py`\n\n*Files Created:* `follow.py`\n\n## (a) A Simple Generator\n\nIf you ever find yourself wanting to customize iteration, you should\nalways think generator functions.  They're easy to write---simply make\na function that carries out the desired iteration logic and uses `yield`\nto emit values.\n\nFor example, try this generator that allows you to iterate over a\nrange of numbers with fractional steps (something not supported by\nthe `range()` builtin):\n\n```python\n>>> def frange(start,stop,step):\n        while start < stop:\n            yield start\n            start += step\n\n>>> for x in frange(0, 2, 0.25):\n        print(x, end=' ')\n\n0 0.25 0.5 0.75 1.0 1.25 1.5 1.75\n>>>\n```\n\nIterating on a generator is a one-time operation. For example, here's\nwhat happen if you try to iterate twice:\n\n```python\n>>> f = frange(0, 2, 0.25)\n>>> for x in f:\n        print(x, end=' ')\n\n0 0.25 0.5 0.75 1.0 1.25 1.5 1.75\n>>> for x in f:\n        print(x, end=' ')\n\n>>>\n```\n\nIf you want to iterate over the same sequence, you need to recreate the generator\nby calling `frange()` again.    Alternative, you could package everything into a class:\n\n```python\n>>> class FRange:\n        def __init__(self, start, stop, step):\n            self.start = start\n            self.stop = stop\n            self.step = step\n        def __iter__(self):\n            n = self.start\n            while n < self.stop:\n                yield n\n                n += self.step\n\n>>> f = FRange(0, 2, 0.25)\n>>> for x in f:\n        print(x, end=' ')\n\n0 0.25 0.5 0.75 1.0 1.25 1.5 1.75\n>>> for x in f:\n        print(x, end=' ')\n\n0 0.25 0.5 0.75 1.0 1.25 1.5 1.75\n>>>\n```\n\n## (b) Adding Iteration to Objects\n\nIf you've created a custom class, you can make it support iteration by\ndefining an `__iter__()` special method.  `__iter__()` returns an\niterator as a result.  As shown in the previous example, an easy way\nto do it is to define `__iter__()` as a generator.\n\nIn earlier exercises, you defined a `Structure` base class.\nAdd an `__iter__()` method to this class that produces the attribute values\nin order. For example:\n\n```python\nclass Structure(metaclass=StructureMeta):\n    ...\n    def __iter__(self):\n        for name in self._fields:\n            yield getattr(self, name)\n    ...\n```\n\nOnce you've done this, you should be able to iterate over the instance\nattributes like this:\n\n```python\n>>> from stock import Stock\n>>> s = Stock('GOOG', 100, 490.1)\n>>> for val in s:\n        print(val)\nGOOG\n100\n490.1\n>>>\n```\n\n## (c) The Surprising Power of Iteration\n\nPython uses iteration in ways you might not expect.   Once you've added `__iter__()`\nto the `Structure` class, you'll find that it is easy to do all sorts of new \noperations. For example, conversions to sequences and unpacking:\n\n```python\n>>> s = Stock('GOOG', 100, 490.1)\n>>> list(s)\n['GOOG', 100, 490.1]\n>>> tuple(s)\n('GOOG', 100, 490.1)\n>>> name, shares, price = s\n>>> name\n'GOOG'\n>>> shares\n100\n>>> price\n490.1\n>>> \n```\n\nWhile we're at it, we can now add a comparison operator to our `Structure`\nclass:\n\n```python\n# structure.py\nclass Structure(metaclass=StructureMeta):\n    ...\n    def __eq__(self, other):\n        return isinstance(other, type(self)) and tuple(self) == tuple(other)\n    ...\n```\n\nYou should now be able to compare objects:\n\n```python\n>>> a = Stock('GOOG', 100, 490.1)\n>>> b = Stock('GOOG', 100, 490.1)\n>>> a == b\nTrue\n>>>\n```\n\nTry running your `teststock.py` unit tests again.  Everything should be passing now.\nExcellent.\n\n## (d) Monitoring a streaming data source\n\nGenerators can also be a useful way to simply produce a stream of\ndata.  In this part, we'll explore this idea by writing a generator to\nwatch a log file.  To start, follow the next instructions carefully.\n\nThe program `Data/stocksim.py` is a program that\nsimulates stock market data.  As output, the program constantly writes\nreal-time data to a file `stocklog.csv`.  In a\ncommand window (not IDLE) go into the `Data/` directory and run this program:\n\n```\n% python3 stocksim.py\n```\n\nIf you are on Windows, just locate the `stocksim.py` program and\ndouble-click on it to run it.  Now, forget about this program (just\nlet it run).  Again, just let this program run in the background---it\nwill run for several hours (you shouldn't need to worry about it).\n\nOnce the above program is running, let's write a little program to\nopen the file, seek to the end, and watch for new output.  Create a\nfile `follow.py` and put this code in it:\n\n```python\n# follow.py\nimport os\nimport time\nf = open('Data/stocklog.csv')\nf.seek(0, os.SEEK_END)   # Move file pointer 0 bytes from end of file\n\nwhile True:\n    line = f.readline()\n    if line == '':\n        time.sleep(0.1)   # Sleep briefly and retry\n        continue\n    fields = line.split(',')\n    name = fields[0].strip('\"')\n    price = float(fields[1])\n    change = float(fields[4])\n    if change < 0:\n        print('%10s %10.2f %10.2f' % (name, price, change))\n```\n\nIf you run the program, you'll see a real-time stock ticker.  Under the covers,\nthis code is kind of like the Unix `tail -f` command that's used to watch a log file.\n\n**Note:** The use of the `readline()` method in this example is\nsomewhat unusual in that it is not the usual way of reading lines from\na file (normally you would just use a `for`-loop).  However, in\nthis case, we are using it to repeatedly probe the end of the file to\nsee if more data has been added (`readline()` will either\nreturn new data or an empty string).\n\nIf you look at the code carefully, the first part of the code is\nproducing lines of data whereas the statements at the end of the\n`while` loop are consuming the data.  A major feature of generator\nfunctions is that you can move all of the data production code into a\nreusable function.\n\nModify the code so that the file-reading is performed by \na generator function `follow(filename)`.   Make it so the following code\nworks:\n\n```python\n>>> for line in follow('Data/stocklog.csv'):\n          print(line, end='')\n   \n... Should see lines of output produced here ...\n```\n\nModify the stock ticker code so that it looks like this:\n\n```python\nfor line in follow('Data/stocklog.csv'):\n    fields = line.split(',')\n    name = fields[0].strip('\"')\n    price = float(fields[1])\n    change = float(fields[4])\n    if change < 0:\n        print('%10s %10.2f %10.2f' % (name, price, change))\n```\n\n**Discussion**\n\nSomething very powerful just happened here.  You moved an interesting iteration pattern\n(reading lines at the end of a file) into its own little function.   The `follow()` function\nis now this completely general purpose utility that you can use in any program.  For\nexample, you could use it to watch server logs, debugging logs, and other similar data sources.\nThat's kind of cool.\n\n\\[ [Solution](soln8_1.md) | [Index](index.md) | [Exercise 7.6](ex7_6.md) | [Exercise 8.2](ex8_2.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex8_2.md",
    "content": "\\[ [Index](index.md) | [Exercise 8.1](ex8_1.md) | [Exercise 8.3](ex8_3.md) \\]\n\n# Exercise 8.2\n\n*Objectives:*\n\n- Using generators to set up processing pipelines\n\n*Files Created:* `ticker.py`\n\n**Note**\n\nFor this exercise the `stocksim.py` program should still be\nrunning in the background.  You're going to use the `follow()`\nfunction you wrote in the previous exercise.\n\n## (a) Setting up a processing pipeline\n\nA major power of generators is that they allow you to create programs\nthat set up processing pipelines--much like pipes on Unix systems.\nExperiment with this concept by performing these steps:\n\n```python\n>>> from follow import follow\n>>> import csv\n>>> lines = follow('Data/stocklog.csv')\n>>> rows = csv.reader(lines)\n>>> for row in rows:\n        print(row)\n\n['BA', '98.35', '6/11/2007', '09:41.07', '0.16', '98.25', '98.35', '98.31', '158148']\n['AA', '39.63', '6/11/2007', '09:41.07', '-0.03', '39.67', '39.63', '39.31', '270224']\n['XOM', '82.45', '6/11/2007', '09:41.07', '-0.23', '82.68', '82.64', '82.41', '748062']\n['PG', '62.95', '6/11/2007', '09:41.08', '-0.12', '62.80', '62.97', '62.61', '454327']\n...\n```\n\nWell, that's interesting.  What you're seeing here is that the output of the\n`follow()` function has been piped into the `csv.reader()` function and we're\nnow getting a sequence of split rows.   \n\n## (b) Making more pipeline components\n\nIn a file `ticker.py`, define the following class (using your structure code from before) and set up\na pipeline:\n\n```python\n# ticker.py\n\nfrom structure import Structure\n\nclass Ticker(Structure):\n    name = String()\n    price = Float()\n    date = String()\n    time = String()\n    change = Float()\n    open = Float()\n    high = Float()\n    low = Float()\n    volume = Integer()\n\nif __name__ == '__main__':\n    from follow import follow\n    import csv\n    lines = follow('Data/stocklog.csv')\n    rows = csv.reader(lines)\n    records = (Ticker.from_row(row) for row in rows)\n    for record in records:\n        print(record)\n```\n\nWhen you run this, you should see some output like this:\n\n    Ticker('IBM',103.53,'6/11/2007','09:53.59',0.46,102.87,103.53,102.77,541633)\n    Ticker('MSFT',30.21,'6/11/2007','09:54.01',0.16,30.05,30.21,29.95,7562516)\n    Ticker('AA',40.01,'6/11/2007','09:54.01',0.35,39.67,40.15,39.31,576619)\n    Ticker('T',40.1,'6/11/2007','09:54.08',-0.16,40.2,40.19,39.87,1312959)\n\n## (c) Keep going\n\nOh, you can do better than that.  Let's plug this into your table generation code. Change\nthe program to the following:\n\n```python\n# ticker.py\n...\n\nif __name__ == '__main__':\n    from follow import follow\n    import csv\n    from tableformat import create_formatter, print_table\n\n    formatter = create_formatter('text')\n\n    lines = follow('Data/stocklog.csv')\n    rows = csv.reader(lines)\n    records = (Ticker.from_row(row) for row in rows)\n    negative = (rec for rec in records if rec.change < 0)\n    print_table(negative, ['name','price','change'], formatter)\n```\n\nThis should produce some output that looks like this:\n\n          name      price     change \n    ---------- ---------- ---------- \n             C      53.12      -0.21 \n           UTX      70.04      -0.19 \n           AXP      62.86      -0.18 \n           MMM      85.72      -0.22 \n           MCD      51.38      -0.03 \n           WMT      49.85      -0.23 \n            KO       51.6      -0.07 \n           AIG      71.39      -0.14 \n            PG      63.05      -0.02 \n            HD      37.76      -0.19 \n\nNow, THAT is crazy! And pretty awesome.\n\n**Discussion**\n\nSome lessons learned: You can create various generator functions and\nchain them together to perform processing involving data-flow\npipelines.  \n\nA good mental model for generator functions might be Lego blocks.\nYou can make a collection of small iterator patterns and start\nstacking them together in various ways.  It can be an extremely powerful way to program.\n\n\\[ [Solution](soln8_2.md) | [Index](index.md) | [Exercise 8.1](ex8_1.md) | [Exercise 8.3](ex8_3.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex8_3.md",
    "content": "\\[ [Index](index.md) | [Exercise 8.2](ex8_2.md) | [Exercise 8.4](ex8_4.md) \\]\n\n# Exercise 8.3\n\n*Objectives:*\n\n- Using coroutines to set up processing pipelines\n\n*Files Created:* `cofollow.py`, `coticker.py`\n\n**Note**\n\nFor this exercise the `stocksim.py` program should still be\nrunning in the background.  \n\nIn [Exercise 8.2](ex8_2.md) you wrote some code that used\ngenerators to set up a processing pipeline.  A key aspect of that\nprogram was the idea of data flowing between generator functions.  A\nvery similar kind of dataflow can be set up using coroutines.  The\nonly difference is that with a coroutine, you send data into different\nprocessing elements as opposed to pulling data out with a for-loop.\n\n## (a) A coroutine example\n\nGetting started with coroutines can be a little tricky.  Here is an\nexample program that performs the same task as\n[Exercise 8.2](ex8_2.md), but with coroutines.  Take this program\nand copy it into a file called `cofollow.py`.\n\n```python\n# cofollow.py\nimport os\nimport time\n\n# Data source\ndef follow(filename,target):\n    with open(filename,'r') as f:\n        f.seek(0,os.SEEK_END)\n        while True:\n            line = f.readline()\n            if line != '':\n                target.send(line)\n            else:\n                time.sleep(0.1)\n\n# Decorator for coroutine functions\nfrom functools import wraps\n\ndef consumer(func):\n    @wraps(func)\n    def start(*args,**kwargs):\n        f = func(*args,**kwargs)\n        f.send(None)\n        return f\n    return start\n\n# Sample coroutine\n@consumer\ndef printer():\n    while True:\n        item = yield     # Receive an item sent to me\n        print(item)\n\n# Example use\nif __name__ == '__main__':\n    follow('Data/stocklog.csv',printer())\n```\n\nRun this program and make sure produces output..   Make sure you understand how the different pieces are hooked together.\n\n## (b) Build some pipeline components\n\nIn a file `coticker.py`, build a series of pipeline components that carry out the same tasks as\nthe `ticker.py` program in [Exercise 8.2](ex8_2.md).  Here is the implementation of the\nvarious pieces.\n\n```python\n# coticker.py\nfrom structure import Structure\n\nclass Ticker(Structure):\n    name = String()\n    price =Float()\n    date = String()\n    time = String()\n    change = Float()\n    open = Float()\n    high = Float()\n    low = Float()\n    volume = Integer()\n\nfrom cofollow import consumer, follow\nfrom tableformat import create_formatter\nimport csv\n\n# This one is tricky. See solution for notes about it\n@consumer\ndef to_csv(target):\n    def producer():\n        while True:\n            yield line\n\n    reader = csv.reader(producer())\n    while True:\n        line = yield\n        target.send(next(reader))\n\n@consumer\ndef create_ticker(target):\n    while True:\n        row = yield\n        target.send(Ticker.from_row(row))\n\n@consumer\ndef negchange(target):\n    while True:\n        record = yield\n        if record.change < 0:\n            target.send(record)\n\n@consumer\ndef ticker(fmt, fields):\n    formatter = create_formatter(fmt)\n    formatter.headings(fields)\n    while True:\n        rec = yield\n        row = [getattr(rec, name) for name in fields]\n        formatter.row(row)\n```\n\nYour challenge: Write the main program that hooks all of these components together to \ngenerate the same stock ticker as in the previous exercise.\n\n\\[ [Solution](soln8_3.md) | [Index](index.md) | [Exercise 8.2](ex8_2.md) | [Exercise 8.4](ex8_4.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex8_4.md",
    "content": "\\[ [Index](index.md) | [Exercise 8.3](ex8_3.md) | [Exercise 8.5](ex8_5.md) \\]\n\n# Exercise 8.4\n\n*Objectives:*\n\n- Managing what happens at the `yield` statements\n\n*Files Modified:* `follow.py`, `cofollow.py`\n\n## (a) Closing a Generator\n\nA common question concerning generators is their lifetime and garbage\ncollection. For example, the `follow()` generator runs forever in\nan infinite `while` loop. What happens if the iteration loop that's\ndriving it stops?  Also, is there anyway to prematurely terminate the \ngenerator?\n\nModify the `follow()` function so that all of the code is enclosed in\na `try-except` block like this:\n\n```python\ndef follow(filename):\n    try:\n        with open(filename,'r') as f:\n            f.seek(0,os.SEEK_END)\n            while True:\n                 line = f.readline()\n                 if line == '':\n                     time.sleep(0.1)    # Sleep briefly to avoid busy wait\n                     continue\n                 yield line\n    except GeneratorExit:\n        print('Following Done')\n```\n\nNow, try a few experiments:\n\n```python\n>>> from follow import follow\n>>> # Experiment: Garbage collection of a running generator\n>>> f = follow('Data/stocklog.csv')\n>>> next(f)\n'\"MO\",70.29,\"6/11/2007\",\"09:30.09\",-0.01,70.25,70.30,70.29,365314\\n'\n>>> del f\nFollowing Done\n>>> # Experiment: Closing a generator\n>>> f = follow('Data/stocklog.csv')\n>>> for line in f:\n        print(line,end='')\n        if 'IBM' in line:\n            f.close()\n\n\"VZ\",42.91,\"6/11/2007\",\"09:34.28\",-0.16,42.95,42.91,42.78,210151\n\"HPQ\",45.76,\"6/11/2007\",\"09:34.29\",0.06,45.80,45.76,45.59,257169\n\"GM\",31.45,\"6/11/2007\",\"09:34.31\",0.45,31.00,31.50,31.45,582429\n...\n\"IBM\",102.86,\"6/11/2007\",\"09:34.44\",-0.21,102.87,102.86,102.77,147550\nFollowing Done\n>>> for line in f:\n        print(line, end='')    # No output: generator is done\n\n>>> \n```\n\nIn these experiments you can see that a `GeneratorExit` exception is\nraised when a generator is garbage-collected or explicitly closed via\nits `close()` method. \n\nOne additional area of exploration is whether or not you can resume\niteration on a generator if you break out of a for-loop.  For example,\ntry this:\n\n```python\n>>> f = follow('Data/stocklog.csv')\n>>> for line in f:\n        print(line,end='')\n        if 'IBM' in line:\n            break\n\n\"CAT\",78.36,\"6/11/2007\",\"09:37.19\",-0.16,78.32,78.36,77.99,237714\n\"VZ\",42.99,\"6/11/2007\",\"09:37.20\",-0.08,42.95,42.99,42.78,268459\n...\n\"IBM\",102.91,\"6/11/2007\",\"09:37.31\",-0.16,102.87,102.91,102.77,190859\n>>> # Resume iteration\n>>> for line in f:\n        print(line,end='')\n        if 'IBM' in line:\n            break\n\n\"AA\",39.58,\"6/11/2007\",\"09:39.28\",-0.08,39.67,39.58,39.31,243159\n\"HPQ\",45.94,\"6/11/2007\",\"09:39.29\",0.24,45.80,45.94,45.59,408919\n...\n\"IBM\",102.95,\"6/11/2007\",\"09:39.44\",-0.12,102.87,102.95,102.77,225350\n>>> del f\nFollowing Done\n>>>\n```\n\nIn general, you can break out of running iteration and resume it later\nif you need to.  You just need to make sure the generator object isn't\nforcefully closed or garbage collected somehow.\n\n## (b) Raising Exceptions\n\nIn the file `cofollow.py`, you created a coroutine `printer()`.  Modify the\ncode to catch and report exceptions like this:\n\n```python\n# cofollow.py\n...\n@consumer\ndef printer():\n    while True:\n        try:\n            item = yield\n            print(item)\n        except Exception as e:\n            print('ERROR: %r' % e)\n```\n\nNow, try an experiment:\n\n```python\n>>> from cofollow import printer\n>>> p = printer()\n>>> p.send('hello')\nhello\n>>> p.send(42)\n42\n>>> p.throw(ValueError('It failed'))\nERROR: ValueError('It failed',)\n>>> try:\n        int('n/a')\n    except ValueError as e:\n        p.throw(e)\n\nERROR: ValueError(\"invalid literal for int() with base 10: 'n/a'\",)\n>>> \n```\n\nNotice how the running generator is not terminated by the exception. This\nis merely allowing the `yield` statement to signal an error instead of\nreceiving a value.\n\n\\[ [Solution](soln8_4.md) | [Index](index.md) | [Exercise 8.3](ex8_3.md) | [Exercise 8.5](ex8_5.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex8_5.md",
    "content": "\\[ [Index](index.md) | [Exercise 8.4](ex8_4.md) | [Exercise 8.6](ex8_6.md) \\]\n\n# Exercise 8.5\n\n*Objectives:*\n\n- Learn about managed generators\n\n*Files Created:* `multitask.py`, `server.py`\n\nA generator or coroutine function can never execute without being\ndriven by some other code.  For example, a generator used for\niteration doesn't do anything unless iteration is actually carried out\nusing a for-loop.  Similarly, a collection of coroutines won't run\nunless their `send()` method is invoked somehow.\n\nIn advanced applications of generators, it is possible to drive \ngenerators in various unusual ways.  In this exercise, we look at a\nfew examples.\n\n## (a) Generators as tasks\n\nIf a file `multitask.py`, define the following code:\n\n```python\n# multitask.py\n\nfrom collections import deque\n\ntasks = deque()\ndef run():\n    while tasks:\n        task = tasks.popleft()\n        try:\n            task.send(None)\n            tasks.append(task)\n        except StopIteration:\n            print('Task done')\n```\n\nThis code implements a tiny task scheduler that runs generator functions.\nTry it by running it on the following functions.\n\n```python\n# multitask.py\n...\n\ndef countdown(n):\n    while n > 0:\n        print('T-minus', n)\n        yield\n        n -= 1\n\ndef countup(n):\n    x = 0\n    while x < n:\n        print('Up we go', x)\n        yield\n        x += 1\n\nif __name__ == '__main__':\n    tasks.append(countdown(10))\n    tasks.append(countdown(5))\n    tasks.append(countup(20))\n    run()\n```\n\nWhen you run this, you should see output from all of the generators\ninterleaved together.  For example:\n\n```python\nT-minus 10\nT-minus 5\nUp we go 0\nT-minus 9\nT-minus 4\nUp we go 1\nT-minus 8\nT-minus 3\nUp we go 2\nT-minus 7\nT-minus 2\nUp we go 3\nT-minus 6\nT-minus 1\nUp we go 4\nT-minus 5\nTask done\nUp we go 5\nT-minus 4\nUp we go 6\nT-minus 3\nUp we go 7\nT-minus 2\nUp we go 8\nT-minus 1\nUp we go 9\nTask done\nUp we go 10\nUp we go 11\nUp we go 12\nUp we go 13\nUp we go 14\nUp we go 15\nUp we go 16\nUp we go 17\nUp we go 18\nUp we go 19\nTask done\n```\n\nThat's interesting, but not especially compelling.  Move on to the next example.\n\n## (b) Generators as Tasks Serving Network Connections\n\nCreate a file `server.py` and put the following code into it:\n\n```python\n# server.py\n\nfrom socket import *\nfrom select import select\nfrom collections import deque\n\ntasks = deque()\nrecv_wait = {}   #  sock -> task\nsend_wait = {}   #  sock -> task\n\ndef run():\n    while any([tasks, recv_wait, send_wait]):\n        while not tasks:\n            can_recv, can_send, _ = select(recv_wait, send_wait, [])\n            for s in can_recv:\n                tasks.append(recv_wait.pop(s))\n            for s in can_send:\n                tasks.append(send_wait.pop(s))\n        task = tasks.popleft()\n        try:\n            reason, resource = task.send(None)\n            if reason == 'recv':\n                recv_wait[resource] = task\n            elif reason == 'send':\n                send_wait[resource] = task\n            else:\n                raise RuntimeError('Unknown reason %r' % reason)\n        except StopIteration:\n            print('Task done')\n```\n\nThis code is a slightly more complicated version of the task scheduler in\npart (a).  It will require a bit of study, but the idea is that not only\nwill each task yield, it will indicate a reason for doing so (receiving or\nsending).  Depending on the reason, the task will move over to a waiting\narea.  The scheduler then runs any available tasks or waits for I/O\nevents to occur when nothing is left to do.\n\nIt's all a bit tricky perhaps, but add the following code which implements\na simple echo server:\n\n```python\n# server.py\n...\n\ndef tcp_server(address, handler):\n    sock = socket(AF_INET, SOCK_STREAM)\n    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)\n    sock.bind(address)\n    sock.listen(5)\n    while True:\n        yield 'recv', sock\n        client, addr = sock.accept()\n        tasks.append(handler(client, addr))\n        \ndef echo_handler(client, address):\n    print('Connection from', address)\n    while True:\n        yield 'recv', client\n        data = client.recv(1000)\n        if not data:\n            break\n        yield 'send', client\n        client.send(b'GOT:' + data)\n    print('Connection closed')\n\nif __name__ == '__main__':\n    tasks.append(tcp_server(('',25000), echo_handler))\n    run()\n```\n\nRun this server in its own terminal window.   In another terminal, connect to it using a command such as `telnet` or `nc`.   For example:\n\n```\nbash % nc localhost 25000\nHello\nGot: Hello\nWorld\nGot: World\n```\n\nIf you don't have access to `nc` or `telnet` you can also use Python itself:\n\n```\nbash % python3 -m telnetlib localhost 25000\nHello\nGot: Hello\nWorld\nGot: World\n```\n\nIf it's working, you should see output being echoed back to you. Not only that,\nif you connect multiple clients, they'll all operate concurrently.\n\nThis tricky use of generators is not something that you would\nlikely have to code directly.  However, they are used in certain advanced\npackages such as `asyncio` that was added to the standard\nlibrary in Python 3.4.\n\n\\[ [Solution](soln8_5.md) | [Index](index.md) | [Exercise 8.4](ex8_4.md) | [Exercise 8.6](ex8_6.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex8_6.md",
    "content": "\\[ [Index](index.md) | [Exercise 8.5](ex8_5.md) | [Exercise 9.1](ex9_1.md) \\]\n\n# Exercise 8.6\n\n*Objectives:*\n\n- Learn about delegating generators\n\n*Files Modified:* `cofollow.py`, `server.py`\n\nOne potential issue in code that relies on generators is the problem\nof hiding details from the user and writing libraries.  A lot of low-level\nmechanics are generally required to drive everything and it's often rather\nawkward to directly expose it to users.\n\nStarting in Python 3.3, a new `yield from` statement can be used to\ndelegate generators to another function.  It is a useful way to\nclean-up code that relies on generators.\n\n## (a) Example: Receiving messages\n\nIn [Exercise 8.3](ex8_3.md), we looked at the definitions of coroutines.\nCoroutines were functions that you sent data to.  For example:\n\n```python\n>>> from cofollow import consumer\n>>> @consumer\n    def printer():\n        while True:\n            item = yield\n            print('Got:', item)\n\n>>> p = printer()\n>>> p.send('Hello')\nGot: Hello\n>>> p.send('World')\nGot: World\n>>>\n```\n\nAt the time, it might have been interesting to use `yield` to receive a \nvalue.  However, if you really look at the code, it looks pretty weird--a\nbare `yield` like that?  What's going on there?\n\nIn the `cofollow.py` file, define the following function:\n\n```python\ndef receive(expected_type):\n    msg = yield\n    assert isinstance(msg, expected_type), 'Expected type %s' % (expected_type)\n    return msg\n```\n\nThis function receives a message, but then verifies that it is of an expected\ntype.  Try it:\n\n```python\n>>> from cofollow import consumer, receive\n>>> @consumer\n    def print_ints():\n        while True:\n             val = yield from receive(int)\n             print('Got:', val)\n\n>>> p = print_ints()\n>>> p.send(42)\nGot: 42\n>>> p.send(13)\nGot: 13\n>>> p.send('13')\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  ...\nAssertionError: Expected type <class 'int'>\n>>> \n```\n\nFrom a readability point of view, the `yield from receive(int)` statement\nis a bit more descriptive--it indicates that the function will yield until\nit receives a message of a given type. \n\nNow, modify all of the coroutines in `coticker.py` to use the new `receive()`\nfunction and make sure the code from [Exercise 8.3](ex8_3.md) still\nworks.\n\n## (b) Wrapping a Socket\n\nIn the previous exercise, you wrote a simple network echo server using\ngenerators.  The code for the server looked like this:\n\n```python\ndef tcp_server(address, handler):\n    sock = socket(AF_INET, SOCK_STREAM)\n    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)\n    sock.bind(address)\n    sock.listen(5)\n    while True:\n        yield 'recv', sock\n        client, addr = sock.accept()\n        tasks.append(handler(client, addr))\n        \ndef echo_handler(client, address):\n    print('Connection from', address)\n    while True:\n        yield 'recv', client\n        data = client.recv(1000)\n        if not data:\n            break\n        yield 'send', client\n        client.send(b'GOT:', data)\n    print('Connection closed')\n```\n\nCreate a class `GenSocket` that cleans up the `yield` statements and\nallows the server to be rewritten more simply as follows:\n\n```python\ndef tcp_server(address, handler):\n    sock = GenSocket(socket(AF_INET, SOCK_STREAM))\n    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)\n    sock.bind(address)\n    sock.listen(5)\n    while True:\n        client, addr = yield from sock.accept()\n        tasks.append(handler(client, addr))\n        \ndef echo_handler(client, address):\n    print('Connection from', address)\n    while True:\n        data = yield from client.recv(1000)\n        if not data:\n            break\n        yield from client.send(b'GOT:', data)\n    print('Connection closed')\n```\n\n## (c) Async/Await\n\nTake the `GenSocket` class you just wrote and wrap all of the methods \nthat use `yield` with the `@coroutine` decorator from the `types` module.\n\n```python\nfrom types import coroutine\n...\n\nclass GenSocket:\n    def __init__(self, sock):\n        self.sock = sock\n\n    @coroutine\n    def accept(self):\n        yield 'recv', self.sock\n        client, addr = self.sock.accept()\n        return GenSocket(client), addr\n\n    @coroutine\n    def recv(self, maxsize):\n        yield 'recv', self.sock\n        return self.sock.recv(maxsize)\n\n    @coroutine\n    def send(self, data):\n        yield 'send', self.sock\n        return self.sock.send(data)\n\n    def __getattr__(self, name):\n        return getattr(self.sock, name)\n```\n\nNow, rewrite your server code to use `async` functions and `await` statements like this:\n\n```python\nasync def tcp_server(address, handler):\n    sock = GenSocket(socket(AF_INET, SOCK_STREAM))\n    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)\n    sock.bind(address)\n    sock.listen(5)\n    while True:\n        client, addr = await sock.accept()\n        tasks.append(handler(client, addr))\n        \nasync def echo_handler(client, address):\n    print('Connection from', address)\n    while True:\n        data = await client.recv(1000)\n        if not data:\n            break\n        await client.send(b'GOT:', data)\n    print('Connection closed')\n```\n\n\n\\[ [Solution](soln8_6.md) | [Index](index.md) | [Exercise 8.5](ex8_5.md) | [Exercise 9.1](ex9_1.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex9_1.md",
    "content": "\\[ [Index](index.md) | [Exercise 8.6](ex8_6.md) | [Exercise 9.2](ex9_2.md) \\]\n\n# Exercise 9.1\n\n*Objectives:*\n\n- A review of module basics\n\nThis exercise is about some of the more tricky details of library modules. \nStart this exercise by creating a very simple library module:\n\n```python\n# simplemod.py\n\nx = 42        # A global variable\n\n# A simple function\ndef foo():   \n    print('x is', x)\n\n# A simple class\nclass Spam:\n    def yow(self):\n        print('Yow!')\n\n# A scripting statement\nprint('Loaded simplemod')\n```\n    \n##  (a) Module Loading and System Path\n\nTry importing the module you just created:\n\n```python\n>>> import simplemod\nLoaded simplemod\n>>> simplemod.foo()\nx is 42\n>>>\n```\n    \nIf this failed with an `ImportError`, your path setting is\nflaky.  Look at the value of `sys.path` and fix it.\n\n```python\n>>> import sys\n>>> sys.path\n... look at the result ...\n>>>\n```\n    \n## (b) Repeated Module Loading\n    \nMake sure you understand that modules are only loaded\nonce.  Try a repeated import and notice how you do not see\nthe output from the `print` function:\n\n```python\n>>> import simplemod\n>>>\n```\n\nTry changing the value of `x` and see that a repeated import\nhas no effect.\n\n```python\n>>> simplemod.x\n42\n>>> simplemod.x = 13\n>>> simplemod.x\n13\n>>> import simplemod\n>>> simplemod.x\n13\n>>>\n```\n\nUse `importlib.reload()` if you want to force a module to reload.\n\n```python\n>>> import importlib\n>>> importlib.reload(simplemod)\nLoaded simplemod\n<module 'simplemod' from 'simplemod.py'>\n>>> simplemod.x\n42\n>>>\n```\n    \n`sys.modules` is a dictionary of all loaded modules.  Take\na look at it, delete your module, and try a repeated import.\n\n```python\n>>> sys.modules\n... look at output ...\n>>> sys.modules['simplemod']\n<module 'simplemod' from 'simplemod.py'>\n>>> del sys.modules['simplemod']\n>>> import simplemod\nLoaded simplemod\n>>>\n```\n\n## (c) from module import\n\nRestart Python and import a selected symbol from a module.\n\n```python\n>>> ############### [ RESTART ] ###############\n>>> from simplemod import foo\nLoaded simplemod\n>>> foo()\nx is 42\n>>>\n```\n\nNotice how this loaded the entire module (observe the output of\nthe print function and how the `x` variable is used).\n\nWhen you use `from`, the module object itself is not\nvisible.  For example:\n\n```python\n>>> simplemod.foo()\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nNameError: name 'simplemod' is not defined\n>>> \n```\n\nMake sure you understand that when you export things from a module,\nthey are simply name references.  For example, try this and explain:\n\n```python\n>>> from simplemod import x,foo\n>>> x\n42\n>>> foo()\nx is 42\n>>> x = 13\n>>> foo()\nx is 42                   # !! Please explain\n>>> x\n13\n>>>\n```\n\n## (d) Broken reload()\n\nCreate an instance:\n\n```python\n>>> import simplemod\n>>> s = simplemod.Spam()\n>>> s.yow()\nYow!\n>>>\n```\n\nNow, go to the `simplemod.py` file and change the implementation of `Spam.yow()` to the\nfollowing:\n\n```python\n# simplemod.py\n...\n\nclass Spam:\n    def yow(self):\n        print('More Yow!')\n```\n\nNow, watch what happens on a reload.  Do not restart Python for this part.\n\n```python\n>>> importlib.reload(simplemod)\nLoaded simplemod\n<module 'simplemod' from 'simplemod.py'>\n>>> s.yow()\n'Yow!'\n>>> t = simplemod.Spam()\n>>> t.yow()\n'More Yow!'\n>>>\n```\n\nNotice how you have two instances of `Spam`, but they're using different implementations\nof the `yow()` method.  Yes, actually both versions of code are loaded at the same time.\nYou'll find other oddities as well.  For example:\n\n```python\n>>> s\n<simplemod.Spam object at 0x1006940b8>\n>>> isinstance(s, simplemod.Spam)\nFalse\n>>> isinstance(t, simplemod.Spam)\nTrue\n>>>\n```\n\nBottom line:  It's probably best not to rely on reloading for anything important. \nIt might be fine if you're just trying to debug some things (as long as you're\naware of its limitations and dangers).\n\n\\[ [Solution](soln9_1.md) | [Index](index.md) | [Exercise 8.6](ex8_6.md) | [Exercise 9.2](ex9_2.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex9_2.md",
    "content": "\\[ [Index](index.md) | [Exercise 9.1](ex9_1.md) | [Exercise 9.3](ex9_3.md) \\]\n\n# Exercise 9.2\n\n*Objectives:*\n\n- Learn how to create a Python package\n\n**Note**\n\nThis exercise mostly just involves copying files on the file system.\nThere shouldn't be a lot of coding.\n\n## (a) Making a Package\n\nIn previous exercises, you created the following files that were related to\ntype-checked structures, reading data, and making tables:\n\n- `structure.py`\n- `validate.py`\n- `reader.py`\n- `tableformat.py`\n\nYour task is to take all of these files and move them into a package called `structly`.\nTo do that, follow these steps:\n\n- Make a directory called `structly`\n- Make an empty file `__init__.py` and put it in the `structly` directory\n- Move the files `structure.py`, `validate.py`, `reader.py`, and `tableformat.py` into the `structly` directory.\n- Fix any import statements between modules (specifically, the `structure` module depends on `validate`).\n\nOnce you've done that, modify the `stock.py` program so that it looks exactly like this\nand that it works:\n\n```python\n# stock.py\n\nfrom structly.structure import Structure\n\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n    \n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares: PositiveInteger):\n        self.shares -= nshares\n\nif __name__ == '__main__':\n    from structly.reader import read_csv_as_instances\n    from structly.tableformat import create_formatter, print_table\n    portfolio = read_csv_as_instances('Data/portfolio.csv', Stock)\n    formatter = create_formatter('text')\n    print_table(portfolio, ['name','shares','price'], formatter)\n```\n\n\\[ [Solution](soln9_2.md) | [Index](index.md) | [Exercise 9.1](ex9_1.md) | [Exercise 9.3](ex9_3.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex9_3.md",
    "content": "\\[ [Index](index.md) | [Exercise 9.2](ex9_2.md) | [Exercise 9.4](ex9_4.md) \\]\n\n# Exercise 9.3\n\n*Objectives:*\n\n- Learn about controlling symbols and combining submodules\n- Learn about module splitting\n\nOne potentially annoying aspect of packages is that they complicate\nimport statements.   For example, in the `stock.py` program, you now\nhave import statements such as the following:\n\n```python\nfrom structly.structure import Structure\nfrom structly.reader import read_csv_as_instances\nfrom structly.tableformat import create_formatter, print_table\n```\n\nIf the package is meant to be used as a unified whole, it might be\nmore sane (and easier) to consolidate everything into a single top\nlevel package.   Let's do that:\n\n## (a) Controlling Exported Symbols\n\nModify all of the submodules in the `structly` package so that they explicitly\ndefine an `__all__` variable which exports selected symbols.  Specifically:\n\n- `structure.py` should export `Structure`\n- `reader.py` should export all of the various `read_csv_as_*()` functions\n- `tableformat.py` exports `create_formatter()` and `print_table()`\n\nNow, in the `__init__.py` file, unify all of the submodules like this:\n\n```python\n# structly/__init__.py\n\nfrom .structure import *\nfrom .reader import *\nfrom .tableformat import *\n```\n\nOnce you have done this, you should be able to import everything from \na single logical module:\n\n```python\n# stock.py\n\nfrom structly import Structure\n\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n    \n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares: PositiveInteger):\n        self.shares -= nshares\n\nif __name__ == '__main__':\n    from structly import read_csv_as_instances, create_formatter, print_table\n    portfolio = read_csv_as_instances('Data/portfolio.csv', Stock)\n    formatter = create_formatter('text')\n    print_table(portfolio, ['name','shares','price'], formatter)\n```\n\n## (b) Exporting Everything\n\nIn the `structly/__init__.py`, define an `__all__` variable that contains all\nexported symbols.   Once you've done this, you should be able to simplify the\n`stock.py` file further:\n\n```python\n# stock.py\n\nfrom structly import *\n\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n    \n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares: PositiveInteger):\n        self.shares -= nshares\n\nif __name__ == '__main__':\n    portfolio = read_csv_as_instances('Data/portfolio.csv', Stock)\n    formatter = create_formatter('text')\n    print_table(portfolio, ['name','shares','price'], formatter)\n```\n\nAs an aside, use of the `from module import *` statement is generally frowned upon\nthe Python community--especially if you're not sure what you're doing.  That said,\nthere are situations where it often makes sense. For example, if a package defines\na large number of commonly used symbols or constants it might be useful to use it.\n\n\n## (c) Module Splitting\n\nThe file `structly/tableformat.py` contains code for creating tables in different\nformats. Specifically:\n\n- A `TableFormatter` base class.\n- A `TextTableFormatter` class.\n- A `CSVTableFormatter` class.\n- A `HTMLTableFormatter` class.\n\nInstead of having all of these classes in a single `.py`\nfile, maybe it would make sense to move each concrete formatter to\nits own file.  To do this, we're going to split the `tableformat.py`\nfile into parts.  Follow these instructions carefully:\n\nFirst, remove the `structly/__pycache__` directory. \n\n```\n% cd structly\n% rm -rf __pycache__\n```\n\nNext, create the directory `structly/tableformat`.  This directory\nmust have exactly the same name as the module it is replacing\n(`tableformat.py`).\n\n```\nbash % mkdir tableformat\nbash %\n```\n\nMove the original `tableformat.py` file into the new\n`tableformat` directory and rename it to `formatter.py`. \n\n```\nbash % mv tableformat.py tableformat/formatter.py\nbash %\n```\n\nIn the `tableformat` directory, split the\n`tableformat.py` code into the following files and directories:\n\n- `formatter.py` - Contains the `TableFormatter` base class, mixins, and various functions.\n- `formats/text.py` - Contains the `TextTableFormatter` class.\n- `formats/csv.py` - Contains the `CSVTableFormatter` class.\n- `formats/html.py` - Contains the `HTMLTableFormatter` class.\n\nAdd an `__init__.py` file to the `tableformat/` and `tableformat/formats`\ndirectories.  Have the `tableformat/__init__.py` export the same\nsymbols that the original `tableformat.py` file exported.\n\nAfter you have made all of these changes, you should have a package\nstructure that looks like this:\n\n```\nstructly/\n      __init__.py\n      validate.py\n      reader.py\n      structure.py\n      tableformat/\n           __init__.py\n           formatter.py\n           formats/\n               __init__.py\n               text.py\n               csv.py\n               html.py\n```\n\nTo users, everything should work exactly as it did before.  For example, your\nprior `stock.py` file should work:\n\n```python\n# stock.py\n\nfrom structly import *\n\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n    \n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\nif __name__ == '__main__':\n    portfolio = read_csv_as_instances('Data/portfolio.csv', Stock)\n    formatter = create_formatter('text')\n    print_table(portfolio, ['name','shares','price'], formatter)\n```\n\n\\[ [Solution](soln9_3.md) | [Index](index.md) | [Exercise 9.2](ex9_2.md) | [Exercise 9.4](ex9_4.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/ex9_4.md",
    "content": "\\[ [Index](index.md) | [Exercise 9.3](ex9_3.md) | []() \\]\n\n# Exercise 9.4\n\n*Objectives:*\n\n- Explore circular imports\n- Dynamic module imports\n\nIn the last exercise, you split the `tableformat.py` file up into submodules.\nThe last part of the resulting `tableformat/formatter.py` file has turned into a mess of imports.\n\n```python\n# tableformat.py\n...\n\nclass TableFormatter(ABC):\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n\nfrom .formats.text import TextTableFormatter\nfrom .formats.csv import CSVTableFormatter\nfrom .formats.html import HTMLTableFormatter\n\n...\n\ndef create_formatter(name, column_formats=None, upper_headers=False):\n    if name == 'text':\n        formatter_cls = TextTableFormatter\n    elif name == 'csv':\n        formatter_cls = CSVTableFormatter\n    elif name == 'html':\n        formatter_cls = HTMLTableFormatter\n    else:\n        raise RuntimeError('Unknown format %s' % name)\n\n    if column_formats:\n        class formatter_cls(ColumnFormatMixin, formatter_cls):\n            formats = column_formats\n\n    if upper_headers:\n        class formatter_cls(UpperHeadersMixin, formatter_cls):\n            pass\n\n    return formatter_cls()\n```\n\nThe imports in the middle of the file are required because the `create_formatter()`\nfunction needs them to find the appropriate classes.   Really, the whole thing is a mess.\n\n## (a) Circular Imports\n\nTry moving the following import statements to the top of the `formatter.py` file:\n\n```python\n# formatter.py\n\nfrom .formats.text import TextTableFormatter\nfrom .formats.csv import CSVTableFormatter\nfrom .formats.html import HTMLTableFormatter\n\nclass TableFormatter(ABC):\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n\n...\n```\n\nObserve that nothing works anymore.  Try running the `stock.py` program and\nnotice the error about `TableFormatter` not being defined.   The order\nof import statements matters and you can't just move the imports anywhere\nyou want.\n\nMove the import statements back where they were.  Sigh.\n\n## (b) Subclass Registration\n\nTry the following experiment and observe:\n\n```python\n>>> from structly.tableformat.formats.text import TextTableFormatter\n>>> TextTableFormatter.__module__\n'structly.tableformat.formats.text'\n>>> TextTableFormatter.__module__.split('.')[-1]\n'text'\n>>>\n```\n\nModify the `TableFormatter` base class by adding a dictionary and an\n`__init_subclass__()` method:\n\n```python\nclass TableFormatter(ABC):\n    _formats = { }\n\n    @classmethod\n    def __init_subclass__(cls):\n        name = cls.__module__.split('.')[-1]\n        TableFormatter._formats[name] = cls\n\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n```\n\nThis makes the parent class track all of its subclasses.  Check it out:\n\n```python\n>>> from structly.tableformat.formatter import TableFormatter\n>>> TableFormatter._formats\n{'text': <class 'structly.tableformat.formats.text.TextTableFormatter'>, \n 'csv': <class 'structly.tableformat.formats.csv.CSVTableFormatter'>, \n 'html': <class 'structly.tableformat.formats.html.HTMLTableFormatter'>}\n>>>\n```\n\nModify the `create_formatter()` function to look up the class in this dictionary\ninstead:\n\n```python\ndef create_formatter(name, column_formats=None, upper_headers=False):\n    formatter_cls = TableFormatter._formats.get(name)\n    if not formatter_cls:\n        raise RuntimeError('Unknown format %s' % name)\n\n    if column_formats:\n        class formatter_cls(ColumnFormatMixin, formatter_cls):\n            formats = column_formats\n\n    if upper_headers:\n        class formatter_cls(UpperHeadersMixin, formatter_cls):\n            pass\n\n    return formatter_cls()\n```\n\nRun the `stock.py` program.  Make sure it still works after you've made these changes.\nJust a note that all of the import statements are still there.  You've mainly\njust cleaned up the code a bit and eliminated the hard-wired class names.\n\n## (c) Dynamic Imports\n\nYou're now ready for the final frontier.   Delete the following import statements\naltogether:\n\n```python\n# formatter.py\n...\n\nfrom .formats.text import TextTableFormatter     # DELETE\nfrom .formats.csv import CSVTableFormatter       # DELETE\nfrom .formats.html import HTMLTableFormatter     # DELETE\n...\n```\n\nRun your `stock.py` code again--it should fail with an error. It knows nothing about the\ntext formatter.  Fix it by adding this tiny fragment of code to `create_formatter()`:\n\n```python\ndef create_formatter(name, column_formats=None, upper_headers=False):\n    if name not in TableFormatter._formats:\n        __import__(f'{__package__}.formats.{name}')\n    ...\n```\n\nThis code attempts a dynamic import of a formatter module if nothing is known about the\nname.   The import alone (if it works) will register the class with the `_formats` \ndictionary and everything will just work.   Magic!\n\nTry running the `stock.py` code and make sure it works afterwards.\n\n\\[ [Solution](soln9_4.md) | [Index](index.md) | [Exercise 9.3](ex9_3.md) \\]\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n"
  },
  {
    "path": "Exercises/index.md",
    "content": "# Advanced Python Mastery Exercises\n\nCopyright (C) 2007-2023  \nDavid M. Beazley  \nhttps://www.dabeaz.com\n\nThis page contains links to all of the course exercises.  For the best experience,\nthe exercises should be worked in order as later exercises often build upon earlier\nexercises.  The [`Solutions/`](../Solutions) directory contains fully worked\nout solutions to every exercise.  You can use this if you get stuck or you\nneed to reset your code to a working state.\n\nBefore beginning, you should fork/clone the GitHub repo so that you have a\nlocal copy on your machine.  Exercises assume that all of your work will\ntake place in the top-level `python-mastery` directory.\n\nAssociated course presentation slides can be found at [PythonMastery.pdf](../PythonMastery.pdf).\n\n## 1. Python Review\n\n- [Exercise 1.1](ex1_1.md)\n- [Exercise 1.2](ex1_2.md)\n- [Exercise 1.3](ex1_3.md)\n- [Exercise 1.4](ex1_4.md)\n- [Exercise 1.5](ex1_5.md)\n- [Exercise 1.6](ex1_6.md)\n\n## 2. Data Handling\n\n- [Exercise 2.1](ex2_1.md)\n- [Exercise 2.2](ex2_2.md)\n- [Exercise 2.3](ex2_3.md)\n- [Exercise 2.4](ex2_4.md)\n- [Exercise 2.5](ex2_5.md)\n- [Exercise 2.6](ex2_6.md)\n\n## 3. Classes and Objects\n\n- [Exercise 3.1](ex3_1.md)\n- [Exercise 3.2](ex3_2.md)\n- [Exercise 3.3](ex3_3.md)\n- [Exercise 3.4](ex3_4.md)\n- [Exercise 3.5](ex3_5.md)\n- [Exercise 3.6](ex3_6.md)\n- [Exercise 3.7](ex3_7.md)\n- [Exercise 3.8](ex3_8.md)\n\n## 4. Inside Python Objects\n\n- [Exercise 4.1](ex4_1.md)\n- [Exercise 4.2](ex4_2.md)\n- [Exercise 4.3](ex4_3.md)\n- [Exercise 4.4](ex4_4.md)\n\n## 5. Functions, Errors, and Testing\n\n- [Exercise 5.1](ex5_1.md)\n- [Exercise 5.2](ex5_2.md)\n- [Exercise 5.3](ex5_3.md)\n- [Exercise 5.4](ex5_4.md)\n- [Exercise 5.5](ex5_5.md)\n- [Exercise 5.6](ex5_6.md)\n\n## 6. Working with Code\n\n- [Exercise 6.1](ex6_1.md)\n- [Exercise 6.2](ex6_2.md)\n- [Exercise 6.3](ex6_3.md)\n- [Exercise 6.4](ex6_4.md)\n- [Exercise 6.5](ex6_5.md)\n\n## 7. Metaprogramming\n\n- [Exercise 7.1](ex7_1.md)\n- [Exercise 7.2](ex7_2.md)\n- [Exercise 7.3](ex7_3.md)\n- [Exercise 7.4](ex7_4.md)\n- [Exercise 7.5](ex7_5.md)\n- [Exercise 7.6](ex7_6.md)\n\n## 8. Iterators, Generators, and Coroutines\n\n- [Exercise 8.1](ex8_1.md)\n- [Exercise 8.2](ex8_2.md)\n- [Exercise 8.3](ex8_3.md)\n- [Exercise 8.4](ex8_4.md)\n- [Exercise 8.5](ex8_5.md)\n- [Exercise 8.6](ex8_6.md)\n\n## 9. Modules and Packages\n\n- [Exercise 9.1](ex9_1.md)\n- [Exercise 9.2](ex9_2.md)\n- [Exercise 9.3](ex9_3.md)\n- [Exercise 9.4](ex9_4.md)\n"
  },
  {
    "path": "Exercises/soln1_1.md",
    "content": "# Exercise 1.1 - Solution\n\nCheck [here](../Solutions/1_1/art.py)\n\n"
  },
  {
    "path": "Exercises/soln1_2.md",
    "content": "# Exercise 1.2 - Solution\n\nSolution code is shown in the exercise.\n\n\n[Back](ex1_2.md)\n"
  },
  {
    "path": "Exercises/soln1_3.md",
    "content": "# Exercise 1.3 - Solution\n\n```python\n# pcost.py\n\ntotal_cost = 0.0\n\nwith open('Data/portfolio.dat', 'r') as f:\n    for line in f:\n        fields = line.split()\n        nshares = int(fields[1])\n        price = float(fields[2])\n        total_cost = total_cost + nshares * price\n\nprint(total_cost)\n```\n\n\n[Back](ex1_3.md)\n"
  },
  {
    "path": "Exercises/soln1_4.md",
    "content": "# Exercise 1.4 - Solution\n\n## (a) Defining a function\n\n```python\n# pcost.py\n\ndef portfolio_cost(filename):\n    total_cost = 0.0\n    with open(filename) as f:\n        for line in f:\n            fields = line.split()\n            nshares = int(fields[1])\n            price = float(fields[2])\n            total_cost = total_cost + nshares * price\n    return total_cost\n\nprint(portfolio_cost('Data/portfolio.dat'))\n```\n\n## (b) Adding some error handling\n\n```python\n# pcost.py\n\ndef portfolio_cost(filename):\n    total_cost = 0.0\n    with open(filename) as f:\n        for line in f:\n            fields = line.split()\n            try:\n                nshares = int(fields[1])\n                price = float(fields[2])\n                total_cost = total_cost + nshares*price\n\n            # This catches errors in int() and float() conversions above\n            except ValueError as e:\n                print(\"Couldn't parse:\", repr(line))\n                print(\"Reason:\", e)\n    return total_cost\n\nprint(portfolio_cost('Data/portfolio3.dat'))\n```\n\n\n[Back](ex1_4.md)\n"
  },
  {
    "path": "Exercises/soln1_5.md",
    "content": "# Exercise 1.5 - Solution\n\nThe solution is shown in the exercise description.\n\n\n[Back](ex1_5.md)\n"
  },
  {
    "path": "Exercises/soln1_6.md",
    "content": "# Exercise 1.6 - Solution\n\n## (b) Main Module\n\n```python\n# pcost.py\n\ndef portfolio_cost(filename):\n    total_cost = 0.0\n    with open(filename) as f:\n        for line in f:\n            fields = line.split()\n            try:\n                nshares = int(fields[1])\n                price = float(fields[2])\n                total_cost = total_cost + nshares * price\n\n            # This catches errors in int() and float() conversions above\n            except ValueError as e:\n                print(\"Couldn't parse:\", line)\n                print(\"Reason:\", e)\n\n    return total_cost\n\nif __name__ == '__main__':\n    print(portfolio_cost('Data/portfolio.dat'))\n```\n\n\n[Back](ex1_6.md)\n"
  },
  {
    "path": "Exercises/soln2_1.md",
    "content": "# Exercise 2.1 - Solution\n\n```python\n# readrides.py\n\nimport csv\n\ndef read_rides_as_tuples(filename):\n    '''\n    Read the bus ride data as a list of tuples\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = (route, date, daytype, rides)\n            records.append(record)\n    return records\n\ndef read_rides_as_dicts(filename):\n    '''\n    Read the bus ride data as a list of dicts\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = {\n                'route': route, \n                'date': date, \n                'daytype': daytype, \n                'rides' : rides\n                }\n            records.append(record)\n    return records\n\nclass Row:\n    # Uncomment to see effect of slots\n    # __slots__ = ('route', 'date', 'daytype', 'rides')\n    def __init__(self, route, date, daytype, rides):\n        self.route = route\n        self.date = date\n        self.daytype = daytype\n        self.rides = rides\n\n# Uncomment to use a namedtuple instead\n#from collections import namedtuple\n#Row = namedtuple('Row',('route','date','daytype','rides'))\n\ndef read_rides_as_instances(filename):\n    '''\n    Read the bus ride data as a list of instances\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = Row(route, date, daytype, rides)\n            records.append(record)\n    return records\n\nif __name__ == '__main__':\n    import tracemalloc\n    tracemalloc.start()\n    read_rides = read_rides_as_tuples # Change to as_dicts, as_instances, etc.\n    rides = read_rides(\"Data/ctabus.csv\")\n\n    print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory())\n```\n\n\n\n[Back](ex2_1.md)\n"
  },
  {
    "path": "Exercises/soln2_2.md",
    "content": "# Exercise 2.2 - Solution\n\nThere is no official solution for this exercise--you need rely on your\ncurrent Python knowledge.  However, there are a few tips that can\nhelp.\n\n- For problems where you need to determine uniqueness, use a set. Sets aren't allowed to\nhave duplicates.\n\n- If you need to tabulate data, consider using a dictionary that maps keys to a numeric\ncount.  For example, to count rides on each bus route, you could make a dictionary that\nmaps the route name to the total ride count for that route.  A `Counter` object from\n`collections` is perfect for this task.\n\n- If you need to keep data in order or sort data, store it in a list.\n\n- You can make Python data structures out of almost anything.  For\nexample, dictionaries of sets, nested dictionaries, etc.  You might\nneed to do this to answer questions 3 and 4.\n\nEven though no code is provided, here are some answers to the questions\nso that you can check your work:\n\n1. How many bus routes exist in Chicago? (Answer: 181)\n\n2. How many people rode the number 22 bus on February 2, 2011? (Answer: 5055)\n\n3. What is the total number of rides taken on each bus route? (Answer: for the top three routes, [('79', 133796763), ('9', 117923787), ('49', 95915008)])\n\n4. 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)])\n\n[Back](ex2_2.md)\n"
  },
  {
    "path": "Exercises/soln2_3.md",
    "content": "# Exercise 2.3 - Solution\n\nWork through the exercise. Code is given in the description.\n\n\n\n[Back](ex2_3.md)\n"
  },
  {
    "path": "Exercises/soln2_4.md",
    "content": "# Exercise 2.4 - Solution\n\n```python\n# mutint.py\n\nfrom functools import total_ordering\n\n@total_ordering\nclass MutInt:\n    __slots__ = ['value']\n\n    def __init__(self, value):\n        self.value = value\n\n    def __str__(self):\n        return str(self.value)\n    \n    def __repr__(self):\n        return f'MutInt({self.value!r})'\n\n    def __format__(self, fmt):\n        return format(self.value, fmt)\n\n    # Implement the \"+\" operator. Forward operands (MutInt + other)\n    def __add__(self, other):\n        if isinstance(other, MutInt):\n            return MutInt(self.value + other.value)\n        elif isinstance(other, int):\n            return MutInt(self.value + other)\n        else:\n            return NotImplemented\n\n    # Support for reversed operands (other + MutInt)\n    __radd__ = __add__\n\n    # Support for in-place update (MutInt += other)\n    def __iadd__(self, other):\n        if isinstance(other, MutInt):\n            self.value += other.value\n            return self\n        elif isinstance(other, int):\n            self.value += other\n            return self\n        else:\n            return NotImplemented\n\n    # Support for equality testing\n    def __eq__(self, other):\n        if isinstance(other, MutInt):\n            return self.value == other.value\n        elif isinstance(other, int):\n            return self.value == other\n        else:\n            return NotImplemented\n\n    # One relation is needed for @total_ordering decorator. It fills in others\n    def __lt__(self, other):\n        if isinstance(other, MutInt):\n            return self.value < other.value\n        elif isinstance(other, int):\n            return self.value < other\n        else:\n            return NotImplemented\n\n    # Conversions to int() and float()\n    def __int__(self):\n        return int(self.value)\n\n    def __float__(self):\n        return float(self.value)\n\n    # Support for indexing s[MutInt]\n    __index__ = __int__\n```\n\n\n\n[Back](ex2_4.md)\n"
  },
  {
    "path": "Exercises/soln2_5.md",
    "content": "# Exercise 2.5 - Solution\n\nHere is a version of `readrides.py` with changes for parts (c) and (d).\n\n```python\n# readrides.py\n\nimport csv\n\ndef read_rides_as_tuples(filename):\n    '''\n    Read the bus ride data as a list of tuples\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = (route, date, daytype, rides)\n            records.append(record)\n    return records\n\ndef read_rides_as_dicts(filename):\n    '''\n    Read the bus ride data as a list of dicts\n    '''\n    records = RideData()          # <---- CHANGED\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = {\n                'route': route, \n                'date': date, \n                'daytype': daytype, \n                'rides' : rides\n                }\n            records.append(record)\n    return records\n\nclass Row:\n    __slots__ = ('route', 'date', 'daytype', 'rides')\n    def __init__(self, route, date, daytype, rides):\n        self.route = route\n        self.date = date\n        self.daytype = daytype\n        self.rides = rides\n\ndef read_rides_as_instances(filename):\n    '''\n    Read the bus ride data as a list of instances\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = Row(route, date, daytype, rides)\n            records.append(record)\n    return records\n\n# Read as columns\ndef read_rides_as_columns(filename):\n    '''\n    Read the bus ride data into 4 lists, representing columns\n    '''\n    routes = []\n    dates = []\n    daytypes = []\n    numrides = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            routes.append(row[0])\n            dates.append(row[1])\n            daytypes.append(row[2])\n            numrides.append(int(row[3]))\n    return dict(routes=routes, dates=dates, daytypes=daytypes, numrides=numrides)\n\n# The great \"fake\"\n\nimport collections\nclass RideData(collections.abc.Sequence):\n    def __init__(self):\n        # Each value is a list with all of the values (a column)\n        self.routes = []\n        self.dates = []\n        self.daytypes = []\n        self.numrides = []\n        \n    def __len__(self):\n        # All lists assumed to have the same length\n        return len(self.routes)\n\n    def append(self, d):\n        self.routes.append(d['route'])\n        self.dates.append(d['date'])\n        self.daytypes.append(d['daytype'])\n        self.numrides.append(d['rides'])\n        \n    def __getitem__(self, index):\n        return { 'route': self.routes[index],\n                 'date': self.dates[index],\n                 'daytype': self.daytypes[index],\n                 'rides': self.numrides[index] }\n\nif __name__ == '__main__':\n    import tracemalloc\n    tracemalloc.start()\n    read_rides = read_rides_as_dicts # Change to as_dicts, as_instances, etc.\n    rides = read_rides(\"Data/ctabus.csv\")\n\n    print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory())\n```\n\n\n\n[Back](ex2_5.md)\n"
  },
  {
    "path": "Exercises/soln2_6.md",
    "content": "# Exercise 2.6 - Solution\n\n```python\n# reader.py\n\nimport csv\nfrom collections import defaultdict\n\ndef read_csv_as_dicts(filename, types):\n    '''\n    Read a CSV file with column type conversion\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            record = { name: func(val) for name, func, val in zip(headers, types, row) }\n            records.append(record)\n    return records\n```\n\n\n\n[Back](ex2_6.md)\n"
  },
  {
    "path": "Exercises/soln3_1.md",
    "content": "# Exercise 3.1 - Solution\n\n```python\n# stock.py\n\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\ndef read_portfolio(filename):\n    '''\n    Read a CSV file of stock data into a list of Stocks\n    '''\n    import csv\n    portfolio = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            record = Stock(row[0], int(row[1]), float(row[2]))\n            portfolio.append(record)\n    return portfolio\n\ndef print_portfolio(portfolio):\n    '''\n    Make a nicely formatted table showing stock data\n    '''\n    print('%10s %10s %10s' % ('name', 'shares', 'price'))\n    print(('-'*10 + ' ')*3)\n    for s in portfolio:\n        print('%10s %10d %10.2f' % (s.name, s.shares, s.price))\n\nif __name__ == '__main__':\n    portfolio = read_portfolio('Data/portfolio.csv')\n    print_portfolio(portfolio)\n```\n\n\n\n[Back](ex3_1.md)\n"
  },
  {
    "path": "Exercises/soln3_2.md",
    "content": "# Exercise 3.2 - Solution\n\n(c) Table Formatter\n\n```python\n# tableformat.py\n\n# Print a table\ndef print_table(records, fields):\n    print(' '.join('%10s' % fieldname for fieldname in fields))\n    print(('-'*10 + ' ')*len(fields))\n    for record in records:\n        print(' '.join('%10s' % getattr(record, fieldname) for fieldname in fields))\n```\n\n\n\n[Back](ex3_2.md)\n"
  },
  {
    "path": "Exercises/soln3_3.md",
    "content": "# Exercise 3.3 - Solution\n\n```python\n# stock.py\n\nclass Stock:\n    types = (str, int, float)\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls.types, row)]\n        return cls(*values)\n\ndef read_portfolio(filename):\n    '''\n    Read a CSV file of stock data into a list of Stocks\n    '''\n    import csv\n    portfolio = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            record = Stock.from_row(row)\n            portfolio.append(record)\n    return portfolio\n\nif __name__ == '__main__':\n    import tableformat\n    import reader\n    portfolio = read_portfolio('Data/portfolio.csv')\n    \n    # Generalized version\n    # portfolio = reader.read_csv_as_instances('Data/portfolio.csv', Stock)\n    tableformat.print_table(portfolio, ['name', 'shares', 'price'])\n```\n\nThe `reader.py` file should now look like this:\n\n```python\n# reader.py\n\nimport csv\nfrom collections import defaultdict\n\ndef read_csv_as_dicts(filename, types):\n    '''\n    Read a CSV file into a list of dicts with column type conversion\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            record = { name: func(val) for name, func, val in zip(headers, types, row) }\n            records.append(record)\n    return records\n\ndef read_csv_as_instances(filename, cls):\n    '''\n    Read a CSV file into a list of instances\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            records.append(cls.from_row(row))\n    return records\n```\n\n\n\n[Back](ex3_3.md)\n"
  },
  {
    "path": "Exercises/soln3_4.md",
    "content": "# Exercise 3.4 - Solution\n\n## (a) Private attributes\n\n```python\n# stock.py\n\nclass Stock:\n    _types = (str, int, float)\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls._types, row)]\n        return cls(*values)\n\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n```\n\n\n## (b) Computed Attributes\n\n```python\n# stock.py\n\nclass Stock:\n    _types = (str, int, float)\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls._types, row)]\n        return cls(*values)\n    \n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n```\n\n## (c) Enforcing Type-Checking Rules\n\n```python\n# stock.py\n\nclass Stock:\n    _types = (str, int, float)\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls._types, row)]\n        return cls(*values)\n    \n    @property\n    def shares(self):\n        return self._shares\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value, int):\n            raise TypeError('Expected an integer')\n        if value < 0:\n            raise ValueError('shares must be >= 0')\n        self._shares = value\n\n    @property\n    def price(self):\n        return self._price\n    @price.setter\n    def price(self, value):\n        if not isinstance(value, float):\n            raise TypeError('Expected a float')\n        if value < 0:\n            raise ValueError('price must be >= 0')\n        self._price = value\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n```\n\n## (d) Adding `__slots__`\n\n```python\n# stock.py\n\nclass Stock:\n    __slots__ = ('name','_shares','_price')\n    _types = (str, int, float)\n\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls._types, row)]\n        return cls(*values)\n\n    @property\n    def shares(self):\n        return self._shares\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value, int):\n            raise TypeError('Expected integer')\n        if value < 0:\n            raise ValueError('shares must be >= 0')\n        self._shares = value\n\n    @property\n    def price(self):\n        return self._price\n    @price.setter\n    def price(self, value):\n        if not isinstance(value, float):\n            raise TypeError('Expected float')\n        if value < 0:\n            raise ValueError('price must be >= 0')\n        self._price = value\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n```\n\n## (e) Reconciling types\n\n```python\n# stock.py\n\nclass Stock:\n    __slots__ = ('name','_shares','_price')\n    _types = (str, int, float)\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls._types, row)]\n        return cls(*values)\n\n    @property\n    def shares(self):\n        return self._shares\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value, self._types[1]):\n            raise TypeError(f'Expected {self._types[1].__name__}')\n        if value < 0:\n            raise ValueError('shares must be >= 0')\n        self._shares = value\n\n    @property\n    def price(self):\n        return self._price\n    @price.setter\n    def price(self, value):\n        if not isinstance(value, self._types[2]):\n            raise TypeError(f'Expected {self._types[2].__name__}')\n        if value < 0:\n            raise ValueError('price must be >= 0')\n        self._price = value\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n```\n\n\n\n[Back](ex3_4.md)\n"
  },
  {
    "path": "Exercises/soln3_5.md",
    "content": "# Exercise 3.5 - Solution\n\n```python\n# tableformat.py\n\ndef print_table(records, fields, formatter):\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n\nclass TableFormatter:\n    def headings(self, headers):\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(' '.join('%10s' % h for h in headers))\n        print(('-'*10 + ' ')*len(headers))\n    \n    def row(self, rowdata):\n        print(' '.join('%10s' % d for d in rowdata))\n\nclass CSVTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(str(d) for d in rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print('<tr>', end=' ')\n        for h in headers:\n            print('<th>%s</th>' % h, end=' ')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end=' ')\n        for d in rowdata:\n            print('<td>%s</td>' % d, end=' ')\n        print('</tr>')\n\ndef create_formatter(name):\n    if name == 'text':\n        formatter_cls = TextTableFormatter\n    elif name == 'csv':\n        formatter_cls = CSVTableFormatter\n    elif name == 'html':\n        formatter_cls = HTMLTableFormatter\n    else:\n        raise RuntimeError('Unknown format %s' % name)\n    return formatter_cls()\n```\n\n\n\n[Back](ex3_5.md)\n"
  },
  {
    "path": "Exercises/soln3_6.md",
    "content": "# Exercise 3.6 - Solution\n\n## (a) Better output for printing objects\n\n```python\n# stock.py\n\nclass Stock:\n    ...\n\n    def __repr__(self):\n        # Note: The !r format code produces the repr() string\n        return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})'\n    ...\n```\n\n## (b) Making Objects Comparable\n\n```python\nclass Stock:\n    ...\n    def __eq__(self, other):\n        return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==\n                                 (other.name, other.shares, other.price))\n    ...\n```\n\n## (c) Context Managers\n\nCode is given in the exercise.\n\n\n\n[Back](ex3_6.md)\n"
  },
  {
    "path": "Exercises/soln3_7.md",
    "content": "\n# Exercise 3.7 - Solution\n\n## (a) Interfaces\n\n```python\n# tableformat.py\n\ndef print_table(records, fields, formatter):\n    if not isinstance(formatter, TableFormatter):\n        raise TypeError('Expected a TableFormatter')\n\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n...\n```\n\n## (b) Abstract Base Classes\n\n```python\n# tableformat.py\nfrom abc import ABC, abstractmethod\n\nclass TableFormatter(ABC):\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n```\n\n## (c) Algorithm Template Classes\n\n```python\n# reader.py\n\nimport csv\nfrom abc import ABC, abstractmethod\n\nclass CSVParser(ABC):\n    def parse(self, filename):\n        records = []\n        with open(filename) as f:\n            rows = csv.reader(f)\n            headers = next(rows)\n            for row in rows:\n                record = self.make_record(headers, row)\n                records.append(record)\n        return records\n\n    @abstractmethod\n    def make_record(self, headers, row):\n        pass\n\nclass DictCSVParser(CSVParser):\n    def __init__(self, types):\n        self.types = types\n\n    def make_record(self, headers, row):\n        return { name: func(val) for name, func, val in zip(headers, self.types, row) }\n\nclass InstanceCSVParser(CSVParser):\n    def __init__(self, cls):\n        self.cls = cls\n\n    def make_record(self, headers, row):\n        return self.cls.from_row(row)\n\ndef read_csv_as_dicts(filename, types):\n    parser = DictCSVParser(types)\n    return parser.parse(filename)\n\ndef read_csv_as_instances(filename, cls):\n    parser = InstanceCSVParser(cls)\n    return parser.parse(filename)\n```\n\n\n\n\n[Back](ex3_7.md)\n"
  },
  {
    "path": "Exercises/soln3_8.md",
    "content": "# Exercise 3.8 - Solution\n\n```python\n# tableformat.py\nfrom abc import ABC, abstractmethod\n\ndef print_table(records, fields, formatter):\n    if not isinstance(formatter, TableFormatter):\n        raise RuntimeError('Expected a TableFormatter')\n\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n\nclass TableFormatter(ABC):\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n\n\nclass TextTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(' '.join('%10s' % h for h in headers))\n        print(('-'*10 + ' ')*len(headers))\n    \n    def row(self, rowdata):\n        print(' '.join('%10s' % d for d in rowdata))\n\nclass CSVTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(str(d) for d in rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print('<tr>', end=' ')\n        for h in headers:\n            print('<th>%s</th>' % h, end=' ')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end=' ')\n        for d in rowdata:\n            print('<td>%s</td>' % d, end=' ')\n        print('</tr>')\n\n\nclass ColumnFormatMixin:\n    formats = []\n    def row(self, rowdata):\n        rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)]\n        super().row(rowdata)\n\nclass UpperHeadersMixin:\n    def headings(self, headers):\n        super().headings([h.upper() for h in headers])\n\ndef create_formatter(name, column_formats=None, upper_headers=False):\n    if name == 'text':\n        formatter_cls = TextTableFormatter\n    elif name == 'csv':\n        formatter_cls = CSVTableFormatter\n    elif name == 'html':\n        formatter_cls = HTMLTableFormatter\n    else:\n        raise RuntimeError('Unknown format %s' % name)\n\n    if column_formats:\n        class formatter_cls(ColumnFormatMixin, formatter_cls):\n              formats = column_formats\n\n    if upper_headers:\n        class formatter_cls(UpperHeadersMixin, formatter_cls):\n            pass\n\n    return formatter_cls()\n```\n\n\n\n[Back](ex3_8.md)\n"
  },
  {
    "path": "Exercises/soln4_1.md",
    "content": "# Exercise 4.1 - Solution\n\nNo solution.  Just follow along.\n\n\n\n[Back](ex4_1.md)\n"
  },
  {
    "path": "Exercises/soln4_2.md",
    "content": "# Exercise 4.2 - Solution\n\nHere is the validator code in its entirety:\n\n```python\nclass Validator:\n    @classmethod\n    def check(cls, value):\n        return value\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'Expected {cls.expected_type}')\n        return super().check(value)\n\nclass Integer(Typed):\n    expected_type = int\n\nclass Float(Typed):\n    expected_type = float\n\nclass String(Typed):\n    expected_type = str\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('Must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('Must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n```\n\n## (c) Using the validators\n\n```python\n# validate.py\n...\n\nif __name__ == '__main__':\n    class Stock:\n        __slots__ = ('name', '_shares', '_price')\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def shares(self):\n            return self._shares\n        @shares.setter\n        def shares(self, value):\n            self._shares = PositiveInteger.check(value)\n\n        @property\n        def price(self):\n            return self._price\n        @price.setter\n        def price(self, value):\n            self._price = PositiveFloat.check(value)\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        def sell(self, nshares):\n            self.shares -= nshares\n```\n\n\n\n[Back](ex4_2.md)\n"
  },
  {
    "path": "Exercises/soln4_3.md",
    "content": "# Exercise 4.3 - Solution\n\n```python\nclass Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance, value):\n        instance.__dict__[self.name] = self.check(value)\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        return super().check(value)\n\nclass Integer(Typed):\n    expected_type = int\n\nclass Float(Typed):\n    expected_type = float\n\nclass String(Typed):\n    expected_type = str\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\n# Examples\nif __name__ == '__main__':\n    def add(x, y):\n        Integer.check(x)\n        Integer.check(y)\n        return x + y\n\n    class Stock:\n        name = NonEmptyString()\n        shares = PositiveInteger()\n        price = PositiveFloat()\n        def __init__(self,name,shares,price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        def sell(self, nshares):\n            self.shares -= nshares\n```\n\n\n\n[Back](ex4_3.md)\n"
  },
  {
    "path": "Exercises/soln4_4.md",
    "content": "# Exercise 4.4 - Solution\n\nCode is given in the exercise.\n\n\n\n[Back](ex4_4.md)\n"
  },
  {
    "path": "Exercises/soln5_1.md",
    "content": "# Exercise 5.1 - Solution\n\nThis is a partial solution that does not include type-hints. \n\n```python\n# reader.py\n\nimport csv\n\ndef csv_as_dicts(lines, types, *, headers=None):\n    '''\n    Convert lines of CSV data into a list of dictionaries\n    '''\n    records = []\n    rows = csv.reader(lines)\n    if headers is None:\n        headers = next(rows)\n    for row in rows:\n        record = { name: func(val)\n                   for name, func, val in zip(headers, types, row) }\n        records.append(record)\n    return records\n\ndef csv_as_instances(lines, cls, *, headers=None):\n    '''\n    Convert lines of CSV data into a list of instances\n    '''\n    records = []\n    rows = csv.reader(lines)\n    if headers is None:\n        headers = next(rows)\n    for row in rows:\n        record = cls.from_row(row)\n        records.append(record)\n    return records\n\ndef read_csv_as_dicts(filename, types, *, headers=None):\n    '''\n    Read CSV data into a list of dictionaries with optional type conversion\n    '''\n    with open(filename) as file:\n        return csv_as_dicts(file, types, headers=headers)\n\ndef read_csv_as_instances(filename, cls, *, headers=None):\n    '''\n    Read CSV data into a list of instances\n    '''\n    with open(filename) as file:\n        return csv_as_instances(file, cls, headers=headers)\n```\n\n\n\n\n[Back](ex5_1.md)\n"
  },
  {
    "path": "Exercises/soln5_2.md",
    "content": "# Exercise 5.2 - Solution\n\nNone. Just work through the exercise.\n\n\n\n[Back](ex5_2.md)\n"
  },
  {
    "path": "Exercises/soln5_3.md",
    "content": "# Exercise 5.3 - Solution\n\n## (a) Higher order functions\n\n```python\n# reader.py\n\nimport csv\n\ndef convert_csv(lines, converter, *, headers=None):\n    records = []\n    rows = csv.reader(lines)\n    if headers is None:\n        headers = next(rows)\n    for row in rows:\n        record = converter(headers, row)\n        records.append(record)\n    return records\n\ndef csv_as_dicts(lines, types, *, headers=None):\n    return convert_csv(lines, \n                       lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) })\n\ndef csv_as_instances(lines, cls, *, headers=None):\n    return convert_csv(lines,\n                       lambda headers, row: cls.from_row(row))\n\ndef read_csv_as_dicts(filename, types, *, headers=None):\n    '''\n    Read CSV data into a list of dictionaries with optional type conversion\n    '''\n    with open(filename) as file:\n        return csv_as_dicts(file, types, headers=headers)\n\ndef read_csv_as_instances(filename, cls, *, headers=None):\n    '''\n    Read CSV data into a list of instances\n    '''\n    with open(filename) as file:\n        return csv_as_instances(file, cls, headers=headers)\n```\n\n## (b) Using map()\n\n```python\n# reader.py\n\nimport csv\n\ndef convert_csv(lines, converter, *, headers=None):\n    rows = csv.reader(lines)\n    if headers is None:\n        headers = next(rows)\n    return list(map(lambda row: converter(headers, row), rows))\n```\n\n\n\n[Back](ex5_3.md)\n"
  },
  {
    "path": "Exercises/soln5_4.md",
    "content": "# Exercise 5.4 - Solution\n\n```python\n# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n\n    @property\n    def value(self):\n        return getattr(self, private_name)\n\n    @value.setter\n    def value(self, val):\n        if not isinstance(val, expected_type):\n            raise TypeError(f'Expected {expected_type}')\n        setattr(self, private_name, val)\n\n    return value\n\n\nString = lambda name: typedproperty(name, str)\nInteger = lambda name: typedproperty(name, int)\nFloat = lambda name: typedproperty(name, float)\n\n# Example\nif __name__ == '__main__':\n    class Stock:\n        name = String('name')\n        shares = Integer('shares')\n        price = Float('price')\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n```\n\n\n[Back](ex5_4.md)\n"
  },
  {
    "path": "Exercises/soln5_5.md",
    "content": "# Exercise 5.5 - Solution\n\n```python\n# reader.py\n\nimport csv\nimport logging\n\nlog = logging.getLogger(__name__)\n\ndef convert_csv(lines, converter, *, headers=None):\n    rows = csv.reader(lines)\n    if headers is None:\n        headers = next(rows)\n\n    records = []\n    for rowno, row in enumerate(rows, start=1):\n        try:\n            records.append(converter(headers, row))\n        except ValueError as e:\n            log.warning('Row %s: Bad row: %s', rowno, row)\n            log.debug('Row %s: Reason: %s', rowno, row)\n    return records\n\ndef csv_as_dicts(lines, types, *, headers=None):\n    return convert_csv(lines, \n                       lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) })\n\ndef csv_as_instances(lines, cls, *, headers=None):\n    return convert_csv(lines,\n                       lambda headers, row: cls.from_row(row))\n\ndef read_csv_as_dicts(filename, types, *, headers=None):\n    '''\n    Read CSV data into a list of dictionaries with optional type conversion\n    '''\n    with open(filename) as file:\n        return csv_as_dicts(file, types, headers=headers)\n\ndef read_csv_as_instances(filename, cls, *, headers=None):\n    '''\n    Read CSV data into a list of instances\n    '''\n    with open(filename) as file:\n        return csv_as_instances(file, cls, headers=headers)\n```\n\n\n\n[Back](ex5_5.md)\n"
  },
  {
    "path": "Exercises/soln5_6.md",
    "content": "# Exercise 5.6 - Solution\n\n## (b) Unit testing\n\n```python\n# teststock.py\n\nimport stock\nimport unittest\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_create_keyword(self):\n        s = stock.Stock(name='GOOG', shares=100, price=490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n        \n    def test_cost(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.cost, 49010.0)\n\n    def test_sell(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        s.sell(25)\n        self.assertEqual(s.shares, 75)\n\n    def test_from_row(self):\n        s = stock.Stock.from_row(['GOOG','100','490.1'])\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_repr(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(repr(s), \"Stock('GOOG', 100, 490.1)\")\n\n    def test_eq(self):\n        a = stock.Stock('GOOG', 100, 490.1)\n        b = stock.Stock('GOOG', 100, 490.1)\n        self.assertTrue(a==b)\n\nif __name__ == '__main__':\n    unittest.main()\n```\n\n## (c) Testing for Errors\n\n```python\n# teststock.py\n\nimport stock\nimport unittest\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_create_keyword(self):\n        s = stock.Stock(name='GOOG', shares=100, price=490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n        \n    def test_cost(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.cost, 49010.0)\n\n    def test_sell(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        s.sell(25)\n        self.assertEqual(s.shares, 75)\n\n    def test_from_row(self):\n        s = stock.Stock.from_row(['GOOG','100','490.1'])\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_repr(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(repr(s), \"Stock('GOOG', 100, 490.1)\")\n\n    def test_eq(self):\n        a = stock.Stock('GOOG', 100, 490.1)\n        b = stock.Stock('GOOG', 100, 490.1)\n        self.assertTrue(a==b)\n\n    # Tests for failure conditions\n    def test_shares_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.shares = '50'\n\n    def test_shares_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.shares = -50\n\n    def test_price_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.price = '45.23'\n\n    def test_price_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.price = -45.23\n\n    def test_bad_attribute(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(AttributeError):\n            s.share = 100\n\nif __name__ == '__main__':\n    unittest.main()\n```\n\n\n\n[Back](ex5_6.md)\n"
  },
  {
    "path": "Exercises/soln6_1.md",
    "content": "# Exercise 6.1 - Solution\n\n## (a) Simplified Structures\n\n```python\n# structure.py\n\nclass Structure:\n    _fields = ()\n    def __init__(self, *args):\n        if len(args) != len(self._fields):\n            raise TypeError('Expected %d arguments' % len(self._fields))\n        for name, arg in zip(self._fields,args):\n            setattr(self, name, arg)\n```\n\n## (b) Making a Useful Representation\n\n[source, python]\n```\nclass Structure:\n    ...    \n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n```\n\n## (c) Restricting Attribute Names\n\n[source, python]\n```\nclass Structure:\n    ...    \n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n```\n\n\n\n[Back](ex6_1.md)\n"
  },
  {
    "path": "Exercises/soln6_2.md",
    "content": "# Exercise 6.2 - Solution\n\n```python\n# structure.py\n\nimport sys\n\nclass Structure:\n    _fields = ()\n\n    @staticmethod\n    def _init():\n        locs = sys._getframe(1).f_locals\n        self = locs['self']\n        for name, val in locs.items():\n            if name == 'self': continue\n            setattr(self, name, val)\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n```\n\n\n\n\n[Back](ex6_2.md)\n"
  },
  {
    "path": "Exercises/soln6_3.md",
    "content": "# Exercise 6.3 - Solution\n\n```python\n# structure.py\n\nimport sys\nimport inspect\n\nclass Structure:\n    _fields = ()\n\n    @staticmethod\n    def _init():\n        locs = sys._getframe(1).f_locals\n        self = locs['self']\n        for name, val in locs.items():\n            if name == 'self': continue\n            setattr(self, name, val)\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    @classmethod\n    def set_fields(cls):\n        sig = inspect.signature(cls)\n        cls._fields = tuple(sig.parameters)\n```\n\nHere is the `Stock` class in progress:\n\n```python\n# stock.py\n\nfrom structure import Structure\n\nclass Stock(Structure):\n    def __init__(self, name, shares, price):\n        self._init()\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\nStock.set_fields()\n```\n\n\n\n[Back](ex6_3.md)\n"
  },
  {
    "path": "Exercises/soln6_4.md",
    "content": "# Exercise 6.4 - Solution\n\n```python\n# structure.py\n\nclass Structure:\n    _fields = ()\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    @classmethod\n    def create_init(cls):\n        args = ','.join(cls._fields)\n        code = 'def __init__(self, {0}):\\n'.format(args)\n        statements = [ '    self.{0} = {0}'.format(name) for name in cls._fields]\n        code += '\\n'.join(statements)\n        locs = { }\n        exec(code, locs)\n        cls.__init__ = locs['__init__']\n```\n\nHere is the `Stock` class in progress after this change:\n\n```python\n# stock.py\n\nfrom structure import Structure\n\nclass Stock(Structure):\n    _fields = ('name', 'shares', 'price')\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\nStock.create_init()\n```\n\n\n\n[Back](ex6_4.md)\n"
  },
  {
    "path": "Exercises/soln6_5.md",
    "content": "# Exercise 6.5 - Solution\n\n```python\n# validate.py\n...\n\nfrom inspect import signature\n\nclass ValidatedFunction:\n    def __init__(self, func):\n        self.func = func\n        self.signature = signature(func)\n        self.annotations = dict(func.__annotations__)\n        self.retcheck = self.annotations.pop('return', None)\n\n    def __call__(self, *args, **kwargs):\n        bound = self.signature.bind(*args, **kwargs)\n\n        for name, val in self.annotations.items():\n            val.check(bound.arguments[name])\n\n        result = self.func(*args, **kwargs)\n\n        if self.retcheck:\n            self.retcheck.check(result)\n\n        return result\n\n# Examples\nif __name__ == '__main__':\n    def add(x:Integer, y:Integer) -> Integer:\n        return x + y\n\n    add = ValidatedFunction(add)\n```\n\n\n\n[Back](ex6_5.md)\n"
  },
  {
    "path": "Exercises/soln7_1.md",
    "content": "# Exercise 7.1 - Solution\n\n```python\n# validate.py\n...\n\nfrom inspect import signature\n\ndef validated(func):\n    sig = signature(func)\n\n    # Gather the function annotations\n    annotations = dict(func.__annotations__)\n\n    # Get the return annotation (if any)\n    retcheck = annotations.pop('return', None)\n\n    def wrapper(*args, **kwargs):\n        bound = sig.bind(*args, **kwargs)\n        errors = []\n\n        # Enforce argument checks\n        for name, validator in annotations.items():\n            try:\n                validator.check(bound.arguments[name])\n            except Exception as e:\n                errors.append(f'    {name}: {e}')\n\n        if errors:\n            raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n        result = func(*args, **kwargs)\n\n        # Enforce return check (if any)\n        if retcheck:\n            try:\n                retcheck.check(result)\n            except Exception as e:\n                raise TypeError(f'Bad return: {e}') from None\n        return result\n\n    return wrapper\n```\n\n\n\n[Back](ex7_1.md)\n"
  },
  {
    "path": "Exercises/soln7_2.md",
    "content": "# Exercise 7.2 - Solution\n\n## (a) Copying Metadata\n\n```python\n# logcall.py\n\nfrom functools import wraps\n\ndef logged(func):\n    print('Adding logging to', func.__name__)\n    @wraps(func)\n    def wrapper(*args,**kwargs):\n        print('Calling', func.__name__)\n        return func(*args,**kwargs)\n    return wrapper\n```\n\n## (b) Decorators with arguments\n\n```python\n# logcall.py\n\nfrom functools import wraps\n...\ndef logformat(fmt):\n    def logged(func):\n        print('Adding logging to', func.__name__)\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            print(fmt.format(func=func))\n            return func(*args, **kwargs)\n        return wrapper\n    return logged\n```\n\nThe earlier `@logged` decorator can be rewritten as follows:\n\n```python\nlogged = logformat('Calling {func.__name__}')\n```\n\n## (c) Decorators and methods\n\nYou can get the code to work if you interchange the order of the \ndecorators.  For example:\n\n```python\nfrom logcall import logged\n\nclass Spam:\n    @logged\n    def instance_method(self):\n        pass\n\n    @classmethod\n    @logged\n    def class_method(cls):\n        pass\n\n    @staticmethod\n    @logged\n    def static_method():\n        pass\n\n    @property\n    @logged\n    def property_method(self):\n        pass\n```\n\nPonder why it doesn't work in the original order.  Is there any way to make\nthe `@logged` decorator work regardless of the order in which its applied?\n\n## (d) Validation (Redux)\n\n```python\n# validate.py\n...\n\nfrom inspect import signature\nfrom functools import wraps\n\ndef validated(func):\n    sig = signature(func)\n\n    # Gather the function annotations\n    annotations = dict(func.__annotations__)\n\n    # Get the return annotation (if any)\n    retcheck = annotations.pop('return', None)\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        bound = sig.bind(*args, **kwargs)\n        errors = []\n\n        # Enforce argument checks\n        for name, validator in annotations.items():\n            try:\n                validator.check(bound.arguments[name])\n            except Exception as e:\n                errors.append(f'    {name}: {e}')\n\n        if errors:\n            raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n        result = func(*args, **kwargs)\n\n        # Enforce return check (if any)\n        if retcheck:\n            try:\n                retcheck.check(result)\n            except Exception as e:\n                raise TypeError(f'Bad return: {e}') from None\n        return result\n\n    return wrapper\n\ndef enforce(**annotations):\n    retcheck = annotations.pop('return_', None)\n\n    def decorate(func):\n        sig = signature(func)\n\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            bound = sig.bind(*args, **kwargs)\n            errors = []\n\n            # Enforce argument checks\n            for name, validator in annotations.items():\n                try:\n                    validator.check(bound.arguments[name])\n                except Exception as e:\n                    errors.append(f'    {name}: {e}')\n\n            if errors:\n                raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n            result = func(*args, **kwargs)\n\n            if retcheck:\n                try:\n                    retcheck.check(result)\n                except Exception as e:\n                    raise TypeError(f'Bad return: {e}') from None\n            return result\n        return wrapper\n    return decorate\n```\n\n\n\n\n[Back](ex7_2.md)\n"
  },
  {
    "path": "Exercises/soln7_3.md",
    "content": "# Exercise 7.3 - Solution\n\nHere's a full version of the `structure.py` file.   \n\n```python\n# structure.py\n\nfrom validate import Validator, validated\n\nclass Structure:\n    _fields = ()\n    _types = ()\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    @classmethod\n    def from_row(cls, row):\n        rowdata = [ func(val) for func, val in zip(cls._types, row) ]\n        return cls(*rowdata)\n\n    @classmethod\n    def create_init(cls):\n        '''\n        Create an __init__ method from _fields\n        '''\n        args = ','.join(cls._fields)\n        code = f'def __init__(self, {args}):\\n'\n        for name in cls._fields:\n            code += f'    self.{name} = {name}\\n'\n        locs = { }\n        exec(code, locs)\n        cls.__init__ = locs['__init__']\n\n    @classmethod\n    def __init_subclass__(cls):\n        # Apply the validated decorator to subclasses\n        validate_attributes(cls)\n\ndef validate_attributes(cls):\n    '''\n    Class decorator that scans a class definition for Validators\n    and builds a _fields variable that captures their definition order.\n    '''\n    validators = []\n    for name, val in vars(cls).items():\n        if isinstance(val, Validator):\n            validators.append(val)\n\n        # Apply validated decorator to any callable with annotations\n        elif callable(val) and val.__annotations__:\n            setattr(cls, name, validated(val))\n\n    # Collect all of the field names\n    cls._fields = tuple([v.name for v in validators])\n\n    # Collect type conversions. The lambda x:x is an identity\n    # function that's used in case no expected_type is found.\n    cls._types = tuple([ getattr(v, 'expected_type', lambda x: x)\n                   for v in validators ])\n\n    # Create the __init__ method\n    if cls._fields:\n        cls.create_init()\n    \n    return cls\n```\n\n\n\n[Back](ex7_3.md)\n"
  },
  {
    "path": "Exercises/soln7_4.md",
    "content": "# Exercise 7.4 - Solution\n\nAll of the solution code is given in the exercise description.\n\n\n\n[Back](ex7_4.md)\n"
  },
  {
    "path": "Exercises/soln7_5.md",
    "content": "# Exercise 7.5 - Solution\n\nNothing here.  Just follow the exercise.\n\n\n\n[Back](ex7_5.md)\n"
  },
  {
    "path": "Exercises/soln7_6.md",
    "content": "# Exercise 7.6 - Solution\n\n```python\n# structure.py\n\nfrom validate import Validator, validated\nfrom collections import ChainMap\n\nclass StructureMeta(type):\n    @classmethod\n    def __prepare__(meta, clsname, bases):\n        return ChainMap({}, Validator.validators)\n        \n    @staticmethod\n    def __new__(meta, name, bases, methods):\n        methods = methods.maps[0]\n        return super().__new__(meta, name, bases, methods)\n\nclass Structure(metaclass=StructureMeta):\n    _fields = ()\n    _types = ()\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    @classmethod\n    def from_row(cls, row):\n        rowdata = [ func(val) for func, val in zip(cls._types, row) ]\n        return cls(*rowdata)\n\n\n    @classmethod\n    def create_init(cls):\n        '''\n        Create an __init__ method from _fields\n        '''\n        args = ','.join(cls._fields)\n        code = f'def __init__(self, {args}):\\n'\n        for name in cls._fields:\n            code += f'    self.{name} = {name}\\n'\n        locs = { }\n        exec(code, locs)\n        cls.__init__ = locs['__init__']\n\n    @classmethod\n    def __init_subclass__(cls):\n        # Apply the validated decorator to subclasses\n        validate_attributes(cls)\n\ndef validate_attributes(cls):\n    '''\n    Class decorator that scans a class definition for Validators\n    and builds a _fields variable that captures their definition order.\n    '''\n    validators = []\n    for name, val in vars(cls).items():\n        if isinstance(val, Validator):\n            validators.append(val)\n\n        # Apply validated decorator to any callable with annotations\n        elif callable(val) and val.__annotations__:\n            setattr(cls, name, validated(val))\n\n    # Collect all of the field names\n    cls._fields = tuple([v.name for v in validators])\n\n    # Collect type conversions. The lambda x:x is an identity\n    # function that's used in case no expected_type is found.\n    cls._types = tuple([ getattr(v, 'expected_type', lambda x: x)\n                   for v in validators ])\n\n    # Create the __init__ method\n    if cls._fields:\n        cls.create_init()\n\n    \n    return cls\n\ndef typed_structure(clsname, **validators):\n    cls = type(clsname, (Structure,), validators)\n    return cls\n```\n\n\n\n[Back](ex7_6.md)\n"
  },
  {
    "path": "Exercises/soln8_1.md",
    "content": "# Exercise 8.1 - Solution\n\n## (b) Adding Iteration to Objects\n\n```python\n# structure.py\n...\n        \nclass Structure(metaclass=StructureMeta):\n    ...\n    def __iter__(self):\n        for name in self._fields:\n            yield getattr(self, name)\n    ...\n```\n\n## (c) The Surprising Power of Iteration\n\n```python\n# structure.py\n...\n        \nclass Structure(metaclass=StructureMeta):\n    ...\n    def __eq__(self, other):\n        return isinstance(other, type(self)) and tuple(self) == tuple(other)\n    ...\n```\n\n## (d) Monitoring a streaming data source\n\n```python\n# follow.py\nimport os\nimport time\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being written at the end of a file.\n    '''\n    with open(filename,'r') as f:\n        f.seek(0,os.SEEK_END)\n        while True:\n             line = f.readline()\n             if line == '':\n                 time.sleep(0.1)    # Sleep briefly to avoid busy wait\n                 continue\n             yield line\n\n# Example use\nif __name__ == '__main__':\n    for line in follow('Data/stocklog.csv'):\n        row = line.split(',')\n        name = row[0].strip('\"')\n        price = float(row[1])\n        change = float(row[4])\n        if change < 0:\n            print('%10s %10.2f %10.2f' % (name, price, change))\n```\n\n**Discussion**\n\nThe `time.sleep()` function is being used here to avoid busy-waiting\non the CPU (e.g., sitting in an infinite loop with 100% CPU use while waiting for new lines to arrive)\n\n\n\n[Back](ex8_1.md)\n"
  },
  {
    "path": "Exercises/soln8_2.md",
    "content": "# Exercise 8.2 - Solution\n\n```python\n# ticker.py\nfrom structure import Structure\n\nclass Ticker(Structure):\n    name = String()\n    price = Float()\n    date = String()\n    time = String()\n    change = Float()\n    open = Float()\n    high = Float()\n    low = Float()\n    volume = Integer()\n\nif __name__ == '__main__':\n    from follow import follow\n    import csv\n    from tableformat import create_formatter, print_table\n\n    formatter = create_formatter('text')\n\n    lines = follow('Data/stocklog.csv')\n    rows = csv.reader(lines)\n    records = (Ticker.from_row(row) for row in rows)\n    negative = (rec for rec in records if rec.change < 0)\n    print_table(negative, ['name','price','change'], formatter)\n```\n\n\n\n[Back](ex8_2.md)\n"
  },
  {
    "path": "Exercises/soln8_3.md",
    "content": "# Exercise 8.3 - Solution\n\n```python\n# coticker.py\nfrom structure import Structure\nfrom validate import String, Integer, Float\n\nclass Ticker(Structure):\n    name = String()\n    price = Float()\n    date = String()\n    time = String()\n    change = Float()\n    open = Float()\n    high = Float()\n    low = Float()\n    volume = Integer()\n\nfrom cofollow import consumer, follow\nfrom tableformat import create_formatter\nimport csv\n\n# Coroutine that splits rows using the CSV module.  This is rather\n# mind-bending due to the fact that the csv module only understands\n# iteration with the for-loop.  To make it work, we wrap it around\n# a generator that simply produces the last item received.\n\n@consumer\ndef to_csv(target):\n    def producer():\n        while True:\n            yield line\n\n    reader = csv.reader(producer())\n    while True:\n        line = yield\n        target.send(next(reader))\n\n@consumer\ndef create_ticker(target):\n    while True:\n        row = yield\n        target.send(Ticker.from_row(row))\n\n@consumer\ndef negchange(target):\n    while True:\n        record = yield\n        if record.change < 0:\n            target.send(record)\n\n@consumer\ndef ticker(fmt, fields):\n    formatter = create_formatter(fmt)\n    formatter.headings(fields)\n    while True:\n        rec = yield\n        row = [getattr(rec, name) for name in fields]\n        formatter.row(row)\n\nif __name__ == '__main__':\n    follow('Data/stocklog.csv',\n           to_csv(\n           create_ticker(\n           negchange(\n           ticker('text', ['name','price','change'])))))\n```\n\n\n\n[Back](ex8_3.md)\n"
  },
  {
    "path": "Exercises/soln8_4.md",
    "content": "# Exercise 8.4 - Solution\n\nAll of the code is given in the exercise description.\n\n\n\n[Back](ex8_4.md)\n"
  },
  {
    "path": "Exercises/soln8_5.md",
    "content": "# Exercise 8.5 - Solution\n\n## (b) Generators as Tasks Serving Network Connections\n\nHere's the complete code.\n\n```python\n# server.py\n\nfrom socket import *\nfrom select import select\nfrom collections import deque\n\ntasks = deque()\nrecv_wait = {}   #  sock -> task\nsend_wait = {}   #  sock -> task\n\ndef run():\n    while any([tasks, recv_wait, send_wait]):\n        while not tasks:\n            can_recv, can_send, _ = select(recv_wait, send_wait, [])\n            for s in can_recv:\n                tasks.append(recv_wait.pop(s))\n            for s in can_send:\n                tasks.append(send_wait.pop(s))\n        task = tasks.popleft()\n        try:\n            reason, resource = task.send(None)\n            if reason == 'recv':\n                recv_wait[resource] = task\n            elif reason == 'send':\n                send_wait[resource] = task\n            else:\n                raise RuntimeError('Unknown reason %r' % reason)\n        except StopIteration:\n            print('Task done')\n\ndef tcp_server(address, handler):\n    sock = socket(AF_INET, SOCK_STREAM)\n    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)\n    sock.bind(address)\n    sock.listen(5)\n    while True:\n        yield 'recv', sock\n        client, addr = sock.accept()\n        tasks.append(handler(client, addr))\n        \ndef echo_handler(client, address):\n    print('Connection from', address)\n    while True:\n        yield 'recv', client\n        data = client.recv(1000)\n        if not data:\n            break\n        yield 'send', client\n        client.send(b'GOT:' + data)\n    print('Connection closed')\n\nif __name__ == '__main__':\n    tasks.append(tcp_server(('',25000), echo_handler))\n    run()\n```\n\n\n\n[Back](ex8_5.md)\n"
  },
  {
    "path": "Exercises/soln8_6.md",
    "content": "# Exercise 8.6 - Solution\n\n## (a) Receiving Messages\n\n```python\n# coticker.py\nfrom structure import Structure\nfrom validate import String, Integer, Float\n\nclass Ticker(Structure):\n    name = String()\n    price = Float()\n    date = String()\n    time = String()\n    change = Float()\n    open = Float()\n    high = Float()\n    low = Float()\n    volume = Integer()\n\nfrom cofollow import consumer, follow, receive\nfrom tableformat import create_formatter\nimport csv\n\n@consumer\ndef to_csv(target):\n    def producer():\n        while True:\n            yield line\n\n    reader = csv.reader(producer())\n    while True:\n        line = yield from receive(str)\n        target.send(next(reader))\n\n@consumer\ndef create_ticker(target):\n    while True:\n        row = yield from receive(list)\n        target.send(Ticker.from_row(row))\n\n@consumer\ndef negchange(target):\n    while True:\n        record = yield from receive(Ticker)\n        if record.change < 0:\n            target.send(record)\n\n@consumer\ndef ticker(fmt, fields):\n    formatter = create_formatter('text')\n    formatter.headings(fields)\n    while True:\n        rec = yield from receive(Ticker)\n        row = [getattr(rec, name) for name in fields]\n        formatter.row(row)\n\nif __name__ == '__main__':\n    follow('Data/stocklog.csv',\n           to_csv(\n           create_ticker(\n           negchange(\n           ticker('text', ['name','price','change'])))))\n```\n\n## (b) Wrapping a Socket\n\n```python\n# server.py\n...\n\nclass GenSocket:\n    def __init__(self, sock):\n        self.sock = sock\n\n    def accept(self):\n        yield 'recv', self.sock\n        client, addr = self.sock.accept()\n        return GenSocket(client), addr\n\n    def recv(self, maxsize):\n        yield 'recv', self.sock\n        return self.sock.recv(maxsize)\n\n    def send(self, data):\n        yield 'send', self.sock\n        return self.sock.send(data)\n\n    def __getattr__(self, name):\n        return getattr(self.sock, name)\n\ndef tcp_server(address, handler):\n    sock = GenSocket(socket(AF_INET, SOCK_STREAM))\n    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)\n    sock.bind(address)\n    sock.listen(5)\n    while True:\n        client, addr = yield from sock.accept()\n        tasks.append(handler(client, addr))\n        \ndef echo_handler(client, address):\n    print('Connection from', address)\n    while True:\n        data = yield from client.recv(1000)\n        if not data:\n            break\n        yield from client.send(b'GOT:' + data)\n    print('Connection closed')\n\nif __name__ == '__main__':\n    tasks.append(tcp_server(('',25000), echo_handler))\n    run()\n```\n\n## (c) Async/Await\n\n```python\n# server.py\n\nfrom socket import *\nfrom select import select\nfrom collections import deque\nfrom types import coroutine\n\ntasks = deque()\nrecv_wait = {}   #  sock -> task\nsend_wait = {}   #  sock -> task\n\ndef run():\n    while any([tasks, recv_wait, send_wait]):\n        while not tasks:\n            can_recv, can_send, _ = select(recv_wait, send_wait, [])\n            for s in can_recv:\n                tasks.append(recv_wait.pop(s))\n            for s in can_send:\n                tasks.append(send_wait.pop(s))\n        task = tasks.popleft()\n        try:\n            reason, resource = task.send(None)\n            if reason == 'recv':\n                recv_wait[resource] = task\n            elif reason == 'send':\n                send_wait[resource] = task\n            else:\n                raise RuntimeError('Unknown reason %r' % reason)\n        except StopIteration:\n            print('Task done')\n\nclass GenSocket:\n    def __init__(self, sock):\n        self.sock = sock\n\n    @coroutine\n    def accept(self):\n        yield 'recv', self.sock\n        client, addr = self.sock.accept()\n        return GenSocket(client), addr\n\n    @coroutine\n    def recv(self, maxsize):\n        yield 'recv', self.sock\n        return self.sock.recv(maxsize)\n\n    @coroutine\n    def send(self, data):\n        yield 'send', self.sock\n        return self.sock.send(data)\n\n    def __getattr__(self, name):\n        return getattr(self.sock, name)\n\nasync def tcp_server(address, handler):\n    sock = GenSocket(socket(AF_INET, SOCK_STREAM))\n    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)\n    sock.bind(address)\n    sock.listen(5)\n    while True:\n        client, addr = await sock.accept()\n        tasks.append(handler(client, addr))\n        \nasync def echo_handler(client, address):\n    print('Connection from', address)\n    while True:\n        data = await client.recv(1000)\n        if not data:\n            break\n        await client.send(b'GOT:' + data)\n    print('Connection closed')\n\nif __name__ == '__main__':\n    tasks.append(tcp_server(('',25000), echo_handler))\n    run()\n```\n\n\n\n[Back](ex8_6.md)\n"
  },
  {
    "path": "Exercises/soln9_1.md",
    "content": "# Exercise 9.1 - Solution\n\nNone.  Just follow the exercise steps.\n\n\n\n[Back](ex9_1.md)\n"
  },
  {
    "path": "Exercises/soln9_2.md",
    "content": "# Exercise 9.2 - Solution\n\n## (a) Making a package\n\nYou don't need to modify much source code.   Just make a directory with\nthis structure:\n\n```\nstructly/\n    __init__.py\n    validate.py\n    reader.py\n    structure.py\n    tableformat.py\n```\n\nThe `__init__.py` file can be empty.  You need to make one small change to the `structure.py` file to make the import statement work.\n\n```python\n# structure.py\n\n...\nfrom .validate import Validator\n```\n\n\n\n[Back](ex9_2.md)\n"
  },
  {
    "path": "Exercises/soln9_3.md",
    "content": "# Exercise 9.3 - Solution\n\n## (a) Controlling Exported Symbols\n\n```python\n# structure.py\n\n__all__ = ['Structure']\n...\n```\n\n```python\n# reader.py\n\n__all__ = [ 'read_csv_as_dicts',\n            'read_csv_as_instances' ]\n...\n```\n\n```python\n# tableformat.py\n\n__all__ = ['create_formatter', 'print_table']\n...\n```\n\n## (b) Exporting Everything\n\n```python\n# structly/__init__.py\n\nfrom .structure import *\nfrom .reader import *\nfrom .tableformat import *\n\n__all__ = [ *structure.__all__,\n\t    *reader.__all__,\n\t    *tableformat.__all__ ]\n```\n\n## (c) Module Splitting\n\nThere are a few parts to this.   First, the individual files in `tableformat/formats` are going to\nlook like this:\n\n```python\n# tableformat/formats/text.py\nfrom ..formatter import TableFormatter\n\nclass TextTableFormatter(TableFormatter):\n    ...\n\n\n# tableformat/formats/csv.py\nfrom ..formatter import TableFormatter\nclass CSVTableFormatter(TableFormatter):\n    ...\n\n\n# tableformat/formats/html.py\nfrom ..formatter import TableFormatter\nclass HTMLTableFormatter(TableFormatter):\n    ...\n```\n\nThe `formatter.py` file will look like this:\n\n```python\n# tableformat/formatter.py\n\nfrom abc import ABC, abstractmethod\n\ndef print_table(records, fields, formatter):\n    if not isinstance(formatter, TableFormatter):\n        raise RuntimeError('Expected a TableFormatter')\n\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n\nclass TableFormatter(ABC):\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n\nfrom .formats.text import TextTableFormatter\nfrom .formats.csv import CSVTableFormatter\nfrom .formats.html import HTMLTableFormatter\n\nclass ColumnFormatMixin:\n    formats = []\n    def row(self, rowdata):\n        rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)]\n        super().row(rowdata)\n\nclass UpperHeadersMixin:\n    def headings(self, headers):\n        super().headings([h.upper() for h in headers])\n\ndef create_formatter(name, column_formats=None, upper_headers=False):\n    if name == 'text':\n        formatter_cls = TextTableFormatter\n    elif name == 'csv':\n        formatter_cls = CSVTableFormatter\n    elif name == 'html':\n        formatter_cls = HTMLTableFormatter\n    else:\n        raise RuntimeError('Unknown format %s' % name)\n\n    if column_formats:\n        class formatter_cls(ColumnFormatMixin, formatter_cls):\n              formats = column_formats\n\n    if upper_headers:\n        class formatter_cls(UpperHeadersMixin, formatter_cls):\n            pass\n\n    return formatter_cls()\n```\n\nFinally, you need to have a `tableformat/__init__.py` file like this:\n\n```python\n# tableformat/__init__.py\n\nfrom .formatter import print_table, create_formatter\n\n__all__ = [ 'print_table', 'create_formatter' ]\n```\n\n\n[Back](ex9_3.md)\n"
  },
  {
    "path": "Exercises/soln9_4.md",
    "content": "# Exercise 9.4 - Solution\n\n```python\n# tableformat.py\nfrom abc import ABC, abstractmethod\n\ndef print_table(records, fields, formatter):\n    if not isinstance(formatter, TableFormatter):\n        raise RuntimeError('Expected a TableFormatter')\n\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n\nclass TableFormatter(ABC):\n    _formats = { }\n\n    @classmethod\n    def __init_subclass__(cls):\n        name = cls.__module__.split('.')[-1]\n        TableFormatter._formats[name] = cls\n\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n\nclass ColumnFormatMixin:\n    formats = []\n    def row(self, rowdata):\n        rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)]\n        super().row(rowdata)\n\nclass UpperHeadersMixin:\n    def headings(self, headers):\n        super().headings([h.upper() for h in headers])\n\ndef create_formatter(name, column_formats=None, upper_headers=False):\n    if name not in TableFormatter._formats:\n        __import__(f'{__package__}.formats.{name}')\n        \n    formatter_cls = TableFormatter._formats.get(name)\n    if not formatter_cls:\n        raise RuntimeError('Unknown format %s' % name)\n\n    if column_formats:\n        class formatter_cls(ColumnFormatMixin, formatter_cls):\n              formats = column_formats\n\n    if upper_headers:\n        class formatter_cls(UpperHeadersMixin, formatter_cls):\n            pass\n\n    return formatter_cls()\n```\n\n\n\n[Back](ex9_4.md)\n"
  },
  {
    "path": "LICENSE.md",
    "content": "## creative commons\n\n# Attribution-ShareAlike 4.0 International\n\nCreative 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.\n\n### Using Creative Commons Public Licenses\n\nCreative 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.\n\n* __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).\n\n* __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).\n\n## Creative Commons Attribution-ShareAlike 4.0 International Public License\n\nBy 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.\n\n### Section 1 – Definitions.\n\na. __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.\n\nb. __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.\n\nc. __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.\n\nd. __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.\n\ne. __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.\n\nf. __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.\n\ng. __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.\n\nh. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License.\n\ni. __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.\n\nj. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License.\n\nk. __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.\n\nl. __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.\n\nm. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.\n\n### Section 2 – Scope.\n\na. ___License grant.___\n\n    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:\n\n        A. reproduce and Share the Licensed Material, in whole or in part; and\n\n        B. produce, reproduce, and Share Adapted Material.\n\n    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.\n\n    3. __Term.__ The term of this Public License is specified in Section 6(a).\n\n    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.\n\n    5. __Downstream recipients.__\n\n        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.\n\n        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.\n\n        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.\n\n    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).\n\nb. ___Other rights.___\n\n    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.\n\n    2. Patent and trademark rights are not licensed under this Public License.\n\n    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.\n\n### Section 3 – License Conditions.\n\nYour exercise of the Licensed Rights is expressly made subject to the following conditions.\n\na. ___Attribution.___\n\n    1. If You Share the Licensed Material (including in modified form), You must:\n\n        A. retain the following if it is supplied by the Licensor with the Licensed Material:\n\n            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);\n\n            ii. a copyright notice;\n\n            iii. a notice that refers to this Public License;\n\n            iv. a notice that refers to the disclaimer of warranties;\n\n            v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;\n\n        B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and\n\n        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.\n\n    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.\n\n    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.\n\nb. ___ShareAlike.___\n\nIn addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply.\n\n1. 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.\n\n2. 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.\n\n3. 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.\n\n### Section 4 – Sui Generis Database Rights.\n\nWhere the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:\n\na. 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;\n\nb. 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\n\nc. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.\n\nFor 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.\n\n### Section 5 – Disclaimer of Warranties and Limitation of Liability.\n\na. __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.__\n\nb. __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.__\n\nc. 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.\n\n### Section 6 – Term and Termination.\n\na. 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.\n\nb. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:\n\n    1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or\n\n    2. upon express reinstatement by the Licensor.\n\n    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.\n\nc. 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.\n\nd. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.\n\n### Section 7 – Other Terms and Conditions.\n\na. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.\n\nb. 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.\n\n### Section 8 – Interpretation.\n\na. 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.\n\nb. 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.\n\nc. 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.\n\nd. 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.\n\n```\nCreative 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.\n\nCreative Commons may be contacted at creativecommons.org\n```\n"
  },
  {
    "path": "README.md",
    "content": "# Advanced Python Mastery\n\nA course by David Beazley (https://www.dabeaz.com)  \nCopyright (C) 2007-2024  \n\n## Synopsis\n\nAn exercise-driven course on Advanced Python Programming that was\nbattle-tested several hundred times on the corporate-training circuit\nfor more than a decade.  Written by David Beazley, author of the\n[Python Cookbook, 3rd Edition](https://www.dabeaz.com/cookbook.html) (O'Reilly) and \n[Python Distilled](https://www.dabeaz.com/python-distilled/index.html)\n(Addison-Wesley).  Released under a Creative Commons license.  Free of\nads, tracking, pop-ups, newsletters, and AI.\n\nEverything in this course should work with the latest version of\nPython, but be aware that the course primarily targets the feature set\nof Python 3.6.  As such, certain modern features don't get coverage. \nHonestly, this shouldn't affect you much unless you're trying to write code\nthat's freakishly clever.\n\n## Target Audience \n\nThis course is for Python programmers who want to move beyond \nshort scripts to writing more sophisticated programs.    To do that,\nit helps to better understand the programming techniques used\nin popular libraries and frameworks. Thus, this course is mainly \nfor programmers who want to build a more complete mental model of the\nPython language itself and how it works.  Ultimately, the goal\nis to be able to apply this knowledge to your own projects.\n\n## Prerequisites\n\nYou already know some Python.  This is not a course for beginners.\nFor more introductory material, you might consider the\n[Practical Python Programming](https://dabeaz-course.github.io/practical-python) course.\n\n## How to Take the Course\n\nTo take the course, you should first fork/clone the GitHub repo to your own\nmachine.\n\nIt is assumed that you are working locally in a proper Python\ndevelopment environment.  That means a proper installation of Python,\nan editor/IDE, and whatever other tools that you would normally\ninstall to work on Python.  Due to the use of multiple files and\nmodule imports, the use of Notebooks is not recommended.\n\nThe [`PythonMastery.pdf`](PythonMastery.pdf) file contains detailed\npresentation slides. Course exercises and suggested timings are\nclearly indicated. You'll want to keep this by your side (I recommend\ndownloading and viewing it with a local PDF viewer). Start here! \n\nThe [Exercises/](Exercises/index.md) directory has all of the\ncourse exercises. \n\nThe [Solutions/](Solutions/) directory has fully worked out solution code.\n\nThe [Data/](Data/) directory has some datafiles used during the course.\n\nThe course was originally taught over 4-5 days in an in-person\nclassroom setting with a mix of lecture and hands-on exercises.\nSuccessful completion of the course will likely require 30-50 hours of\nwork.  Exercises tend to build upon each other.  Solutions are always\nprovided in case you get stuck.\n\n## Supplemental Material\n\nThe Advanced Python Mastery course often suggested more in-depth tutorials\non selected topics.  These were presented at the PyCon conference and\nmight be of interest:\n\n* [Generator Tricks for Systems Programmers](https://www.dabeaz.com/generators/)\n* [A Curious Course on Coroutines and Concurrency](http://dabeaz.com/coroutines/index.html)\n* [Python3 Metaprogramming](https://dabeaz.com/py3meta/index.html)\n* [Generators: The Final Frontier](https://dabeaz.com/finalgenerator/index.html)\n* [Modules and Packages: Live and Let Die](https://dabeaz.com/modulepackage/index.html)\n\n## Questions and Answers\n\n**Q: Are any videos available?**\n\n**A:** No. You will be able to more quickly read the presentation slides which contain\ntechnical information.  However, the [Python Programming Language: LiveLessons](https://www.safaribooksonline.com/library/view/python-programming-language/9780134217314/) video\navailable on O'Reilly's Safari site is closely related to the material in this course.\n\n**Q: Can I use these materials in my own course?**\n\n**A:** Yes. I just kindly ask that you give proper attribution.\n\n**Q: Do you accept bug reports or pull requests?**\n\n**A:** If you've found a bug, please report it!  However, I'm not\nlooking to expand or reorganize the course content with new topics or\nexercises.\n\n**Q: Are the presentation slides available in any format other than PDF?**\n\n**A:** No.\n\n**Q: Is there any forum/chat where the course can be discussed?**\n\n**A:** You can use [GitHub discussions](https://github.com/dabeaz-course/python-mastery/discussions) to discuss the course.\n\n**Q: Why wasn't topic/tool/library X covered?**\n\n**A:** The course was designed to be completed in an intense 4-day\nin-person format. It simply isn't possible to cover absolutely\neverything.  As such, the course is focused primarily on the core\nPython language, not third party libraries or tooling.\n\n**Q: Why aren't features like typing, async, or pattern matching covered?**\n\n**A:** Mainly, it's an issue of calendar timing and scope.  Course\nmaterial was primarily developed pre-pandemic and represents Python as\nit was at that time. Some topics (e.g., typing or async) are\nsufficiently complex that they would be better covered on their own\nin a separate course.\n\n**Q: Do you have plans to modernize the course?**\n\n**A:** It is my intention that everything in the course apply to the\nlatest version of Python.  Unless Python makes backwards-incompatible\nchanges to the core language, that should hold.  Although the course\ndoesn't cover every new features, I won't rule out future changes.  A\nlot depends on my available time and interest however.  So, I make no\npromises.\n\n**Q: Why did you release the course?**\n\n**A:** This course was extensively taught pre-pandemic. Post-pandemic,\nmy teaching has shifted towards projects and CS fundamentals.\nHowever, why let a good course just languish on my computer? \n\n**Q: How can I help?**\n\n**A:** If you like the course, the best way to support it is to tell\nother people about it.\n\n----\n`>>>` Advanced Python Mastery  \n`...` A course by [dabeaz](https://www.dabeaz.com)  \n`...` Copyright 2007-2023  \n\n![](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/)\n\n\n\n\n\n\n"
  },
  {
    "path": "Solutions/1_1/art.py",
    "content": "# art.py\n\nimport sys\nimport random\n\nchars = '\\|/'\n\ndef draw(rows, columns):\n    for r in range(rows):\n        print(''.join(random.choice(chars) for _ in range(columns)))\n\nif __name__ == '__main__':\n    if len(sys.argv) != 3:\n        raise SystemExit(\"Usage: art.py rows columns\")\n    draw(int(sys.argv[1]), int(sys.argv[2]))\n"
  },
  {
    "path": "Solutions/1_3/pcost.py",
    "content": "# pcost.py\n\ntotal_cost = 0.0\n\nwith open('../../Data/portfolio.dat', 'r') as f:\n    for line in f:\n        fields = line.split()\n        nshares = int(fields[1])\n        price = float(fields[2])\n        total_cost = total_cost + nshares * price\n\nprint(total_cost)\n"
  },
  {
    "path": "Solutions/1_4/pcost.py",
    "content": "# pcost.py\n\ndef portfolio_cost(filename):\n    total_cost = 0.0\n    with open(filename) as f:\n        for line in f:\n            fields = line.split()\n            try:\n                nshares    = int(fields[1])\n                price      = float(fields[2])\n                total_cost = total_cost + nshares*price\n\n            # This catches errors in int() and float() conversions above\n            except ValueError as e:\n                print(\"Couldn't parse:\", repr(line))\n                print(\"Reason:\", e)\n\n    return total_cost\n\nprint(portfolio_cost('../../Data/portfolio3.dat'))\n"
  },
  {
    "path": "Solutions/1_5/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n    def cost(self):\n        return self.shares * self.price\n"
  },
  {
    "path": "Solutions/1_6/pcost.py",
    "content": "# pcost.py\n\ndef portfolio_cost(filename):\n    total_cost = 0.0\n    with open(filename) as f:\n        for line in f:\n            fields = line.split()\n            try:\n                nshares = int(fields[1])\n                price = float(fields[2])\n                total_cost = total_cost + nshares * price\n\n            # This catches errors in int() and float() conversions above\n            except ValueError as e:\n                print(\"Couldn't parse:\", repr(line))\n                print(\"Reason:\", e)\n\n    return total_cost\n\n\nif __name__ == '__main__':\n    print(portfolio_cost('../../Data/portfolio.dat'))\n"
  },
  {
    "path": "Solutions/2_1/readrides.py",
    "content": "# readrides.py\n\nimport csv\n\ndef read_rides_as_tuples(filename):\n    '''\n    Read the bus ride data as a list of tuples\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = (route, date, daytype, rides)\n            records.append(record)\n    return records\n\ndef read_rides_as_dicts(filename):\n    '''\n    Read the bus ride data as a list of dicts\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = {\n                'route': route, \n                'date': date, \n                'daytype': daytype, \n                'rides' : rides\n                }\n            records.append(record)\n    return records\n\nclass Row:\n    # Uncomment to see effect of slots\n    # __slots__ = ('route', 'date', 'daytype', 'rides')\n    def __init__(self, route, date, daytype, rides):\n        self.route = route\n        self.date = date\n        self.daytype = daytype\n        self.rides = rides\n\n# Uncomment to use a namedtuple instead\n#from collections import namedtuple\n#Row = namedtuple('Row',('route','date','daytype','rides'))\n\ndef read_rides_as_instances(filename):\n    '''\n    Read the bus ride data as a list of instances\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = Row(route, date, daytype, rides)\n            records.append(record)\n    return records\n\nif __name__ == '__main__':\n    import tracemalloc\n    tracemalloc.start()\n    read_rides = read_rides_as_tuples # Change to as_dicts, as_instances, etc.\n    rides = read_rides(\"../../Data/ctabus.csv\")\n\n    print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory())\n"
  },
  {
    "path": "Solutions/2_2/cta.py",
    "content": "# cta.py\n\nfrom collections import defaultdict, Counter\nimport tracemalloc\nimport readrides\n\ntracemalloc.start()\n\nrows = readrides.read_rides_as_dicts('../../Data/ctabus.csv')\n\n# --------------------------------------------------\n# Question 1:  How many bus routes are in Chicago?\n# Solution: Use a set to get unique values. \n\nroutes = set()\nfor row in rows:\n    routes.add(row['route'])\nprint(len(routes), 'routes')\n\n# --------------------------------------------------\n# Question 2: How many people rode route 22 on February 2, 2011?\n# Solution: Make dictionary with composite keys\n\nby_route_date = { }\nfor row in rows:\n    by_route_date[row['route'], row['date']] = row['rides']\n\nprint('Rides on Route 22, February 2, 2011:', by_route_date['22','02/02/2011'])\n\n# --------------------------------------------------\n# Question 3: Total number of rides per route\n# Solution: Use a counter to tabulate things\nrides_per_route = Counter()\nfor row in rows:\n    rides_per_route[row['route']] += row['rides']\n\n# Make a table showing the routes and a count ranked by popularity\nfor route, count in rides_per_route.most_common():\n    print('%5s %10d' % (route, count))\n\n# --------------------------------------------------\n# Question 4: Routes with greatest increase in ridership 2001 - 2011\n# Solution: Counters embedded inside a defaultdict\n\nrides_by_year = defaultdict(Counter)\nfor row in rows:\n    year = row['date'].split('/')[2]\n    rides_by_year[year][row['route']] += row['rides']\n\ndiffs = rides_by_year['2011'] - rides_by_year['2001']\nfor route, diff in diffs.most_common(5):\n    print(route, diff)\n\n# ---- Memory use\nprint('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory())\n"
  },
  {
    "path": "Solutions/2_2/readport.py",
    "content": "# readport.py\n\nimport csv\n\n# A function that reads a file into a list of dicts\ndef read_portfolio(filename):\n    portfolio = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            record = {\n                'name' : row[0],\n                'shares' : int(row[1]),\n                'price' : float(row[2])\n                }\n            portfolio.append(record)\n    return portfolio\n"
  },
  {
    "path": "Solutions/2_2/readrides.py",
    "content": "# readrides.py\n\nimport csv\n\ndef read_rides_as_tuples(filename):\n    '''\n    Read the bus ride data as a list of tuples\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = (route, date, daytype, rides)\n            records.append(record)\n    return records\n\ndef read_rides_as_dicts(filename):\n    '''\n    Read the bus ride data as a list of dicts\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = {\n                'route': route, \n                'date': date, \n                'daytype': daytype, \n                'rides' : rides\n                }\n            records.append(record)\n    return records\n\nclass Row:\n    # Uncomment to see effect of slots\n    # __slots__ = ('route', 'date', 'daytype', 'rides')\n    def __init__(self, route, date, daytype, rides):\n        self.route = route\n        self.date = date\n        self.daytype = daytype\n        self.rides = rides\n\n# Uncomment to use a namedtuple instead\n#from collections import namedtuple\n#Row = namedtuple('Row',('route','date','daytype','rides'))\n\ndef read_rides_as_instances(filename):\n    '''\n    Read the bus ride data as a list of instances\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = Row(route, date, daytype, rides)\n            records.append(record)\n    return records\n\nif __name__ == '__main__':\n    import tracemalloc\n    tracemalloc.start()\n    read_rides = read_rides_as_tuples # Change to as_dicts, as_instances, etc.\n    rides = read_rides(\"../../Data/ctabus.csv\")\n\n    print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory())\n"
  },
  {
    "path": "Solutions/2_4/mutint.py",
    "content": "# mutint.py\n#\n# Mutable integers\n\nfrom functools import total_ordering\n\n@total_ordering\nclass MutInt:\n    __slots__ = ['value']\n\n    def __init__(self, value):\n        self.value = value\n\n    def __str__(self):\n        return str(self.value)\n    \n    def __repr__(self):\n        return f'MutInt({self.value!r})'\n\n    def __format__(self, fmt):\n        return format(self.value, fmt)\n\n    # Implement the \"+\" operator. Forward operands (MutInt + other)\n    def __add__(self, other):\n        if isinstance(other, MutInt):\n            return MutInt(self.value + other.value)\n        elif isinstance(other, int):\n            return MutInt(self.value + other)\n        else:\n            return NotImplemented\n\n    # Support for reversed operands (other + MutInt)\n    __radd__ = __add__\n\n    # Support for in-place update (MutInt += other)\n    def __iadd__(self, other):\n        if isinstance(other, MutInt):\n            self.value += other.value\n            return self\n        elif isinstance(other, int):\n            self.value += other\n            return self\n        else:\n            return NotImplemented\n\n    # Support for equality testing\n    def __eq__(self, other):\n        if isinstance(other, MutInt):\n            return self.value == other.value\n        elif isinstance(other, int):\n            return self.value == other\n        else:\n            return NotImplemented\n\n    # One relation is needed for @total_ordering decorator. It fills in others\n    def __lt__(self, other):\n        if isinstance(other, MutInt):\n            return self.value < other.value\n        elif isinstance(other, int):\n            return self.value < other\n        else:\n            return NotImplemented\n\n    # Conversions to int() and float()\n    def __int__(self):\n        return int(self.value)\n\n    def __float__(self):\n        return float(self.value)\n\n    # Support for indexing s[MutInt]\n    __index__ = __int__\n\n    \n"
  },
  {
    "path": "Solutions/2_5/cta.py",
    "content": "# cta.py\n\nfrom collections import defaultdict, Counter\nimport tracemalloc\nimport readrides\n\ntracemalloc.start()\n\nrows = readrides.read_rides_as_dicts('../../Data/ctabus.csv')\n\n# --------------------------------------------------\n# Question 1:  How many bus routes are in Chicago?\n# Solution: Use a set to get unique values. \n\nroutes = set()\nfor row in rows:\n    routes.add(row['route'])\nprint(len(routes), 'routes')\n\n# --------------------------------------------------\n# Question 2: How many people rode route 22 on February 2, 2011?\n# Solution: Make dictionary with composite keys\n\nby_route_date = { }\nfor row in rows:\n    by_route_date[row['route'], row['date']] = row['rides']\n\nprint('Rides on Route 22, February 2, 2011:', by_route_date['22','02/02/2011'])\n\n# --------------------------------------------------\n# Question 3: Total number of rides per route\n# Solution: Use a counter to tabulate things\nrides_per_route = Counter()\nfor row in rows:\n    rides_per_route[row['route']] += row['rides']\n\n# Make a table showing the routes and a count ranked by popularity\nfor route, count in rides_per_route.most_common():\n    print('%5s %10d' % (route, count))\n\n# --------------------------------------------------\n# Question 4: Routes with greatest increase in ridership 2001 - 2011\n# Solution: Counters embedded inside a defaultdict\n\nrides_by_year = defaultdict(Counter)\nfor row in rows:\n    year = row['date'].split('/')[2]\n    rides_by_year[year][row['route']] += row['rides']\n\ndiffs = rides_by_year['2011'] - rides_by_year['2001']\nfor route, diff in diffs.most_common(5):\n    print(route, diff)\n\n# ---- Memory use\nprint('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory())\n"
  },
  {
    "path": "Solutions/2_5/readrides.py",
    "content": "# readrides.py\n\nimport csv\nimport collections.abc\n\ndef read_rides_as_tuples(filename):\n    '''\n    Read the bus ride data as a list of tuples\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = (route, date, daytype, rides)\n            records.append(record)\n    return records\n\ndef read_rides_as_dicts(filename):\n    '''\n    Read the bus ride data as a list of dicts\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = {\n                'route': route, \n                'date': date, \n                'daytype': daytype, \n                'rides' : rides\n                }\n            records.append(record)\n    return records\n\nclass Row:\n    __slots__ = ('route', 'date', 'daytype', 'rides')\n    def __init__(self, route, date, daytype, rides):\n        self.route = route\n        self.date = date\n        self.daytype = daytype\n        self.rides = rides\n\ndef read_rides_as_instances(filename):\n    '''\n    Read the bus ride data as a list of instances\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = Row(route, date, daytype, rides)\n            records.append(record)\n    return records\n\n# Read as columns\ndef read_rides_as_columns(filename):\n    '''\n    Read the bus ride data into 4 lists, representing columns\n    '''\n    routes = []\n    dates = []\n    daytypes = []\n    numrides = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            routes.append(row[0])\n            dates.append(row[1])\n            daytypes.append(row[2])\n            numrides.append(int(row[3]))\n    return dict(routes=routes, dates=dates, daytypes=daytypes, numrides=numrides)\n\n# The great \"fake\"\n\nimport collections\nclass RideData(collections.abc.Sequence):\n    def __init__(self):\n        # Each value is a list with all of the values (a column)\n        self.routes = []\n        self.dates = []\n        self.daytypes = []\n        self.numrides = []\n        \n    def __len__(self):\n        # All lists assumed to have the same length\n        return len(self.routes)\n\n    def append(self, d):\n        self.routes.append(d['route'])\n        self.dates.append(d['date'])\n        self.daytypes.append(d['daytype'])\n        self.numrides.append(d['rides'])\n        \n    def __getitem__(self, index):\n        return { 'route': self.routes[index],\n                 'date': self.dates[index],\n                 'daytype': self.daytypes[index],\n                 'rides': self.numrides[index] }\n\n# Modified version using RideData\ndef read_rides_as_dicts(filename):\n    '''\n    Read the bus ride data as a list of dicts\n    '''\n    records = RideData()\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headings = next(rows)     # Skip headers\n        for row in rows:\n            route = row[0]\n            date = row[1]\n            daytype = row[2]\n            rides = int(row[3])\n            record = {\n                'route': route, \n                'date': date, \n                'daytype': daytype, \n                'rides' : rides\n                }\n            records.append(record)\n    return records\n\nif __name__ == '__main__':\n    import tracemalloc\n    tracemalloc.start()\n    read_rides = read_rides_as_dicts # Change to as_dicts, as_instances, etc.\n    rides = read_rides(\"../../Data/ctabus.csv\")\n\n    print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory())\n"
  },
  {
    "path": "Solutions/2_6/colreader.py",
    "content": "# colreader.py\n\nimport collections\nimport collections.abc\nimport csv\n\nclass DataCollection(collections.abc.Sequence):\n    def __init__(self, columns):\n        self.column_names = list(columns)\n        self.column_data = list(columns.values())\n\n    def __len__(self):\n        return len(self.column_data[0])\n\n    def __getitem__(self, index):\n        return dict(zip(self.column_names,\n                        (col[index] for col in self.column_data)))\n\n\ndef read_csv_as_columns(filename, types):\n    columns = collections.defaultdict(list)\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            for name, func, val in zip(headers, types, row):\n                columns[name].append(func(val))\n            \n    return DataCollection(columns)\n\nif __name__ == '__main__':\n    import tracemalloc\n    from sys import intern\n\n    tracemalloc.start()\n    data = read_csv_as_columns('../../Data/ctabus.csv', [intern, intern, intern, int])\n    print(tracemalloc.get_traced_memory())\n"
  },
  {
    "path": "Solutions/2_6/cta.py",
    "content": "# cta.py\n\nfrom collections import defaultdict, Counter\nimport tracemalloc\nimport csv\nimport sys\n\ntracemalloc.start()\n\nif True:\n    # Part (b)\n    import reader\n    rows = reader.read_csv_as_dicts('../../Data/ctabus.csv',\n                                    [sys.intern, sys.intern, sys.intern, int])\nelse:\n    # Part (d) - Challenge\n    import colreader\n    rows = colreader.read_csv_as_columns('../../Data/ctabus.csv', \n                                         [sys.intern, sys.intern, sys.intern, int])\n\n# --------------------------------------------------\n# Question 1:  How many bus routes are in Chicago?\n# Solution: Use a set to get unique values. \n\nroutes = set()\nfor row in rows:\n    routes.add(row['route'])\nprint(len(routes), 'routes')\n\n# --------------------------------------------------\n# Question 2: Total number of rides per route\n# Solution: Use a counter to tabulate things\nrides_per_route = Counter()\nfor row in rows:\n    rides_per_route[row['route']] += row['rides']\n\n# Make a table showing the routes and a count ranked by popularity\nfor route, count in rides_per_route.most_common():\n    print('%5s %10d' % (route, count))\n\n# --------------------------------------------------\n# Question 3: Routes with greatest increase in ridership 2001 - 2011\n# Solution: Counters embedded inside a defaultdict\n\nrides_by_year = defaultdict(Counter)\nfor row in rows:\n    year = row['date'].split('/')[2]\n    rides_by_year[year][row['route']] += row['rides']\n\ndiffs = rides_by_year['2011'] - rides_by_year['2001']\nfor route, diff in diffs.most_common(5):\n    print(route, diff)\n\n# ---- Memory use\nprint('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory())\n"
  },
  {
    "path": "Solutions/2_6/reader.py",
    "content": "# reader.py\n\nimport csv\n\ndef read_csv_as_dicts(filename, types):\n    '''\n    Read a CSV file into a list of dicts with column type conversion\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            record = { name: func(val) for name, func, val in zip(headers, types, row) }\n            records.append(record)\n    return records\n\n        \n\n    \n\n\n"
  },
  {
    "path": "Solutions/3_1/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\ndef read_portfolio(filename):\n    '''\n    Read a CSV file of stock data into a list of Stocks\n    '''\n    import csv\n    portfolio = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            record = Stock(row[0], int(row[1]), float(row[2]))\n            portfolio.append(record)\n    return portfolio\n\ndef print_portfolio(portfolio):\n    '''\n    Make a nicely formatted table showing stock data\n    '''\n    print('%10s %10s %10s' % ('name', 'shares', 'price'))\n    print(('-'*10 + ' ')*3)\n    for s in portfolio:\n        print('%10s %10d %10.2f' % (s.name, s.shares, s.price))\n\nif __name__ == '__main__':\n    portfolio = read_portfolio('../../Data/portfolio.csv')\n    print_portfolio(portfolio)\n\n"
  },
  {
    "path": "Solutions/3_2/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\n\ndef read_portfolio(filename):\n    '''\n    Read a CSV file of stock data into a list of Stocks\n    '''\n    import csv\n    portfolio = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            record = Stock(row[0], int(row[1]), float(row[2]))\n            portfolio.append(record)\n    return portfolio\n\nif __name__ == '__main__':\n    import tableformat\n    portfolio = read_portfolio('../../Data/portfolio.csv')\n    tableformat.print_table(portfolio, ['name','shares','price'])\n"
  },
  {
    "path": "Solutions/3_2/tableformat.py",
    "content": "# tableformat.py\n\n# Print a table\ndef print_table(records, fields):\n    print(' '.join('%10s' % fieldname for fieldname in fields))\n    print(('-'*10 + ' ')*len(fields))\n    for record in records:\n        print(' '.join('%10s' % getattr(record, fieldname) for fieldname in fields))\n"
  },
  {
    "path": "Solutions/3_3/reader.py",
    "content": "# reader.py\n\nimport csv\n\ndef read_csv_as_dicts(filename, types):\n    '''\n    Read a CSV file into a list of dicts with column type conversion\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            record = { name: func(val) for name, func, val in zip(headers, types, row) }\n            records.append(record)\n    return records\n\ndef read_csv_as_instances(filename, cls):\n    '''\n    Read a CSV file into a list of instances\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            records.append(cls.from_row(row))\n    return records\n"
  },
  {
    "path": "Solutions/3_3/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    types = (str, int, float)\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls.types, row)]\n        return cls(*values)\n\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\ndef read_portfolio(filename):\n    '''\n    Read a CSV file of stock data into a list of Stocks\n    '''\n    import csv\n    portfolio = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            record = Stock.from_row(row)\n            portfolio.append(record)\n    return portfolio\n\nif __name__ == '__main__':\n    import tableformat\n    import reader\n    # portfolio = read_portfolio('../../Data/portfolio.csv')\n    portfolio = reader.read_csv_as_instances('../../Data/portfolio.csv', Stock)\n    tableformat.print_table(portfolio, ['name', 'shares', 'price'])\n"
  },
  {
    "path": "Solutions/3_3/tableformat.py",
    "content": "# tableformat.py\n\n# Print a table\ndef print_table(records, fields):\n    # Print the table headers in a 10-character wide field\n    for fieldname in fields:\n        print('%10s' % fieldname, end=' ')\n    print()\n\n    # Print the separator bars\n    print(('-'*10 + ' ')*len(fields))\n\n    # Output the table contents\n    for r in records:\n        for fieldname in fields:\n            print('%10s' % getattr(r, fieldname), end=' ')\n        print()\n"
  },
  {
    "path": "Solutions/3_4/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    __slots__ = ('name','_shares','_price')\n    _types = (str, int, float)\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls._types, row)]\n        return cls(*values)\n\n    @property\n    def shares(self):\n        return self._shares\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value, self._types[1]):\n            raise TypeError(f'Expected {self._types[1].__name__}')\n        if value < 0:\n            raise ValueError('shares must be >= 0')\n        self._shares = value\n\n    @property\n    def price(self):\n        return self._price\n    @price.setter\n    def price(self, value):\n        if not isinstance(value, self._types[2]):\n            raise TypeError(f'Expected {self._types[2].__name__}')\n        if value < 0:\n            raise ValueError('price must be >= 0')\n        self._price = value\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/3_5/reader.py",
    "content": "# reader.py\n\nimport csv\n\ndef read_csv_as_dicts(filename, types):\n    '''\n    Read a CSV file into a list of dicts with column type conversion\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            record = { name: func(val) for name, func, val in zip(headers, types, row) }\n            records.append(record)\n    return records\n\ndef read_csv_as_instances(filename, cls):\n    '''\n    Read a CSV file into a list of instances\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            records.append(cls.from_row(row))\n    return records\n"
  },
  {
    "path": "Solutions/3_5/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    __slots__ = ('name','_shares','_price')\n    _types = (str, int, float)\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls._types, row)]\n        return cls(*values)\n\n    @property\n    def shares(self):\n        return self._shares\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value, self._types[1]):\n            raise TypeError(f'Expected {self._types[1].__name__}')\n        if value < 0:\n            raise ValueError('shares must be >= 0')\n        self._shares = value\n\n    @property\n    def price(self):\n        return self._price\n    @price.setter\n    def price(self, value):\n        if not isinstance(value, self._types[2]):\n            raise TypeError(f'Expected {self._types[2].__name__}')\n        if value < 0:\n            raise ValueError('price must be >= 0')\n        self._price = value\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\n# Sample\nif __name__ == '__main__':\n    import reader\n    from tableformat import create_formatter, print_table\n\n    portfolio = reader.read_csv_as_instances('../../Data/portfolio.csv', Stock)\n    formatter = create_formatter('text')\n    print_table(portfolio,['name','shares','price'], formatter)\n"
  },
  {
    "path": "Solutions/3_5/tableformat.py",
    "content": "# tableformat.py\n\ndef print_table(records, fields, formatter):\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n\nclass TableFormatter:\n    def headings(self, headers):\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(' '.join('%10s' % h for h in headers))\n        print(('-'*10 + ' ')*len(headers))\n    \n    def row(self, rowdata):\n        print(' '.join('%10s' % d for d in rowdata))\n\nclass CSVTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(str(d) for d in rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print('<tr>', end=' ')\n        for h in headers:\n            print('<th>%s</th>' % h, end=' ')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end=' ')\n        for d in rowdata:\n            print('<td>%s</td>' % d, end=' ')\n        print('</tr>')\n\ndef create_formatter(name):\n    if name == 'text':\n        formatter_cls = TextTableFormatter\n    elif name == 'csv':\n        formatter_cls = CSVTableFormatter\n    elif name == 'html':\n        formatter_cls = HTMLTableFormatter\n    else:\n        raise RuntimeError('Unknown format %s' % name)\n    return formatter_cls()\n\n\n\n\n"
  },
  {
    "path": "Solutions/3_6/reader.py",
    "content": "# reader.py\n\nimport csv\n\ndef read_csv_as_dicts(filename, types):\n    '''\n    Read a CSV file into a list of dicts with column type conversion\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            record = { name: func(val) for name, func, val in zip(headers, types, row) }\n            records.append(record)\n    return records\n\ndef read_csv_as_instances(filename, cls):\n    '''\n    Read a CSV file into a list of instances\n    '''\n    records = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            records.append(cls.from_row(row))\n    return records\n"
  },
  {
    "path": "Solutions/3_6/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    __slots__ = ('name','_shares','_price')\n    _types = (str, int, float)\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    def __repr__(self):\n        # Note: The !r format code produces the repr() string\n        return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    def __eq__(self, other):\n        return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==\n                (other.name, other.shares, other.price))\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls._types, row)]\n        return cls(*values)\n\n    @property\n    def shares(self):\n        return self._shares\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value, self._types[1]):\n            raise TypeError(f'Expected {self._types[1].__name__}')\n        if value < 0:\n            raise ValueError('shares must be >= 0')\n        self._shares = value\n\n    @property\n    def price(self):\n        return self._price\n    @price.setter\n    def price(self, value):\n        if not isinstance(value, self._types[2]):\n            raise TypeError(f'Expected {self._types[2].__name__}')\n        if value < 0:\n            raise ValueError('price must be >= 0')\n        self._price = value\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\n# Sample\nif __name__ == '__main__':\n    import reader\n    from tableformat import create_formatter, print_table\n\n    portfolio = reader.read_csv_as_instances('../../Data/portfolio.csv', Stock)\n    formatter = create_formatter('text')\n    print_table(portfolio,['name','shares','price'], formatter)\n"
  },
  {
    "path": "Solutions/3_6/tableformat.py",
    "content": "# tableformat.py\n\ndef print_table(records, fields, formatter):\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n\nclass TableFormatter:\n    def headings(self, headers):\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(' '.join('%10s' % h for h in headers))\n        print(('-'*10 + ' ')*len(headers))\n    \n    def row(self, rowdata):\n        print(' '.join('%10s' % d for d in rowdata))\n\nclass CSVTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(str(d) for d in rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print('<tr>', end=' ')\n        for h in headers:\n            print('<th>%s</th>' % h, end=' ')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end=' ')\n        for d in rowdata:\n            print('<td>%s</td>' % d, end=' ')\n        print('</tr>')\n\ndef create_formatter(name):\n    if name == 'text':\n        formatter_cls = TextTableFormatter\n    elif name == 'csv':\n        formatter_cls = CSVTableFormatter\n    elif name == 'html':\n        formatter_cls = HTMLTableFormatter\n    else:\n        raise RuntimeError('Unknown format %s' % name)\n    return formatter_cls()\n\n\n\n\n"
  },
  {
    "path": "Solutions/3_7/reader.py",
    "content": "# reader.py\n\nimport csv\nfrom abc import ABC, abstractmethod\n\nclass CSVParser(ABC):\n\n    def parse(self, filename):\n        records = []\n        with open(filename) as f:\n            rows = csv.reader(f)\n            headers = next(rows)\n            for row in rows:\n                record = self.make_record(headers, row)\n                records.append(record)\n        return records\n\n    @abstractmethod\n    def make_record(self, headers, row):\n        pass\n\nclass DictCSVParser(CSVParser):\n    def __init__(self, types):\n        self.types = types\n\n    def make_record(self, headers, row):\n        return { name: func(val) for name, func, val in zip(headers, self.types, row) }\n\nclass InstanceCSVParser(CSVParser):\n    def __init__(self, cls):\n        self.cls = cls\n\n    def make_record(self, headers, row):\n        return self.cls.from_row(row)\n\ndef read_csv_as_dicts(filename, types):\n    parser = DictCSVParser(types)\n    return parser.parse(filename)\n\ndef read_csv_as_instances(filename, cls):\n    parser = InstanceCSVParser(cls)\n    return parser.parse(filename)\n"
  },
  {
    "path": "Solutions/3_7/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    __slots__ = ('name','_shares','_price')\n    _types = (str, int, float)\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    def __repr__(self):\n        # Note: The !r format code produces the repr() string\n        return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    def __eq__(self, other):\n        return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==\n                (other.name, other.shares, other.price))\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls._types, row)]\n        return cls(*values)\n\n    @property\n    def shares(self):\n        return self._shares\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value, self._types[1]):\n            raise TypeError(f'Expected {self._types[1].__name__}')\n        if value < 0:\n            raise ValueError('shares must be >= 0')\n        self._shares = value\n\n    @property\n    def price(self):\n        return self._price\n    @price.setter\n    def price(self, value):\n        if not isinstance(value, self._types[2]):\n            raise TypeError(f'Expected {self._types[2].__name__}')\n        if value < 0:\n            raise ValueError('price must be >= 0')\n        self._price = value\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\n# Sample\nif __name__ == '__main__':\n    import reader\n    from tableformat import create_formatter, print_table\n\n    portfolio = reader.read_csv_as_instances('../../Data/portfolio.csv', Stock)\n    formatter = create_formatter('text')\n    print_table(portfolio,['name','shares','price'], formatter)\n"
  },
  {
    "path": "Solutions/3_7/tableformat.py",
    "content": "# tableformat.py\nfrom abc import ABC, abstractmethod\n\ndef print_table(records, fields, formatter):\n    if not isinstance(formatter, TableFormatter):\n        raise TypeError('Expected a TableFormatter')\n\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n\nclass TableFormatter(ABC):\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n\nclass TextTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(' '.join('%10s' % h for h in headers))\n        print(('-'*10 + ' ')*len(headers))\n    \n    def row(self, rowdata):\n        print(' '.join('%10s' % d for d in rowdata))\n\nclass CSVTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(str(d) for d in rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print('<tr>', end=' ')\n        for h in headers:\n            print('<th>%s</th>' % h, end=' ')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end=' ')\n        for d in rowdata:\n            print('<td>%s</td>' % d, end=' ')\n        print('</tr>')\n\ndef create_formatter(name):\n    if name == 'text':\n        formatter = TextTableFormatter\n    elif name == 'csv':\n        formatter = CSVTableFormatter\n    elif name == 'html':\n        formatter = HTMLTableFormatter\n    else:\n        raise RuntimeError('Unknown format %s' % name)\n    return formatter()\n\n\n\n"
  },
  {
    "path": "Solutions/3_8/reader.py",
    "content": "# reader.py\n\nimport csv\nfrom abc import ABC, abstractmethod\n\nclass CSVParser(ABC):\n\n    def parse(self, filename):\n        records = []\n        with open(filename) as f:\n            rows = csv.reader(f)\n            headers = next(rows)\n            for row in rows:\n                record = self.make_record(headers, row)\n                records.append(record)\n        return records\n\n    @abstractmethod\n    def make_record(self, headers, row):\n        pass\n\nclass DictCSVParser(CSVParser):\n    def __init__(self, types):\n        self.types = types\n\n    def make_record(self, headers, row):\n        return { name: func(val) for name, func, val in zip(headers, self.types, row) }\n\nclass InstanceCSVParser(CSVParser):\n    def __init__(self, cls):\n        self.cls = cls\n\n    def make_record(self, headers, row):\n        return self.cls.from_row(row)\n\ndef read_csv_as_dicts(filename, types):\n    parser = DictCSVParser(types)\n    return parser.parse(filename)\n\ndef read_csv_as_instances(filename, cls):\n    parser = InstanceCSVParser(cls)\n    return parser.parse(filename)\n"
  },
  {
    "path": "Solutions/3_8/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    __slots__ = ('name','_shares','_price')\n    _types = (str, int, float)\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    def __repr__(self):\n        # Note: The !r format code produces the repr() string\n        return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    def __eq__(self, other):\n        return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==\n                (other.name, other.shares, other.price))\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls._types, row)]\n        return cls(*values)\n\n    @property\n    def shares(self):\n        return self._shares\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value, self._types[1]):\n            raise TypeError(f'Expected {self._types[1].__name__}')\n        if value < 0:\n            raise ValueError('shares must be >= 0')\n        self._shares = value\n\n    @property\n    def price(self):\n        return self._price\n    @price.setter\n    def price(self, value):\n        if not isinstance(value, self._types[2]):\n            raise TypeError(f'Expected {self._types[2].__name__}')\n        if value < 0:\n            raise ValueError('price must be >= 0')\n        self._price = value\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\n# Sample\nif __name__ == '__main__':\n    import tableformat\n    import reader\n    from tableformat import (\n        print_table,\n        create_formatter,\n        TextTableFormatter,\n        ColumnFormatMixin,\n        UpperHeadersMixin\n        )\n\n    portfolio = reader.read_csv_as_instances('../../Data/portfolio.csv', Stock)\n\n    class PortfolioFormatter(ColumnFormatMixin, TextTableFormatter):\n        formats = ['%s','%d','%0.2f']\n\n    formatter = PortfolioFormatter()\n    print_table(portfolio,['name','shares','price'], formatter)\n\n    class PortfolioFormatter(UpperHeadersMixin, TextTableFormatter):\n        pass\n\n    formatter = PortfolioFormatter()\n    print_table(portfolio, ['name','shares','price'], formatter)\n\n    # Factory function version\n    formatter = create_formatter('text', column_formats=['%s','%d','%0.2f'])\n    print_table(portfolio, ['name','shares','price'], formatter)\n\n    formatter = create_formatter('text', upper_headers=True)\n    print_table(portfolio, ['name','shares','price'], formatter)\n"
  },
  {
    "path": "Solutions/3_8/tableformat.py",
    "content": "# tableformat.py\nfrom abc import ABC, abstractmethod\n\ndef print_table(records, fields, formatter):\n    if not isinstance(formatter, TableFormatter):\n        raise TypeError('Expected a TableFormatter')\n\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n\nclass TableFormatter(ABC):\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n\n\nclass TextTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(' '.join('%10s' % h for h in headers))\n        print(('-'*10 + ' ')*len(headers))\n    \n    def row(self, rowdata):\n        print(' '.join('%10s' % d for d in rowdata))\n\nclass CSVTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(str(d) for d in rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print('<tr>', end=' ')\n        for h in headers:\n            print('<th>%s</th>' % h, end=' ')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end=' ')\n        for d in rowdata:\n            print('<td>%s</td>' % d, end=' ')\n        print('</tr>')\n\nclass ColumnFormatMixin:\n    formats = []\n    def row(self, rowdata):\n        rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)]\n        super().row(rowdata)\n\nclass UpperHeadersMixin:\n    def headings(self, headers):\n        super().headings([h.upper() for h in headers])\n\ndef create_formatter(name, column_formats=None, upper_headers=False):\n    if name == 'text':\n        formatter_cls = TextTableFormatter\n    elif name == 'csv':\n        formatter_cls = CSVTableFormatter\n    elif name == 'html':\n        formatter_cls = HTMLTableFormatter\n    else:\n        raise RuntimeError('Unknown format %s' % name)\n\n    if column_formats:\n        class formatter_cls(ColumnFormatMixin, formatter_cls):\n              formats = column_formats\n\n    if upper_headers:\n        class formatter_cls(UpperHeadersMixin, formatter_cls):\n            pass\n\n    return formatter_cls()\n\n\n\n\n"
  },
  {
    "path": "Solutions/4_2/validate.py",
    "content": "class Validator:\n    @classmethod\n    def check(cls, value):\n        return value\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'Expected {cls.expected_type}')\n        return super().check(value)\n\nclass Integer(Typed):\n    expected_type = int\n\nclass Float(Typed):\n    expected_type = float\n\nclass String(Typed):\n    expected_type = str\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('Must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('Must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\n# Examples\nif __name__ == '__main__':\n    def add(x, y):\n        Integer.check(x)\n        Integer.check(y)\n        return x + y\n\n    class Stock:\n        __slots__ = ('name','_shares','_price')\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def shares(self):\n            return self._shares\n        @shares.setter\n        def shares(self, value):\n            self._shares = PositiveInteger.check(value)\n\n        @property\n        def price(self):\n            return self._price\n        @price.setter\n        def price(self, value):\n            self._price = PositiveFloat.check(value)\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        def sell(self, nshares):\n            self.shares -= nshares\n"
  },
  {
    "path": "Solutions/4_3/validate.py",
    "content": "class Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance, value):\n        instance.__dict__[self.name] = self.check(value)\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        return super().check(value)\n\nclass Integer(Typed):\n    expected_type = int\n\nclass Float(Typed):\n    expected_type = float\n\nclass String(Typed):\n    expected_type = str\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\n# Examples\nif __name__ == '__main__':\n    def add(x, y):\n        Integer.check(x)\n        Integer.check(y)\n        return x + y\n\n    class Stock:\n        name = NonEmptyString()\n        shares = PositiveInteger()\n        price = PositiveFloat()\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        def sell(self, nshares):\n            self.shares -= nshares\n\n\n    \n\n    \n"
  },
  {
    "path": "Solutions/5_2/reader.py",
    "content": "# reader.py\n\nfrom abc import ABC, abstractmethod\nimport csv\nimport logging\nlog = logging.getLogger(__name__)\n\nclass CSVParser(ABC):\n\n    def parse(self, filename):\n        records = []\n        with open(filename) as f:\n            rows = csv.reader(f)\n            headers = next(rows)\n            for rowno, row in enumerate(rows, start=1):\n                try:\n                    record = self.make_record(headers, row)\n                    records.append(record)\n                except ValueError as e:\n                    log.warning('Row %d: Bad row: %s', rowno, row)\n                    log.debug('Row %d: Reason: %s', rowno, e)\n\n        return records\n\n    @abstractmethod\n    def make_record(self, headers, row):\n        raise NotImplementedError()\n\nclass DictCSVParser(CSVParser):\n    def __init__(self, types):\n        self.types = types\n\n    def make_record(self, headers, row):\n        return { name: func(val) for name, func, val in zip(headers, self.types, row) }\n\nclass InstanceCSVParser(CSVParser):\n    def __init__(self, cls):\n        self.cls = cls\n\n    def make_record(self, headers, row):\n        return self.cls.from_row(row)\n\ndef read_csv_as_dicts(filename, types):\n    parser = DictCSVParser(types)\n    return parser.parse(filename)\n\ndef read_csv_as_instances(filename, cls):\n    parser = InstanceCSVParser(cls)\n    return parser.parse(filename)\n"
  },
  {
    "path": "Solutions/5_2/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    __slots__ = ('name','_shares','_price')\n    _types = (str, int, float)\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    def __repr__(self):\n        # Note: The !r format code produces the repr() string\n        return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    def __eq__(self, other):\n        return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==\n                (other.name, other.shares, other.price))\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls._types, row)]\n        return cls(*values)\n\n    @property\n    def shares(self):\n        return self._shares\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value, self._types[1]):\n            raise TypeError(f'Expected {self._types[1].__name__}')\n        if value < 0:\n            raise ValueError('shares must be >= 0')\n        self._shares = value\n\n    @property\n    def price(self):\n        return self._price\n    @price.setter\n    def price(self, value):\n        if not isinstance(value, self._types[2]):\n            raise TypeError(f'Expected {self._types[2].__name__}')\n        if value < 0:\n            raise ValueError('price must be >= 0')\n        self._price = value\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/5_3/reader.py",
    "content": "# reader.py\n\nimport csv\n\ndef convert_csv(lines, converter, *, headers=None):\n    rows = csv.reader(lines)\n    if headers is None:\n        headers = next(rows)\n    return list(map(lambda row: converter(headers, row), rows))\n\ndef csv_as_dicts(lines, types, *, headers=None):\n    return convert_csv(lines,\n                       lambda headers, row: {name: func(val) for name, func, val in zip(headers, types, row)},\n                       headers=headers)\n\n\ndef csv_as_instances(lines, cls, *, headers=None):\n    return convert_csv(lines,\n                       lambda headers, row: cls.from_row(row),\n                       headers=headers)\n\ndef read_csv_as_dicts(filename, types, *, headers=None):\n    '''\n    Read CSV data into a list of dictionaries with optional type conversion\n    '''\n    with open(filename) as file:\n        return csv_as_dicts(file, types, headers=headers)\n\ndef read_csv_as_instances(filename, cls, *, headers=None):\n    '''\n    Read CSV data into a list of instances\n    '''\n    with open(filename) as file:\n        return csv_as_instances(file, cls, headers=headers)\n\n"
  },
  {
    "path": "Solutions/5_3/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    __slots__ = ('name','_shares','_price')\n    _types = (str, int, float)\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    def __repr__(self):\n        # Note: The !r format code produces the repr() string\n        return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    def __eq__(self, other):\n        return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==\n                (other.name, other.shares, other.price))\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls._types, row)]\n        return cls(*values)\n\n    @property\n    def shares(self):\n        return self._shares\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value, self._types[1]):\n            raise TypeError(f'Expected {self._types[1].__name__}')\n        if value < 0:\n            raise ValueError('shares must be >= 0')\n        self._shares = value\n\n    @property\n    def price(self):\n        return self._price\n    @price.setter\n    def price(self, value):\n        if not isinstance(value, self._types[2]):\n            raise TypeError(f'Expected {self._types[2].__name__}')\n        if value < 0:\n            raise ValueError('price must be >= 0')\n        self._price = value\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/5_4/typedproperty.py",
    "content": "# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n\n    @property\n    def value(self):\n        return getattr(self, private_name)\n\n    @value.setter\n    def value(self, val):\n        if not isinstance(val, expected_type):\n            raise TypeError(f'Expected {expected_type}')\n        setattr(self, private_name, val)\n\n    return value\n\n\nString = lambda name: typedproperty(name, str)\nInteger = lambda name: typedproperty(name, int)\nFloat = lambda name: typedproperty(name, float)\n\n# Example\nif __name__ == '__main__':\n    class Stock:\n        name = String('name')\n        shares = Integer('shares')\n        price = Float('price')\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n"
  },
  {
    "path": "Solutions/5_5/reader.py",
    "content": "# reader.py\n\nimport csv\nimport logging\n\nlog = logging.getLogger(__name__)\n\ndef convert_csv(lines, converter, *, headers=None):\n    rows = csv.reader(lines)\n    if headers is None:\n        headers = next(rows)\n\n    records = []\n    for rowno, row in enumerate(rows, start=1):\n        try:\n            records.append(converter(headers, row))\n        except ValueError as e:\n            log.warning('Row %s: Bad row: %s', rowno, row)\n            log.debug('Row %s: Reason: %s', rowno, row)\n    return records\n\ndef csv_as_dicts(lines, types, *, headers=None):\n    return convert_csv(lines, \n                       lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) })\n\ndef csv_as_instances(lines, cls, *, headers=None):\n    return convert_csv(lines,\n                       lambda headers, row: cls.from_row(row))\n\ndef read_csv_as_dicts(filename, types, *, headers=None):\n    '''\n    Read CSV data into a list of dictionaries with optional type conversion\n    '''\n    with open(filename) as file:\n        return csv_as_dicts(file, types, headers=headers)\n\ndef read_csv_as_instances(filename, cls, *, headers=None):\n    '''\n    Read CSV data into a list of instances\n    '''\n    with open(filename) as file:\n        return csv_as_instances(file, cls, headers=headers)\n\n"
  },
  {
    "path": "Solutions/5_6/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    __slots__ = ('name','_shares','_price')\n    _types = (str, int, float)\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    def __repr__(self):\n        # Note: The !r format code produces the repr() string\n        return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    def __eq__(self, other):\n        return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==\n                (other.name, other.shares, other.price))\n\n    @classmethod\n    def from_row(cls, row):\n        values = [func(val) for func, val in zip(cls._types, row)]\n        return cls(*values)\n\n    @property\n    def shares(self):\n        return self._shares\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value, self._types[1]):\n            raise TypeError(f'Expected {self._types[1].__name__}')\n        if value < 0:\n            raise ValueError('shares must be >= 0')\n        self._shares = value\n\n    @property\n    def price(self):\n        return self._price\n    @price.setter\n    def price(self, value):\n        if not isinstance(value, self._types[2]):\n            raise TypeError(f'Expected {self._types[2].__name__}')\n        if value < 0:\n            raise ValueError('price must be >= 0')\n        self._price = value\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/5_6/teststock.py",
    "content": "# teststock.py\n\nimport stock\nimport unittest\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_create_keyword(self):\n        s = stock.Stock(name='GOOG', shares=100, price=490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n        \n    def test_cost(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.cost, 49010.0)\n\n    def test_sell(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        s.sell(25)\n        self.assertEqual(s.shares, 75)\n\n    def test_from_row(self):\n        s = stock.Stock.from_row(['GOOG','100','490.1'])\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_repr(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(repr(s), \"Stock('GOOG', 100, 490.1)\")\n\n    def test_eq(self):\n        a = stock.Stock('GOOG', 100, 490.1)\n        b = stock.Stock('GOOG', 100, 490.1)\n        self.assertTrue(a==b)\n\n    # Tests for failure conditions\n    def test_shares_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.shares = '50'\n\n    def test_shares_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.shares = -50\n\n    def test_price_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.price = '45.23'\n\n    def test_price_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.price = -45.23\n\n    def test_bad_attribute(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(AttributeError):\n            s.share = 100\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "Solutions/6_1/stock.py",
    "content": "# stock.py\n\nfrom structure import Structure\n\nclass Stock(Structure):\n    _fields = ('name', 'shares', 'price')\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\n"
  },
  {
    "path": "Solutions/6_1/structure.py",
    "content": "# structure.py\n\nclass Structure:\n    _fields = ()\n    def __init__(self, *args):\n        if len(args) != len(self._fields):\n            raise TypeError('Expected %d arguments' % len(self._fields))\n        for name, val in zip(self._fields, args):\n            setattr(self, name, val)\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n"
  },
  {
    "path": "Solutions/6_1/teststock.py",
    "content": "# teststock.py\n\nimport stock\nimport unittest\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_create_keyword(self):\n        s = stock.Stock(name='GOOG', shares=100, price=490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n        \n    def test_cost(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.cost, 49010.0)\n\n    def test_sell(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        s.sell(25)\n        self.assertEqual(s.shares, 75)\n\n    def test_from_row(self):\n        s = stock.Stock.from_row(['GOOG','100','490.1'])\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_repr(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(repr(s), \"Stock('GOOG', 100, 490.1)\")\n\n    def test_eq(self):\n        a = stock.Stock('GOOG', 100, 490.1)\n        b = stock.Stock('GOOG', 100, 490.1)\n        self.assertTrue(a==b)\n\n    # Tests for failure conditions\n    def test_shares_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.shares = '50'\n\n    def test_shares_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.shares = -50\n\n    def test_price_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.price = '45.23'\n\n    def test_price_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.price = -45.23\n\n    def test_bad_attribute(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(AttributeError):\n            s.share = 100\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "Solutions/6_2/stock.py",
    "content": "# stock.py\n\nfrom structure import Structure\n\nclass Stock(Structure):\n    _fields = ('name', 'shares', 'price')\n    def __init__(self, name, shares, price):\n        self._init()\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\n"
  },
  {
    "path": "Solutions/6_2/structure.py",
    "content": "# structure.py\n\nimport sys\n\nclass Structure:\n    _fields = ()\n\n    @staticmethod\n    def _init():\n        locs = sys._getframe(1).f_locals\n        self = locs['self']\n        for name, val in locs.items():\n            if name == 'self': continue\n            setattr(self, name, val)\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n"
  },
  {
    "path": "Solutions/6_2/teststock.py",
    "content": "# teststock.py\n\nimport stock\nimport unittest\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_create_keyword(self):\n        s = stock.Stock(name='GOOG', shares=100, price=490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n        \n    def test_cost(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.cost, 49010.0)\n\n    def test_sell(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        s.sell(25)\n        self.assertEqual(s.shares, 75)\n\n    def test_from_row(self):\n        s = stock.Stock.from_row(['GOOG','100','490.1'])\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_repr(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(repr(s), \"Stock('GOOG', 100, 490.1)\")\n\n    def test_eq(self):\n        a = stock.Stock('GOOG', 100, 490.1)\n        b = stock.Stock('GOOG', 100, 490.1)\n        self.assertTrue(a==b)\n\n    # Tests for failure conditions\n    def test_shares_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.shares = '50'\n\n    def test_shares_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.shares = -50\n\n    def test_price_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.price = '45.23'\n\n    def test_price_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.price = -45.23\n\n    def test_bad_attribute(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(AttributeError):\n            s.share = 100\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "Solutions/6_3/stock.py",
    "content": "# stock.py\n\nfrom structure import Structure\n\nclass Stock(Structure):\n    def __init__(self, name, shares, price):\n        self._init()\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\nStock.set_fields()\n\n"
  },
  {
    "path": "Solutions/6_3/structure.py",
    "content": "# structure.py\n\nimport sys\nimport inspect\n\nclass Structure:\n    _fields = ()\n\n    @staticmethod\n    def _init():\n        locs = sys._getframe(1).f_locals\n        self = locs['self']\n        for name, val in locs.items():\n            if name == 'self': continue\n            setattr(self, name, val)\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    @classmethod\n    def set_fields(cls):\n        sig = inspect.signature(cls)\n        cls._fields = tuple(sig.parameters)\n\n"
  },
  {
    "path": "Solutions/6_3/teststock.py",
    "content": "# teststock.py\n\nimport stock\nimport unittest\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_create_keyword(self):\n        s = stock.Stock(name='GOOG', shares=100, price=490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n        \n    def test_cost(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.cost, 49010.0)\n\n    def test_sell(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        s.sell(25)\n        self.assertEqual(s.shares, 75)\n\n    def test_from_row(self):\n        s = stock.Stock.from_row(['GOOG','100','490.1'])\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_repr(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(repr(s), \"Stock('GOOG', 100, 490.1)\")\n\n    def test_eq(self):\n        a = stock.Stock('GOOG', 100, 490.1)\n        b = stock.Stock('GOOG', 100, 490.1)\n        self.assertTrue(a==b)\n\n    # Tests for failure conditions\n    def test_shares_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.shares = '50'\n\n    def test_shares_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.shares = -50\n\n    def test_price_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.price = '45.23'\n\n    def test_price_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.price = -45.23\n\n    def test_bad_attribute(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(AttributeError):\n            s.share = 100\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "Solutions/6_4/stock.py",
    "content": "# stock.py\n\nfrom structure import Structure\n\nclass Stock(Structure):\n    _fields = ('name', 'shares', 'price')\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\nStock.create_init()\n\n"
  },
  {
    "path": "Solutions/6_4/structure.py",
    "content": "# structure.py\n\nclass Structure:\n    _fields = ()\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    @classmethod\n    def create_init(cls):\n        '''\n        Create an __init__ method from _fields\n        '''\n        args = ','.join(cls._fields)\n        code = f'def __init__(self, {args}):\\n'\n        for name in cls._fields:\n            code += f'    self.{name} = {name}\\n'\n        locs = { }\n        exec(code, locs)\n        cls.__init__ = locs['__init__']\n"
  },
  {
    "path": "Solutions/6_4/teststock.py",
    "content": "# teststock.py\n\nimport stock\nimport unittest\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_create_keyword(self):\n        s = stock.Stock(name='GOOG', shares=100, price=490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n        \n    def test_cost(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.cost, 49010.0)\n\n    def test_sell(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        s.sell(25)\n        self.assertEqual(s.shares, 75)\n\n    def test_from_row(self):\n        s = stock.Stock.from_row(['GOOG','100','490.1'])\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_repr(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(repr(s), \"Stock('GOOG', 100, 490.1)\")\n\n    def test_eq(self):\n        a = stock.Stock('GOOG', 100, 490.1)\n        b = stock.Stock('GOOG', 100, 490.1)\n        self.assertTrue(a==b)\n\n    # Tests for failure conditions\n    def test_shares_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.shares = '50'\n\n    def test_shares_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.shares = -50\n\n    def test_price_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.price = '45.23'\n\n    def test_price_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.price = -45.23\n\n    def test_bad_attribute(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(AttributeError):\n            s.share = 100\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "Solutions/6_5/validate.py",
    "content": "class Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance, value):\n        instance.__dict__[self.name] = self.check(value)\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        return super().check(value)\n\nclass Integer(Typed):\n    expected_type = int\n\nclass Float(Typed):\n    expected_type = float\n\nclass String(Typed):\n    expected_type = str\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\nfrom inspect import signature\n\nclass ValidatedFunction:\n    def __init__(self, func):\n        self.func = func\n        self.signature = signature(func)\n        self.annotations = dict(func.__annotations__)\n        self.retcheck = self.annotations.pop('return', None)\n\n    def __call__(self, *args, **kwargs):\n        bound = self.signature.bind(*args, **kwargs)\n\n        for name, val in self.annotations.items():\n            val.check(bound.arguments[name])\n\n        result = self.func(*args, **kwargs)\n\n        if self.retcheck:\n            self.retcheck.check(result)\n\n        return result\n\n# Examples\nif __name__ == '__main__':\n    def add(x:Integer, y:Integer) -> Integer:\n        return x + y\n\n    add = ValidatedFunction(add)\n\n    class Stock:\n        name = NonEmptyString()\n        shares = PositiveInteger()\n        price = PositiveFloat()\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        def sell(self, nshares):\n            self.shares -= nshares\n\n        sell = ValidatedFunction(sell)     # Broken\n\n\n\n    \n\n    \n"
  },
  {
    "path": "Solutions/7_1/logcall.py",
    "content": "# logcall.py\n\ndef logged(func):\n    print('Adding logging to', func.__name__)\n    def wrapper(*args,**kwargs):\n        print('Calling', func.__name__)\n        return func(*args,**kwargs)\n    return wrapper\n\n        \n"
  },
  {
    "path": "Solutions/7_1/sample.py",
    "content": "# sample.py\n\nfrom logcall import logged\n\n@logged\ndef add(x,y):\n    return x+y\n\n@logged\ndef sub(x,y):\n    return x-y\n"
  },
  {
    "path": "Solutions/7_1/validate.py",
    "content": "# validate.py\n\nclass Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance, value):\n        instance.__dict__[self.name] = self.check(value)\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        return super().check(value)\n\nclass Integer(Typed):\n    expected_type = int\n\nclass Float(Typed):\n    expected_type = float\n\nclass String(Typed):\n    expected_type = str\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\n\nfrom inspect import signature\n\ndef isvalidator(item):\n    return isinstance(item, type) and issubclass(item, Validator)\n\ndef validated(func):\n    sig = signature(func)\n\n    # Gather the function annotations\n    annotations = { name:val for name, val in func.__annotations__.items()\n                    if isvalidator(val) }\n\n    # Get the return annotation (if any)\n    retcheck = annotations.pop('return', None)\n\n    def wrapper(*args, **kwargs):\n        bound = sig.bind(*args, **kwargs)\n        errors = []\n\n        # Enforce argument checks\n        for name, validator in annotations.items():\n            try:\n                validator.check(bound.arguments[name])\n            except Exception as e:\n                errors.append(f'  {name}: {e}')\n\n        if errors:\n            raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n        result = func(*args, **kwargs)\n\n        # Enforce return check (if any)\n        if retcheck:\n            try:\n                retcheck.check(result)\n            except Exception as e:\n                raise TypeError(f'Bad return: {e}') from None\n        return result\n\n    return wrapper\n\n# Examples\nif __name__ == '__main__':\n    @validated\n    def add(x:Integer, y:Integer) -> Integer:\n        return x + y\n\n    @validated\n    def div(x:Integer, y:Integer) -> Integer:\n        return x / y\n\n    class Stock:\n        name = NonEmptyString()\n        shares = PositiveInteger()\n        price = PositiveFloat()\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        @validated\n        def sell(self, nshares:PositiveInteger):\n            self.shares -= nshares\n\n    \n\n    \n"
  },
  {
    "path": "Solutions/7_2/logcall.py",
    "content": "# logcall.py\n\nfrom functools import wraps\n\ndef logformat(fmt):\n    def logged(func):\n        print('Adding logging to', func.__name__)\n        @wraps(func)\n        def wrapper(*args,**kwargs):\n            print(fmt.format(func=func))\n            return func(*args, **kwargs)\n        return wrapper\n    return logged\n\n# Original no-argument @logged decorator defined in terms of the more\n# general @logformat decorator\n\nlogged = logformat('Calling {func.__name__}')\n\n\n        \n"
  },
  {
    "path": "Solutions/7_2/sample.py",
    "content": "# sample.py\n\nfrom logcall import logged, logformat\n\n@logged\ndef add(x,y):\n    return x+y\n\n@logged\ndef sub(x,y):\n    return x-y\n\n@logformat('{func.__code__.co_filename}:{func.__name__}')\ndef mul(x,y):\n    return x*y\n"
  },
  {
    "path": "Solutions/7_2/spam.py",
    "content": "from logcall import logged\n\nclass Spam:\n    @logged\n    def instance_method(self):\n        pass\n\n    @classmethod\n    @logged\n    def class_method(cls):\n        pass\n\n    @staticmethod\n    @logged\n    def static_method():\n        pass\n\n    @property\n    @logged\n    def property_method(self):\n        pass\n"
  },
  {
    "path": "Solutions/7_2/validate.py",
    "content": "# validate.py\n\nclass Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance, value):\n        instance.__dict__[self.name] = self.check(value)\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        return super().check(value)\n\nclass Integer(Typed):\n    expected_type = int\n\nclass Float(Typed):\n    expected_type = float\n\nclass String(Typed):\n    expected_type = str\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\n\nfrom inspect import signature\nfrom functools import wraps\n\ndef isvalidator(item):\n    return isinstance(item, type) and issubclass(item, Validator)\n\ndef validated(func):\n    sig = signature(func)\n\n    # Gather the function annotations\n    annotations = { name:val for name, val in func.__annotations__.items()\n                    if isvalidator(val) }\n\n    # Get the return annotation (if any)\n    retcheck = annotations.pop('return', None)\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        bound = sig.bind(*args, **kwargs)\n        errors = []\n\n        # Enforce argument checks\n        for name, validator in annotations.items():\n            try:\n                validator.check(bound.arguments[name])\n            except Exception as e:\n                errors.append(f'  {name}: {e}')\n\n        if errors:\n            raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n        result = func(*args, **kwargs)\n\n        # Enforce return check (if any)\n        if retcheck:\n            try:\n                retcheck.check(result)\n            except Exception as e:\n                raise TypeError(f'Bad return: {e}') from None\n        return result\n\n    return wrapper\n\ndef enforce(**annotations):\n    retcheck = annotations.pop('return_', None)\n\n    def decorate(func):\n        sig = signature(func)\n\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            bound = sig.bind(*args, **kwargs)\n            errors = []\n\n            # Enforce argument checks\n            for name, validator in annotations.items():\n                try:\n                    validator.check(bound.arguments[name])\n                except Exception as e:\n                    errors.append(f'    {name}: {e}')\n\n            if errors:\n                raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n            result = func(*args, **kwargs)\n\n            if retcheck:\n                try:\n                    retcheck.check(result)\n                except Exception as e:\n                    raise TypeError(f'Bad return: {e}') from None\n            return result\n        return wrapper\n    return decorate\n\n# Examples\nif __name__ == '__main__':\n    @validated\n    def add(x:Integer, y:Integer) -> Integer:\n        return x + y\n\n    @validated\n    def div(x:Integer, y:Integer) -> Integer:\n        return x / y\n\n    @enforce(x=Integer, y=Integer)\n    def sub(x, y):\n        return x - y\n\n    class Stock:\n        name = NonEmptyString()\n        shares = PositiveInteger()\n        price = PositiveFloat()\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        @validated\n        def sell(self, nshares:PositiveInteger):\n            self.shares -= nshares\n\n    \n\n    \n"
  },
  {
    "path": "Solutions/7_3/reader.py",
    "content": "# reader.py\n\nimport csv\nimport logging\n\nlog = logging.getLogger(__name__)\n\ndef convert_csv(lines, converter, *, headers=None):\n    rows = csv.reader(lines)\n    if headers is None:\n        headers = next(rows)\n\n    records = []\n    for rowno, row in enumerate(rows, start=1):\n        try:\n            records.append(converter(headers, row))\n        except ValueError as e:\n            log.warning('Row %s: Bad row: %s', rowno, row)\n            log.debug('Row %s: Reason: %s', rowno, row)\n    return records\n\ndef csv_as_dicts(lines, types, *, headers=None):\n    return convert_csv(lines, \n                       lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) })\n\ndef csv_as_instances(lines, cls, *, headers=None):\n    return convert_csv(lines,\n                       lambda headers, row: cls.from_row(row))\n\ndef read_csv_as_dicts(filename, types, *, headers=None):\n    '''\n    Read CSV data into a list of dictionaries with optional type conversion\n    '''\n    with open(filename) as file:\n        return csv_as_dicts(file, types, headers=headers)\n\ndef read_csv_as_instances(filename, cls, *, headers=None):\n    '''\n    Read CSV data into a list of instances\n    '''\n    with open(filename) as file:\n        return csv_as_instances(file, cls, headers=headers)\n\n"
  },
  {
    "path": "Solutions/7_3/stock.py",
    "content": "# stock.py\n\nfrom structure import Structure\nfrom validate import String, PositiveInteger, PositiveFloat\n\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares: PositiveInteger):\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/7_3/structure.py",
    "content": "# structure.py\n\nfrom validate import Validator, validated\n\nclass Structure:\n    _fields = ()\n    _types = ()\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    @classmethod\n    def from_row(cls, row):\n        rowdata = [ func(val) for func, val in zip(cls._types, row) ]\n        return cls(*rowdata)\n\n    @classmethod\n    def create_init(cls):\n        '''\n        Create an __init__ method from _fields\n        '''\n        args = ','.join(cls._fields)\n        code = f'def __init__(self, {args}):\\n'\n        for name in cls._fields:\n            code += f'    self.{name} = {name}\\n'\n        locs = { }\n        exec(code, locs)\n        cls.__init__ = locs['__init__']\n\n    @classmethod\n    def __init_subclass__(cls):\n        # Apply the validated decorator to subclasses\n        validate_attributes(cls)\n\ndef validate_attributes(cls):\n    '''\n    Class decorator that scans a class definition for Validators\n    and builds a _fields variable that captures their definition order.\n    '''\n    validators = []\n    for name, val in vars(cls).items():\n        if isinstance(val, Validator):\n            validators.append(val)\n\n        # Apply validated decorator to any callable with annotations\n        elif callable(val) and val.__annotations__:\n            setattr(cls, name, validated(val))\n\n    # Collect all of the field names\n    cls._fields = tuple([v.name for v in validators])\n\n    # Collect type conversions. The lambda x:x is an identity\n    # function that's used in case no expected_type is found.\n    cls._types = tuple([ getattr(v, 'expected_type', lambda x: x)\n                   for v in validators ])\n\n    # Create the __init__ method\n    if cls._fields:\n        cls.create_init()\n\n    \n    return cls\n\n"
  },
  {
    "path": "Solutions/7_3/teststock.py",
    "content": "# teststock.py\n\nimport stock\nimport unittest\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_create_keyword(self):\n        s = stock.Stock(name='GOOG', shares=100, price=490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n        \n    def test_cost(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.cost, 49010.0)\n\n    def test_sell(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        s.sell(25)\n        self.assertEqual(s.shares, 75)\n\n    def test_from_row(self):\n        s = stock.Stock.from_row(['GOOG','100','490.1'])\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_repr(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(repr(s), \"Stock('GOOG', 100, 490.1)\")\n\n    def test_eq(self):\n        a = stock.Stock('GOOG', 100, 490.1)\n        b = stock.Stock('GOOG', 100, 490.1)\n        self.assertTrue(a==b)\n\n    # Tests for failure conditions\n    def test_shares_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.shares = '50'\n\n    def test_shares_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.shares = -50\n\n    def test_price_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.price = '45.23'\n\n    def test_price_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.price = -45.23\n\n    def test_bad_attribute(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(AttributeError):\n            s.share = 100\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "Solutions/7_3/validate.py",
    "content": "# validate.py\n\nclass Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance, value):\n        instance.__dict__[self.name] = self.check(value)\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        return super().check(value)\n\nclass Integer(Typed):\n    expected_type = int\n\nclass Float(Typed):\n    expected_type = float\n\nclass String(Typed):\n    expected_type = str\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\nfrom inspect import signature\nfrom functools import wraps\n\ndef isvalidator(item):\n    return isinstance(item, type) and issubclass(item, Validator)\n\ndef validated(func):\n    sig = signature(func)\n\n    # Gather the function annotations\n    annotations = { name:val for name, val in func.__annotations__.items()\n                    if isvalidator(val) }\n\n    # Get the return annotation (if any)\n    retcheck = annotations.pop('return', None)\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        bound = sig.bind(*args, **kwargs)\n        errors = []\n\n        # Enforce argument checks\n        for name, validator in annotations.items():\n            try:\n                validator.check(bound.arguments[name])\n            except Exception as e:\n                errors.append(f'  {name}: {e}')\n\n        if errors:\n            raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n        result = func(*args, **kwargs)\n\n        # Enforce return check (if any)\n        if retcheck:\n            try:\n                retcheck.check(result)\n            except Exception as e:\n                raise TypeError(f'Bad return: {e}') from None\n        return result\n\n    return wrapper\n\ndef enforce(**annotations):\n    retcheck = annotations.pop('return_', None)\n\n    def decorate(func):\n        sig = signature(func)\n\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            bound = sig.bind(*args, **kwargs)\n            errors = []\n\n            # Enforce argument checks\n            for name, validator in annotations.items():\n                try:\n                    validator.check(bound.arguments[name])\n                except Exception as e:\n                    errors.append(f'    {name}: {e}')\n\n            if errors:\n                raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n            result = func(*args, **kwargs)\n\n            if retcheck:\n                try:\n                    retcheck.check(result)\n                except Exception as e:\n                    raise TypeError(f'Bad return: {e}') from None\n            return result\n        return wrapper\n    return decorate\n\n# Examples\nif __name__ == '__main__':\n    @validated\n    def add(x:Integer, y:Integer) -> Integer:\n        return x + y\n\n    @validated\n    def div(x:Integer, y:Integer) -> Integer:\n        return x / y\n\n    @enforce(x=Integer, y=Integer)\n    def sub(x, y):\n        return x - y\n\n    class Stock:\n        name = NonEmptyString()\n        shares = PositiveInteger()\n        price = PositiveFloat()\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        @validated\n        def sell(self, nshares:PositiveInteger):\n            self.shares -= nshares\n\n    \n\n    \n"
  },
  {
    "path": "Solutions/7_4/stock.py",
    "content": "# stock.py\n\nfrom structure import Structure\nfrom validate import String, PositiveInteger, PositiveFloat\n\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares: PositiveInteger):\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/7_4/structure.py",
    "content": "# structure.py\n\nfrom validate import Validator, validated\n\nclass Structure:\n    _fields = ()\n    _types = ()\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    @classmethod\n    def from_row(cls, row):\n        rowdata = [ func(val) for func, val in zip(cls._types, row) ]\n        return cls(*rowdata)\n\n    @classmethod\n    def create_init(cls):\n        '''\n        Create an __init__ method from _fields\n        '''\n        args = ','.join(cls._fields)\n        code = f'def __init__(self, {args}):\\n'\n        for name in cls._fields:\n            code += f'    self.{name} = {name}\\n'\n        locs = { }\n        exec(code, locs)\n        cls.__init__ = locs['__init__']\n\n    @classmethod\n    def __init_subclass__(cls):\n        # Apply the validated decorator to subclasses\n        validate_attributes(cls)\n\ndef validate_attributes(cls):\n    '''\n    Class decorator that scans a class definition for Validators\n    and builds a _fields variable that captures their definition order.\n    '''\n    validators = []\n    for name, val in vars(cls).items():\n        if isinstance(val, Validator):\n            validators.append(val)\n\n        # Apply validated decorator to any callable with annotations\n        elif callable(val) and val.__annotations__:\n            setattr(cls, name, validated(val))\n\n    # Collect all of the field names\n    cls._fields = tuple([v.name for v in validators])\n\n    # Collect type conversions. The lambda x:x is an identity\n    # function that's used in case no expected_type is found.\n    cls._types = tuple([ getattr(v, 'expected_type', lambda x: x)\n                   for v in validators ])\n\n    # Create the __init__ method\n    if cls._fields:\n        cls.create_init()\n\n    \n    return cls\n\ndef typed_structure(clsname, **validators):\n    cls = type(clsname, (Structure,), validators)\n    return cls\n"
  },
  {
    "path": "Solutions/7_4/teststock.py",
    "content": "# teststock.py\n\nimport stock\nimport unittest\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_create_keyword(self):\n        s = stock.Stock(name='GOOG', shares=100, price=490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n        \n    def test_cost(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.cost, 49010.0)\n\n    def test_sell(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        s.sell(25)\n        self.assertEqual(s.shares, 75)\n\n    def test_from_row(self):\n        s = stock.Stock.from_row(['GOOG','100','490.1'])\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_repr(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(repr(s), \"Stock('GOOG', 100, 490.1)\")\n\n    def test_eq(self):\n        a = stock.Stock('GOOG', 100, 490.1)\n        b = stock.Stock('GOOG', 100, 490.1)\n        self.assertTrue(a==b)\n\n    # Tests for failure conditions\n    def test_shares_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.shares = '50'\n\n    def test_shares_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.shares = -50\n\n    def test_price_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.price = '45.23'\n\n    def test_price_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.price = -45.23\n\n    def test_bad_attribute(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(AttributeError):\n            s.share = 100\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "Solutions/7_4/validate.py",
    "content": "# validate.py\n\nclass Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance, value):\n        instance.__dict__[self.name] = self.check(value)\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        return super().check(value)\n\n_typed_classes = [\n    ('Integer', int),\n    ('Float', float),\n    ('String', str) ]\n\nglobals().update((name, type(name, (Typed,), {'expected_type':ty}))\n                 for name, ty in _typed_classes)\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\nfrom inspect import signature\nfrom functools import wraps\n\ndef isvalidator(item):\n    return isinstance(item, type) and issubclass(item, Validator)\n\ndef validated(func):\n    sig = signature(func)\n\n    # Gather the function annotations\n    annotations = { name:val for name, val in func.__annotations__.items()\n                    if isvalidator(val) }\n\n    # Get the return annotation (if any)\n    retcheck = annotations.pop('return', None)\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        bound = sig.bind(*args, **kwargs)\n        errors = []\n\n        # Enforce argument checks\n        for name, validator in annotations.items():\n            try:\n                validator.check(bound.arguments[name])\n            except Exception as e:\n                errors.append(f'  {name}: {e}')\n\n        if errors:\n            raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n        result = func(*args, **kwargs)\n\n        # Enforce return check (if any)\n        if retcheck:\n            try:\n                retcheck.check(result)\n            except Exception as e:\n                raise TypeError(f'Bad return: {e}') from None\n        return result\n\n    return wrapper\n\ndef enforce(**annotations):\n    retcheck = annotations.pop('return_', None)\n\n    def decorate(func):\n        sig = signature(func)\n\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            bound = sig.bind(*args, **kwargs)\n            errors = []\n\n            # Enforce argument checks\n            for name, validator in annotations.items():\n                try:\n                    validator.check(bound.arguments[name])\n                except Exception as e:\n                    errors.append(f'    {name}: {e}')\n\n            if errors:\n                raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n            result = func(*args, **kwargs)\n\n            if retcheck:\n                try:\n                    retcheck.check(result)\n                except Exception as e:\n                    raise TypeError(f'Bad return: {e}') from None\n            return result\n        return wrapper\n    return decorate\n\n# Examples\nif __name__ == '__main__':\n    @validated\n    def add(x:Integer, y:Integer) -> Integer:\n        return x + y\n\n    @validated\n    def div(x:Integer, y:Integer) -> Integer:\n        return x / y\n\n    @enforce(x=Integer, y=Integer)\n    def sub(x, y):\n        return x - y\n\n    class Stock:\n        name = NonEmptyString()\n        shares = PositiveInteger()\n        price = PositiveFloat()\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        @validated\n        def sell(self, nshares:PositiveInteger):\n            self.shares -= nshares\n\n    \n\n    \n"
  },
  {
    "path": "Solutions/7_5/mymeta.py",
    "content": "# mymeta.py\n\nclass mytype(type):\n    @staticmethod\n    def __new__(meta, name, bases, __dict__):\n        print(\"Creating class :\", name)\n        print(\"Base classes   :\", bases)\n        print(\"Attributes     :\", list(__dict__.keys()))\n        return super().__new__(meta, name, bases, __dict__)\n\nclass myobject(metaclass=mytype):\n    pass\n\nclass Stock(myobject):\n    def __init__(self,name,shares,price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n    def cost(self):\n        return self.shares*self.price\n    def sell(self,nshares):\n        self.shares -= nshares\n\nclass MyStock(Stock):\n    pass\n"
  },
  {
    "path": "Solutions/7_6/reader.py",
    "content": "# reader.py\n\nimport csv\nimport logging\n\nlog = logging.getLogger(__name__)\n\ndef convert_csv(lines, converter, *, headers=None):\n    rows = csv.reader(lines)\n    if headers is None:\n        headers = next(rows)\n\n    records = []\n    for rowno, row in enumerate(rows, start=1):\n        try:\n            records.append(converter(headers, row))\n        except ValueError as e:\n            log.warning('Row %s: Bad row: %s', rowno, row)\n            log.debug('Row %s: Reason: %s', rowno, row)\n    return records\n\ndef csv_as_dicts(lines, types, *, headers=None):\n    return convert_csv(lines, \n                       lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) })\n\ndef csv_as_instances(lines, cls, *, headers=None):\n    return convert_csv(lines,\n                       lambda headers, row: cls.from_row(row))\n\ndef read_csv_as_dicts(filename, types, *, headers=None):\n    '''\n    Read CSV data into a list of dictionaries with optional type conversion\n    '''\n    with open(filename) as file:\n        return csv_as_dicts(file, types, headers=headers)\n\ndef read_csv_as_instances(filename, cls, *, headers=None):\n    '''\n    Read CSV data into a list of instances\n    '''\n    with open(filename) as file:\n        return csv_as_instances(file, cls, headers=headers)\n\n"
  },
  {
    "path": "Solutions/7_6/stock.py",
    "content": "# stock.py\n\nfrom structure import Structure\n\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares: PositiveInteger):\n        self.shares -= nshares\n\nif __name__ == '__main__':\n    from reader import read_csv_as_instances\n    from tableformat import create_formatter, print_table\n    \n    portfolio = read_csv_as_instances('../../Data/portfolio.csv', Stock)\n    formatter = create_formatter('text')\n    print_table(portfolio, ['name','shares','price'], formatter)\n"
  },
  {
    "path": "Solutions/7_6/structure.py",
    "content": "# structure.py\n\nfrom validate import Validator, validated\nfrom collections import ChainMap\n\nclass StructureMeta(type):\n    @classmethod\n    def __prepare__(meta, clsname, bases):\n        return ChainMap({}, Validator.validators)\n        \n    @staticmethod\n    def __new__(meta, name, bases, methods):\n        methods = methods.maps[0]\n        return super().__new__(meta, name, bases, methods)\n\nclass Structure(metaclass=StructureMeta):\n    _fields = ()\n    _types = ()\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    @classmethod\n    def from_row(cls, row):\n        rowdata = [ func(val) for func, val in zip(cls._types, row) ]\n        return cls(*rowdata)\n\n\n    @classmethod\n    def create_init(cls):\n        '''\n        Create an __init__ method from _fields\n        '''\n        args = ','.join(cls._fields)\n        code = f'def __init__(self, {args}):\\n'\n        for name in cls._fields:\n            code += f'    self.{name} = {name}\\n'\n        locs = { }\n        exec(code, locs)\n        cls.__init__ = locs['__init__']\n\n    @classmethod\n    def __init_subclass__(cls):\n        # Apply the validated decorator to subclasses\n        validate_attributes(cls)\n\ndef validate_attributes(cls):\n    '''\n    Class decorator that scans a class definition for Validators\n    and builds a _fields variable that captures their definition order.\n    '''\n    validators = []\n    for name, val in vars(cls).items():\n        if isinstance(val, Validator):\n            validators.append(val)\n\n        # Apply validated decorator to any callable with annotations\n        elif callable(val) and val.__annotations__:\n            setattr(cls, name, validated(val))\n\n    # Collect all of the field names\n    cls._fields = tuple([v.name for v in validators])\n\n    # Collect type conversions. The lambda x:x is an identity\n    # function that's used in case no expected_type is found.\n    cls._types = tuple([ getattr(v, 'expected_type', lambda x: x)\n                   for v in validators ])\n\n    # Create the __init__ method\n    if cls._fields:\n        cls.create_init()\n\n    \n    return cls\n\ndef typed_structure(clsname, **validators):\n    cls = type(clsname, (Structure,), validators)\n    return cls\n"
  },
  {
    "path": "Solutions/7_6/tableformat.py",
    "content": "# tableformat.py\nfrom abc import ABC, abstractmethod\n\ndef print_table(records, fields, formatter):\n    if not isinstance(formatter, TableFormatter):\n        raise RuntimeError('Expected a TableFormatter')\n\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n\nclass TableFormatter(ABC):\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n\nclass TextTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(' '.join('%10s' % h for h in headers))\n        print(('-'*10 + ' ')*len(headers))\n    \n    def row(self, rowdata):\n        print(' '.join('%10s' % d for d in rowdata))\n\nclass CSVTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(str(d) for d in rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print('<tr>', end=' ')\n        for h in headers:\n            print('<th>%s</th>' % h, end=' ')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end=' ')\n        for d in rowdata:\n            print('<td>%s</td>' % d, end=' ')\n        print('</tr>')\n\nclass ColumnFormatMixin:\n    formats = []\n    def row(self, rowdata):\n        rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)]\n        super().row(rowdata)\n\nclass UpperHeadersMixin:\n    def headings(self, headers):\n        super().headings([h.upper() for h in headers])\n\ndef create_formatter(name, column_formats=None, upper_headers=False):\n    if name == 'text':\n        formatter_cls = TextTableFormatter\n    elif name == 'csv':\n        formatter_cls = CSVTableFormatter\n    elif name == 'html':\n        formatter_cls = HTMLTableFormatter\n    else:\n        raise RuntimeError('Unknown format %s' % name)\n\n    if column_formats:\n        class formatter_cls(ColumnFormatMixin, formatter_cls):\n              formats = column_formats\n\n    if upper_headers:\n        class formatter_cls(UpperHeadersMixin, formatter_cls):\n            pass\n\n    return formatter_cls()\n\n\n\n\n"
  },
  {
    "path": "Solutions/7_6/teststock.py",
    "content": "# teststock.py\n\nimport stock\nimport unittest\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_create_keyword(self):\n        s = stock.Stock(name='GOOG', shares=100, price=490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n        \n    def test_cost(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.cost, 49010.0)\n\n    def test_sell(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        s.sell(25)\n        self.assertEqual(s.shares, 75)\n\n    def test_from_row(self):\n        s = stock.Stock.from_row(['GOOG','100','490.1'])\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_repr(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(repr(s), \"Stock('GOOG', 100, 490.1)\")\n\n    def test_eq(self):\n        a = stock.Stock('GOOG', 100, 490.1)\n        b = stock.Stock('GOOG', 100, 490.1)\n        self.assertTrue(a==b)\n\n    # Tests for failure conditions\n    def test_shares_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.shares = '50'\n\n    def test_shares_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.shares = -50\n\n    def test_price_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.price = '45.23'\n\n    def test_price_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.price = -45.23\n\n    def test_bad_attribute(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(AttributeError):\n            s.share = 100\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "Solutions/7_6/validate.py",
    "content": "# validate.py\n\nclass Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance, value):\n        instance.__dict__[self.name] = self.check(value)\n\n    # Collect all derived classes into a dict\n    validators = { }\n    @classmethod\n    def __init_subclass__(cls):\n        cls.validators[cls.__name__] = cls\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        return super().check(value)\n\n_typed_classes = [\n    ('Integer', int),\n    ('Float', float),\n    ('String', str) ]\n\nglobals().update((name, type(name, (Typed,), {'expected_type':ty}))\n                 for name, ty in _typed_classes)\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\nfrom inspect import signature\nfrom functools import wraps\n\ndef isvalidator(item):\n    return isinstance(item, type) and issubclass(item, Validator)\n\ndef validated(func):\n    sig = signature(func)\n\n    # Gather the function annotations\n    annotations = { name:val for name, val in func.__annotations__.items()\n                    if isvalidator(val) }\n\n    # Get the return annotation (if any)\n    retcheck = annotations.pop('return', None)\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        bound = sig.bind(*args, **kwargs)\n        errors = []\n\n        # Enforce argument checks\n        for name, validator in annotations.items():\n            try:\n                validator.check(bound.arguments[name])\n            except Exception as e:\n                errors.append(f'  {name}: {e}')\n\n        if errors:\n            raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n        result = func(*args, **kwargs)\n\n        # Enforce return check (if any)\n        if retcheck:\n            try:\n                retcheck.check(result)\n            except Exception as e:\n                raise TypeError(f'Bad return: {e}') from None\n        return result\n\n    return wrapper\n\ndef enforce(**annotations):\n    retcheck = annotations.pop('return_', None)\n\n    def decorate(func):\n        sig = signature(func)\n\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            bound = sig.bind(*args, **kwargs)\n            errors = []\n\n            # Enforce argument checks\n            for name, validator in annotations.items():\n                try:\n                    validator.check(bound.arguments[name])\n                except Exception as e:\n                    errors.append(f'    {name}: {e}')\n\n            if errors:\n                raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n            result = func(*args, **kwargs)\n\n            if retcheck:\n                try:\n                    retcheck.check(result)\n                except Exception as e:\n                    raise TypeError(f'Bad return: {e}') from None\n            return result\n        return wrapper\n    return decorate\n\n# Examples\nif __name__ == '__main__':\n    @validated\n    def add(x:Integer, y:Integer) -> Integer:\n        return x + y\n\n    @validated\n    def div(x:Integer, y:Integer) -> Integer:\n        return x / y\n\n    @enforce(x=Integer, y=Integer)\n    def sub(x, y):\n        return x - y\n\n    class Stock:\n        name = NonEmptyString()\n        shares = PositiveInteger()\n        price = PositiveFloat()\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        @validated\n        def sell(self, nshares:PositiveInteger):\n            self.shares -= nshares\n\n    \n\n    \n"
  },
  {
    "path": "Solutions/8_1/follow.py",
    "content": "# follow.py\nimport os\nimport time\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being written at the end of a file.\n    '''\n    with open(filename,'r') as f:\n        f.seek(0,os.SEEK_END)\n        while True:\n             line = f.readline()\n             if line == '':\n                 time.sleep(0.1)    # Sleep briefly to avoid busy wait\n                 continue\n             yield line\n\n# Example use\nif __name__ == '__main__':\n    for line in follow('../../Data/stocklog.csv'):\n        fields = line.split(',')\n        name = fields[0].strip('\"')\n        price = float(fields[1])\n        change = float(fields[4])\n        if change < 0:\n            print('%10s %10.2f %10.2f' % (name, price, change))\n\n"
  },
  {
    "path": "Solutions/8_1/reader.py",
    "content": "# reader.py\n\nimport csv\nimport logging\n\nlog = logging.getLogger(__name__)\n\ndef convert_csv(lines, converter, *, headers=None):\n    rows = csv.reader(lines)\n    if headers is None:\n        headers = next(rows)\n\n    records = []\n    for rowno, row in enumerate(rows, start=1):\n        try:\n            records.append(converter(headers, row))\n        except ValueError as e:\n            log.warning('Row %s: Bad row: %s', rowno, row)\n            log.debug('Row %s: Reason: %s', rowno, row)\n    return records\n\ndef csv_as_dicts(lines, types, *, headers=None):\n    return convert_csv(lines, \n                       lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) })\n\ndef csv_as_instances(lines, cls, *, headers=None):\n    return convert_csv(lines,\n                       lambda headers, row: cls.from_row(row))\n\ndef read_csv_as_dicts(filename, types, *, headers=None):\n    '''\n    Read CSV data into a list of dictionaries with optional type conversion\n    '''\n    with open(filename) as file:\n        return csv_as_dicts(file, types, headers=headers)\n\ndef read_csv_as_instances(filename, cls, *, headers=None):\n    '''\n    Read CSV data into a list of instances\n    '''\n    with open(filename) as file:\n        return csv_as_instances(file, cls, headers=headers)\n\n"
  },
  {
    "path": "Solutions/8_1/stock.py",
    "content": "# stock.py\n\nfrom structure import Structure\nfrom validate import String, PositiveInteger, PositiveFloat\n\nclass Stock(Structure):\n    name = String('name')\n    shares = PositiveInteger('shares')\n    price = PositiveFloat('price')\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n\nif __name__ == '__main__':\n    from reader import read_csv_as_instances\n    from tableformat import create_formatter, print_table\n    \n    portfolio = read_csv_as_instances('../../Data/portfolio.csv', Stock)\n    formatter = create_formatter('text')\n    print_table(portfolio, ['name','shares','price'], formatter)\n"
  },
  {
    "path": "Solutions/8_1/structure.py",
    "content": "# structure.py\n\nfrom validate import Validator, validated\nfrom collections import ChainMap\n\nclass StructureMeta(type):\n    @classmethod\n    def __prepare__(meta, clsname, bases):\n        return ChainMap({}, Validator.validators)\n        \n    @staticmethod\n    def __new__(meta, name, bases, methods):\n        methods = methods.maps[0]\n        return super().__new__(meta, name, bases, methods)\n\nclass Structure(metaclass=StructureMeta):\n    _fields = ()\n    _types = ()\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    def __iter__(self):\n        for name in self._fields:\n            yield getattr(self, name)\n\n    def __eq__(self, other):\n        return isinstance(other, type(self)) and tuple(self) == tuple(other)\n\n    @classmethod\n    def from_row(cls, row):\n        rowdata = [ func(val) for func, val in zip(cls._types, row) ]\n        return cls(*rowdata)\n\n\n    @classmethod\n    def create_init(cls):\n        '''\n        Create an __init__ method from _fields\n        '''\n        args = ','.join(cls._fields)\n        code = f'def __init__(self, {args}):\\n'\n        for name in cls._fields:\n            code += f'    self.{name} = {name}\\n'\n        locs = { }\n        exec(code, locs)\n        cls.__init__ = locs['__init__']\n\n    @classmethod\n    def __init_subclass__(cls):\n        # Apply the validated decorator to subclasses\n        validate_attributes(cls)\n\ndef validate_attributes(cls):\n    '''\n    Class decorator that scans a class definition for Validators\n    and builds a _fields variable that captures their definition order.\n    '''\n    validators = []\n    for name, val in vars(cls).items():\n        if isinstance(val, Validator):\n            validators.append(val)\n\n        # Apply validated decorator to any callable with annotations\n        elif callable(val) and val.__annotations__:\n            setattr(cls, name, validated(val))\n\n    # Collect all of the field names\n    cls._fields = tuple([v.name for v in validators])\n\n    # Collect type conversions. The lambda x:x is an identity\n    # function that's used in case no expected_type is found.\n    cls._types = tuple([ getattr(v, 'expected_type', lambda x: x)\n                   for v in validators ])\n\n    # Create the __init__ method\n    if cls._fields:\n        cls.create_init()\n\n    \n    return cls\n\ndef typed_structure(clsname, **validators):\n    cls = type(clsname, (Structure,), validators)\n    return cls\n"
  },
  {
    "path": "Solutions/8_1/teststock.py",
    "content": "# teststock.py\n\nimport stock\nimport unittest\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_create_keyword(self):\n        s = stock.Stock(name='GOOG', shares=100, price=490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n        \n    def test_cost(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.cost, 49010.0)\n\n    def test_sell(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        s.sell(25)\n        self.assertEqual(s.shares, 75)\n\n    def test_from_row(self):\n        s = stock.Stock.from_row(['GOOG','100','490.1'])\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_repr(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(repr(s), \"Stock('GOOG', 100, 490.1)\")\n\n    def test_eq(self):\n        a = stock.Stock('GOOG', 100, 490.1)\n        b = stock.Stock('GOOG', 100, 490.1)\n        self.assertTrue(a==b)\n\n    # Tests for failure conditions\n    def test_shares_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.shares = '50'\n\n    def test_shares_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.shares = -50\n\n    def test_price_badtype(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.price = '45.23'\n\n    def test_price_badvalue(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(ValueError):\n            s.price = -45.23\n\n    def test_bad_attribute(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(AttributeError):\n            s.share = 100\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "Solutions/8_1/validate.py",
    "content": "# validate.py\n\nclass Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance, value):\n        instance.__dict__[self.name] = self.check(value)\n\n    # Collect all derived classes into a dict\n    validators = { }\n    @classmethod\n    def __init_subclass__(cls):\n        cls.validators[cls.__name__] = cls\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        return super().check(value)\n\n_typed_classes = [\n    ('Integer', int),\n    ('Float', float),\n    ('String', str) ]\n\nglobals().update((name, type(name, (Typed,), {'expected_type':ty}))\n                 for name, ty in _typed_classes)\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\nfrom inspect import signature\nfrom functools import wraps\n\ndef isvalidator(item):\n    return isinstance(item, type) and issubclass(item, Validator)\n\ndef validated(func):\n    sig = signature(func)\n\n    # Gather the function annotations\n    annotations = { name:val for name, val in func.__annotations__.items()\n                    if isvalidator(val) }\n\n    # Get the return annotation (if any)\n    retcheck = annotations.pop('return', None)\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        bound = sig.bind(*args, **kwargs)\n        errors = []\n\n        # Enforce argument checks\n        for name, validator in annotations.items():\n            try:\n                validator.check(bound.arguments[name])\n            except Exception as e:\n                errors.append(f'  {name}: {e}')\n\n        if errors:\n            raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n        result = func(*args, **kwargs)\n\n        # Enforce return check (if any)\n        if retcheck:\n            try:\n                retcheck.check(result)\n            except Exception as e:\n                raise TypeError(f'Bad return: {e}') from None\n        return result\n\n    return wrapper\n\ndef enforce(**annotations):\n    retcheck = annotations.pop('return_', None)\n\n    def decorate(func):\n        sig = signature(func)\n\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            bound = sig.bind(*args, **kwargs)\n            errors = []\n\n            # Enforce argument checks\n            for name, validator in annotations.items():\n                try:\n                    validator.check(bound.arguments[name])\n                except Exception as e:\n                    errors.append(f'    {name}: {e}')\n\n            if errors:\n                raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n            result = func(*args, **kwargs)\n\n            if retcheck:\n                try:\n                    retcheck.check(result)\n                except Exception as e:\n                    raise TypeError(f'Bad return: {e}') from None\n            return result\n        return wrapper\n    return decorate\n\n# Examples\nif __name__ == '__main__':\n    @validated\n    def add(x:Integer, y:Integer) -> Integer:\n        return x + y\n\n    @validated\n    def div(x:Integer, y:Integer) -> Integer:\n        return x / y\n\n    @enforce(x=Integer, y=Integer)\n    def sub(x, y):\n        return x - y\n\n    class Stock:\n        name = NonEmptyString()\n        shares = PositiveInteger()\n        price = PositiveFloat()\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        @validated\n        def sell(self, nshares:PositiveInteger):\n            self.shares -= nshares\n\n    \n\n    \n"
  },
  {
    "path": "Solutions/8_2/follow.py",
    "content": "# follow.py\nimport os\nimport time\nimport csv\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being written at the end of a file.\n    '''\n    with open(filename,'r') as f:\n        f.seek(0,os.SEEK_END)\n        while True:\n             line = f.readline()\n             if line == '':\n                 time.sleep(0.1)    # Sleep briefly to avoid busy wait\n                 continue\n             yield line\n"
  },
  {
    "path": "Solutions/8_2/structure.py",
    "content": "# structure.py\n\nfrom validate import Validator, validated\nfrom collections import ChainMap\n\nclass StructureMeta(type):\n    @classmethod\n    def __prepare__(meta, clsname, bases):\n        return ChainMap({}, Validator.validators)\n        \n    @staticmethod\n    def __new__(meta, name, bases, methods):\n        methods = methods.maps[0]\n        return super().__new__(meta, name, bases, methods)\n\nclass Structure(metaclass=StructureMeta):\n    _fields = ()\n    _types = ()\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    def __iter__(self):\n        for name in self._fields:\n            yield getattr(self, name)\n\n    def __eq__(self, other):\n        return isinstance(other, type(self)) and tuple(self) == tuple(other)\n\n    @classmethod\n    def from_row(cls, row):\n        rowdata = [ func(val) for func, val in zip(cls._types, row) ]\n        return cls(*rowdata)\n\n    @classmethod\n    def create_init(cls):\n        '''\n        Create an __init__ method from _fields\n        '''\n        args = ','.join(cls._fields)\n        code = f'def __init__(self, {args}):\\n'\n        for name in cls._fields:\n            code += f'    self.{name} = {name}\\n'\n        locs = { }\n        exec(code, locs)\n        cls.__init__ = locs['__init__']\n\n    @classmethod\n    def __init_subclass__(cls):\n        # Apply the validated decorator to subclasses\n        validate_attributes(cls)\n\ndef validate_attributes(cls):\n    '''\n    Class decorator that scans a class definition for Validators\n    and builds a _fields variable that captures their definition order.\n    '''\n    validators = []\n    for name, val in vars(cls).items():\n        if isinstance(val, Validator):\n            validators.append(val)\n\n        # Apply validated decorator to any callable with annotations\n        elif callable(val) and val.__annotations__:\n            setattr(cls, name, validated(val))\n\n    # Collect all of the field names\n    cls._fields = tuple([v.name for v in validators])\n\n    # Collect type conversions. The lambda x:x is an identity\n    # function that's used in case no expected_type is found.\n    cls._types = tuple([ getattr(v, 'expected_type', lambda x: x)\n                   for v in validators ])\n\n    # Create the __init__ method\n    if cls._fields:\n        cls.create_init()\n\n    \n    return cls\n\ndef typed_structure(clsname, **validators):\n    cls = type(clsname, (Structure,), validators)\n    return cls\n"
  },
  {
    "path": "Solutions/8_2/tableformat.py",
    "content": "# tableformat.py\nfrom abc import ABC, abstractmethod\n\ndef print_table(records, fields, formatter):\n    if not isinstance(formatter, TableFormatter):\n        raise RuntimeError('Expected a TableFormatter')\n\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n\nclass TableFormatter(ABC):\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n\nclass TextTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(' '.join('%10s' % h for h in headers))\n        print(('-'*10 + ' ')*len(headers))\n    \n    def row(self, rowdata):\n        print(' '.join('%10s' % d for d in rowdata))\n\nclass CSVTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(str(d) for d in rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print('<tr>', end=' ')\n        for h in headers:\n            print('<th>%s</th>' % h, end=' ')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end=' ')\n        for d in rowdata:\n            print('<td>%s</td>' % d, end=' ')\n        print('</tr>')\n\nclass ColumnFormatMixin:\n    formats = []\n    def row(self, rowdata):\n        rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)]\n        super().row(rowdata)\n\nclass UpperHeadersMixin:\n    def headings(self, headers):\n        super().headings([h.upper() for h in headers])\n\ndef create_formatter(name, column_formats=None, upper_headers=False):\n    if name == 'text':\n        formatter_cls = TextTableFormatter\n    elif name == 'csv':\n        formatter_cls = CSVTableFormatter\n    elif name == 'html':\n        formatter_cls = HTMLTableFormatter\n    else:\n        raise RuntimeError('Unknown format %s' % name)\n\n    if column_formats:\n        class formatter_cls(ColumnFormatMixin, formatter_cls):\n              formats = column_formats\n\n    if upper_headers:\n        class formatter_cls(UpperHeadersMixin, formatter_cls):\n            pass\n\n    return formatter_cls()\n\n\n\n\n"
  },
  {
    "path": "Solutions/8_2/ticker.py",
    "content": "# ticker.py\nfrom structure import Structure\n\nclass Ticker(Structure):\n    name = String()\n    price = Float()\n    date = String()\n    time = String()\n    change = Float()\n    open = Float()\n    high = Float()\n    low = Float()\n    volume = Integer()\n\nif __name__ == '__main__':\n    from follow import follow\n    import csv\n    from tableformat import create_formatter, print_table\n\n    formatter = create_formatter('text')\n\n    lines = follow('../../Data/stocklog.csv')\n    rows = csv.reader(lines)\n    records = (Ticker.from_row(row) for row in rows)\n    negative = (rec for rec in records if rec.change < 0)\n    print_table(negative, ['name','price','change'], formatter)\n"
  },
  {
    "path": "Solutions/8_2/validate.py",
    "content": "# validate.py\n\nclass Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance, value):\n        instance.__dict__[self.name] = self.check(value)\n\n    # Collect all derived classes into a dict\n    validators = { }\n    @classmethod\n    def __init_subclass__(cls):\n        cls.validators[cls.__name__] = cls\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        return super().check(value)\n\n_typed_classes = [\n    ('Integer', int),\n    ('Float', float),\n    ('String', str) ]\n\nglobals().update((name, type(name, (Typed,), {'expected_type':ty}))\n                 for name, ty in _typed_classes)\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\nfrom inspect import signature\nfrom functools import wraps\n\ndef isvalidator(item):\n    return isinstance(item, type) and issubclass(item, Validator)\n\ndef validated(func):\n    sig = signature(func)\n\n    # Gather the function annotations\n    annotations = { name:val for name, val in func.__annotations__.items()\n                    if isvalidator(val) }\n\n    # Get the return annotation (if any)\n    retcheck = annotations.pop('return', None)\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        bound = sig.bind(*args, **kwargs)\n        errors = []\n\n        # Enforce argument checks\n        for name, validator in annotations.items():\n            try:\n                validator.check(bound.arguments[name])\n            except Exception as e:\n                errors.append(f'  {name}: {e}')\n\n        if errors:\n            raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n        result = func(*args, **kwargs)\n\n        # Enforce return check (if any)\n        if retcheck:\n            try:\n                retcheck.check(result)\n            except Exception as e:\n                raise TypeError(f'Bad return: {e}') from None\n        return result\n\n    return wrapper\n\ndef enforce(**annotations):\n    retcheck = annotations.pop('return_', None)\n\n    def decorate(func):\n        sig = signature(func)\n\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            bound = sig.bind(*args, **kwargs)\n            errors = []\n\n            # Enforce argument checks\n            for name, validator in annotations.items():\n                try:\n                    validator.check(bound.arguments[name])\n                except Exception as e:\n                    errors.append(f'    {name}: {e}')\n\n            if errors:\n                raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n            result = func(*args, **kwargs)\n\n            if retcheck:\n                try:\n                    retcheck.check(result)\n                except Exception as e:\n                    raise TypeError(f'Bad return: {e}') from None\n            return result\n        return wrapper\n    return decorate\n\n# Examples\nif __name__ == '__main__':\n    @validated\n    def add(x:Integer, y:Integer) -> Integer:\n        return x + y\n\n    @validated\n    def div(x:Integer, y:Integer) -> Integer:\n        return x / y\n\n    @enforce(x=Integer, y=Integer)\n    def sub(x, y):\n        return x - y\n\n    class Stock:\n        name = NonEmptyString()\n        shares = PositiveInteger()\n        price = PositiveFloat()\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        @validated\n        def sell(self, nshares:PositiveInteger):\n            self.shares -= nshares\n\n    \n\n    \n"
  },
  {
    "path": "Solutions/8_3/cofollow.py",
    "content": "# cofollow.py\nimport os\nimport time\n\ndef follow(filename, target):\n    with open(filename, 'r') as f:\n        f.seek(0,os.SEEK_END)\n        while True:\n            line = f.readline()\n            if line != '':\n                target.send(line)\n            else:\n                time.sleep(0.1)\n\n# Decorator for coroutines\nfrom functools import wraps\n\ndef consumer(func):\n    @wraps(func)\n    def start(*args,**kwargs):\n        f = func(*args,**kwargs)\n        f.send(None)\n        return f\n    return start\n\n# Sample coroutine\n@consumer\ndef printer():\n    while True:\n        item = yield\n        print(item)\n\n# Example use.  \n\nif __name__ == '__main__':\n    follow('../../Data/stocklog.csv', printer())\n"
  },
  {
    "path": "Solutions/8_3/coticker.py",
    "content": "# coticker.py\nfrom structure import Structure\n\nclass Ticker(Structure):\n    name = String()\n    price = Float()\n    date = String()\n    time = String()\n    change = Float()\n    open = Float()\n    high = Float()\n    low = Float()\n    volume = Integer()\n\nfrom cofollow import consumer, follow\nfrom tableformat import create_formatter\nimport csv\n\n@consumer\ndef to_csv(target):\n    def producer():\n        while True:\n            yield line\n\n    reader = csv.reader(producer())\n    while True:\n        line = yield\n        target.send(next(reader))\n\n@consumer\ndef create_ticker(target):\n    while True:\n        row = yield\n        target.send(Ticker.from_row(row))\n\n@consumer\ndef negchange(target):\n    while True:\n        record = yield\n        if record.change < 0:\n            target.send(record)\n\n@consumer\ndef ticker(fmt, fields):\n    formatter = create_formatter(fmt)\n    formatter.headings(fields)\n    while True:\n        rec = yield\n        row = [getattr(rec, name) for name in fields]\n        formatter.row(row)\n\nif __name__ == '__main__':\n    follow('../../Data/stocklog.csv',\n           to_csv(\n           create_ticker(\n           negchange(\n           ticker('text', ['name','price','change'])))))\n"
  },
  {
    "path": "Solutions/8_3/structure.py",
    "content": "# structure.py\n\nfrom validate import Validator, validated\nfrom collections import ChainMap\n\nclass StructureMeta(type):\n    @classmethod\n    def __prepare__(meta, clsname, bases):\n        return ChainMap({}, Validator.validators)\n        \n    @staticmethod\n    def __new__(meta, name, bases, methods):\n        methods = methods.maps[0]\n        return super().__new__(meta, name, bases, methods)\n\nclass Structure(metaclass=StructureMeta):\n    _fields = ()\n    _types = ()\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    def __iter__(self):\n        for name in self._fields:\n            yield getattr(self, name)\n\n    def __eq__(self, other):\n        return isinstance(other, type(self)) and tuple(self) == tuple(other)\n\n    @classmethod\n    def from_row(cls, row):\n        rowdata = [ func(val) for func, val in zip(cls._types, row) ]\n        return cls(*rowdata)\n\n    @classmethod\n    def create_init(cls):\n        '''\n        Create an __init__ method from _fields\n        '''\n        args = ','.join(cls._fields)\n        code = f'def __init__(self, {args}):\\n'\n        for name in cls._fields:\n            code += f'    self.{name} = {name}\\n'\n        locs = { }\n        exec(code, locs)\n        cls.__init__ = locs['__init__']\n\n    @classmethod\n    def __init_subclass__(cls):\n        # Apply the validated decorator to subclasses\n        validate_attributes(cls)\n\ndef validate_attributes(cls):\n    '''\n    Class decorator that scans a class definition for Validators\n    and builds a _fields variable that captures their definition order.\n    '''\n    validators = []\n    for name, val in vars(cls).items():\n        if isinstance(val, Validator):\n            validators.append(val)\n\n        # Apply validated decorator to any callable with annotations\n        elif callable(val) and val.__annotations__:\n            setattr(cls, name, validated(val))\n\n    # Collect all of the field names\n    cls._fields = tuple([v.name for v in validators])\n\n    # Collect type conversions. The lambda x:x is an identity\n    # function that's used in case no expected_type is found.\n    cls._types = tuple([ getattr(v, 'expected_type', lambda x: x)\n                   for v in validators ])\n\n    # Create the __init__ method\n    if cls._fields:\n        cls.create_init()\n\n    \n    return cls\n\ndef typed_structure(clsname, **validators):\n    cls = type(clsname, (Structure,), validators)\n    return cls\n"
  },
  {
    "path": "Solutions/8_3/tableformat.py",
    "content": "# tableformat.py\nfrom abc import ABC, abstractmethod\n\ndef print_table(records, fields, formatter):\n    if not isinstance(formatter, TableFormatter):\n        raise RuntimeError('Expected a TableFormatter')\n\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n\nclass TableFormatter(ABC):\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n\nclass TextTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(' '.join('%10s' % h for h in headers))\n        print(('-'*10 + ' ')*len(headers))\n    \n    def row(self, rowdata):\n        print(' '.join('%10s' % d for d in rowdata))\n\nclass CSVTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(str(d) for d in rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print('<tr>', end=' ')\n        for h in headers:\n            print('<th>%s</th>' % h, end=' ')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end=' ')\n        for d in rowdata:\n            print('<td>%s</td>' % d, end=' ')\n        print('</tr>')\n\nclass ColumnFormatMixin:\n    formats = []\n    def row(self, rowdata):\n        rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)]\n        super().row(rowdata)\n\nclass UpperHeadersMixin:\n    def headings(self, headers):\n        super().headings([h.upper() for h in headers])\n\ndef create_formatter(name, column_formats=None, upper_headers=False):\n    if name == 'text':\n        formatter_cls = TextTableFormatter\n    elif name == 'csv':\n        formatter_cls = CSVTableFormatter\n    elif name == 'html':\n        formatter_cls = HTMLTableFormatter\n    else:\n        raise RuntimeError('Unknown format %s' % name)\n\n    if column_formats:\n        class formatter_cls(ColumnFormatMixin, formatter_cls):\n              formats = column_formats\n\n    if upper_headers:\n        class formatter_cls(UpperHeadersMixin, formatter_cls):\n            pass\n\n    return formatter_cls()\n\n\n\n\n"
  },
  {
    "path": "Solutions/8_3/validate.py",
    "content": "# validate.py\n\nclass Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance, value):\n        instance.__dict__[self.name] = self.check(value)\n\n    # Collect all derived classes into a dict\n    validators = { }\n    @classmethod\n    def __init_subclass__(cls):\n        cls.validators[cls.__name__] = cls\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        return super().check(value)\n\n_typed_classes = [\n    ('Integer', int),\n    ('Float', float),\n    ('String', str) ]\n\nglobals().update((name, type(name, (Typed,), {'expected_type':ty}))\n                 for name, ty in _typed_classes)\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\nfrom inspect import signature\nfrom functools import wraps\n\ndef isvalidator(item):\n    return isinstance(item, type) and issubclass(item, Validator)\n\ndef validated(func):\n    sig = signature(func)\n\n    # Gather the function annotations\n    annotations = { name:val for name, val in func.__annotations__.items()\n                    if isvalidator(val) }\n\n    # Get the return annotation (if any)\n    retcheck = annotations.pop('return', None)\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        bound = sig.bind(*args, **kwargs)\n        errors = []\n\n        # Enforce argument checks\n        for name, validator in annotations.items():\n            try:\n                validator.check(bound.arguments[name])\n            except Exception as e:\n                errors.append(f'  {name}: {e}')\n\n        if errors:\n            raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n        result = func(*args, **kwargs)\n\n        # Enforce return check (if any)\n        if retcheck:\n            try:\n                retcheck.check(result)\n            except Exception as e:\n                raise TypeError(f'Bad return: {e}') from None\n        return result\n\n    return wrapper\n\ndef enforce(**annotations):\n    retcheck = annotations.pop('return_', None)\n\n    def decorate(func):\n        sig = signature(func)\n\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            bound = sig.bind(*args, **kwargs)\n            errors = []\n\n            # Enforce argument checks\n            for name, validator in annotations.items():\n                try:\n                    validator.check(bound.arguments[name])\n                except Exception as e:\n                    errors.append(f'    {name}: {e}')\n\n            if errors:\n                raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n            result = func(*args, **kwargs)\n\n            if retcheck:\n                try:\n                    retcheck.check(result)\n                except Exception as e:\n                    raise TypeError(f'Bad return: {e}') from None\n            return result\n        return wrapper\n    return decorate\n\n# Examples\nif __name__ == '__main__':\n    @validated\n    def add(x:Integer, y:Integer) -> Integer:\n        return x + y\n\n    @validated\n    def div(x:Integer, y:Integer) -> Integer:\n        return x / y\n\n    @enforce(x=Integer, y=Integer)\n    def sub(x, y):\n        return x - y\n\n    class Stock:\n        name = NonEmptyString()\n        shares = PositiveInteger()\n        price = PositiveFloat()\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        @validated\n        def sell(self, nshares:PositiveInteger):\n            self.shares -= nshares\n\n    \n\n    \n"
  },
  {
    "path": "Solutions/8_4/cofollow.py",
    "content": "# cofollow.py\nimport os\nimport time\nimport csv\n\ndef follow(filename,target):\n    with open(filename,\"r\") as f:\n        f.seek(0,os.SEEK_END)\n        while True:\n            line = f.readline()\n            if line != '':\n                target.send(line)\n            else:\n                time.sleep(0.1)\n\n# Decorator for coroutines\nfrom functools import wraps\n\ndef consumer(func):\n    @wraps(func)\n    def start(*args,**kwargs):\n        f = func(*args,**kwargs)\n        f.send(None)\n        return f\n    return start\n\n# Sample coroutine\n@consumer\ndef printer():\n    while True:\n        try:\n            item = yield\n            print(item)\n        except Exception as e:\n            print('ERROR: %r' % e)\n\n# Example use.  \n\nif __name__ == '__main__':\n    follow('../../Data/stocklog.csv', printer())\n"
  },
  {
    "path": "Solutions/8_4/follow.py",
    "content": "# follow.py\nimport os\nimport time\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being written at the end of a file.\n    '''\n    try:\n        with open(filename,'r') as f:\n            f.seek(0,os.SEEK_END)\n            while True:\n                 line = f.readline()\n                 if line == '':\n                     time.sleep(0.1)    # Sleep briefly to avoid busy wait\n                     continue\n                 yield line\n    except GeneratorExit:\n        print('Following Done')\n\ndef splitter(lines):\n    for line in lines:\n        yield line.split(',')\n\n\ndef make_records(rows,names):\n    for row in rows:\n        yield dict(zip(names,row))\n\ndef unquote(records,keylist):\n    for r in records:\n        for key in keylist:\n            r[key] = r[key].strip('\"')\n        yield r\n\ndef convert(records,converter,keylist):\n    for r in records:\n        for key in keylist:\n            r[key] = converter(r[key])\n        yield r\n\ndef parse_stock_data(lines):\n    rows = splitter(lines)\n    records = make_records(rows,['name','price','date','time',\n                              'change','open','high','low','volume'])\n    records = unquote(records,[\"name\",\"date\",\"time\"])\n    records = convert(records,float,['price','change','open','high','low'])\n    records = convert(records,int,['volume'])\n    return records\n\n# Sample use\nif __name__ == '__main__':\n   lines = follow(\"../../Data/stocklog.dat\")\n   records = parse_stock_data(lines)\n   for r in records:\n       print(\"%(name)10s %(price)10.2f %(change)10.2f\" % r)\n"
  },
  {
    "path": "Solutions/8_5/multitask.py",
    "content": "# multitask.py\n\nfrom collections import deque\n\ntasks = deque()\ndef run():\n    while tasks:\n        task = tasks.popleft()\n        try:\n            next(task)\n            tasks.append(task)\n        except StopIteration:\n            print('Task done')\n\ndef countdown(n):\n    while n > 0:\n        print('T-minus', n)\n        yield\n        n -= 1\n\ndef countup(n):\n    x = 0\n    while x < n:\n        print('Up we go', x)\n        yield\n        x += 1\n\nif __name__ == '__main__':\n    tasks.append(countdown(10))\n    tasks.append(countdown(5))\n    tasks.append(countup(20))\n    run()\n\n"
  },
  {
    "path": "Solutions/8_5/server.py",
    "content": "# server.py\n\nfrom socket import *\nfrom select import select\nfrom collections import deque\n\ntasks = deque()\nrecv_wait = {}   #  sock -> task\nsend_wait = {}   #  sock -> task\n\ndef run():\n    while any([tasks, recv_wait, send_wait]):\n        while not tasks:\n            can_recv, can_send, _ = select(recv_wait, send_wait, [])\n            for s in can_recv:\n                tasks.append(recv_wait.pop(s))\n            for s in can_send:\n                tasks.append(send_wait.pop(s))\n        task = tasks.popleft()\n        try:\n            reason, resource = task.send(None)\n            if reason == 'recv':\n                recv_wait[resource] = task\n            elif reason == 'send':\n                send_wait[resource] = task\n            else:\n                raise RuntimeError('Unknown reason %r' % reason)\n        except StopIteration:\n            print('Task done')\n\ndef tcp_server(address, handler):\n    sock = socket(AF_INET, SOCK_STREAM)\n    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)\n    sock.bind(address)\n    sock.listen(5)\n    while True:\n        yield 'recv', sock\n        client, addr = sock.accept()\n        tasks.append(handler(client, addr))\n        \ndef echo_handler(client, address):\n    print('Connection from', address)\n    while True:\n        yield 'recv', client\n        data = client.recv(1000)\n        if not data:\n            break\n        yield 'send', client\n        client.send(b'GOT:' + data)\n    print('Connection closed')\n\nif __name__ == '__main__':\n    tasks.append(tcp_server(('',25000), echo_handler))\n    run()\n\n"
  },
  {
    "path": "Solutions/8_6/asyncserver.py",
    "content": "# server.py\n\nfrom socket import *\nfrom select import select\nfrom collections import deque\nfrom types import coroutine\n\ntasks = deque()\nrecv_wait = {}   #  sock -> task\nsend_wait = {}   #  sock -> task\n\ndef run():\n    while any([tasks, recv_wait, send_wait]):\n        while not tasks:\n            can_recv, can_send, _ = select(recv_wait, send_wait, [])\n            for s in can_recv:\n                tasks.append(recv_wait.pop(s))\n            for s in can_send:\n                tasks.append(send_wait.pop(s))\n        task = tasks.popleft()\n        try:\n            reason, resource = task.send(None)\n            if reason == 'recv':\n                recv_wait[resource] = task\n            elif reason == 'send':\n                send_wait[resource] = task\n            else:\n                raise RuntimeError('Unknown reason %r' % reason)\n        except StopIteration:\n            print('Task done')\n\nclass GenSocket:\n    def __init__(self, sock):\n        self.sock = sock\n\n    @coroutine\n    def accept(self):\n        yield 'recv', self.sock\n        client, addr = self.sock.accept()\n        return GenSocket(client), addr\n\n    @coroutine\n    def recv(self, maxsize):\n        yield 'recv', self.sock\n        return self.sock.recv(maxsize)\n\n    @coroutine\n    def send(self, data):\n        yield 'send', self.sock\n        return self.sock.send(data)\n\n    def __getattr__(self, name):\n        return getattr(self.sock, name)\n\nasync def tcp_server(address, handler):\n    sock = GenSocket(socket(AF_INET, SOCK_STREAM))\n    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)\n    sock.bind(address)\n    sock.listen(5)\n    while True:\n        client, addr = await sock.accept()\n        tasks.append(handler(client, addr))\n        \nasync def echo_handler(client, address):\n    print('Connection from', address)\n    while True:\n        data = await client.recv(1000)\n        if not data:\n            break\n        await client.send(b'GOT:' + data)\n    print('Connection closed')\n\nif __name__ == '__main__':\n    tasks.append(tcp_server(('',25000), echo_handler))\n    run()\n\n"
  },
  {
    "path": "Solutions/8_6/cofollow.py",
    "content": "# cofollow.py\nimport os\nimport time\nimport csv\n\ndef follow(filename,target):\n    with open(filename,\"r\") as f:\n        f.seek(0,os.SEEK_END)\n        while True:\n            line = f.readline()\n            if line != '':\n                target.send(line)\n            else:\n                time.sleep(0.1)\n\ndef receive(expected_type):\n    msg = yield\n    assert isinstance(msg, expected_type), 'Expected type %s' % (expected_type)\n    return msg\n\n# Decorator for coroutines\nfrom functools import wraps\n\ndef consumer(func):\n    @wraps(func)\n    def start(*args,**kwargs):\n        f = func(*args,**kwargs)\n        f.send(None)\n        return f\n    return start\n\n# Sample coroutine\n@consumer\ndef printer():\n    while True:\n        item = yield from receive(object)\n        print(item)\n\n# Example use.  \nif __name__ == '__main__':\n    follow('../../Data/stocklog.csv', printer())\n"
  },
  {
    "path": "Solutions/8_6/coticker.py",
    "content": "# coticker.py\nfrom structure import Structure\nfrom validate import String, Integer, Float\n\nclass Ticker(Structure):\n    name = String()\n    price = Float()\n    date = String()\n    time = String()\n    change = Float()\n    open = Float()\n    high = Float()\n    low = Float()\n    volume = Integer()\n\nfrom cofollow import consumer, follow, receive\nfrom tableformat import create_formatter\nimport csv\n\n@consumer\ndef to_csv(target):\n    def producer():\n        while True:\n            yield line\n\n    reader = csv.reader(producer())\n    while True:\n        line = yield from receive(str)\n        target.send(next(reader))\n\n@consumer\ndef create_ticker(target):\n    while True:\n        row = yield from receive(list)\n        target.send(Ticker.from_row(row))\n\n@consumer\ndef negchange(target):\n    while True:\n        record = yield from receive(Ticker)\n        if record.change < 0:\n            target.send(record)\n\n@consumer\ndef ticker(fmt, fields):\n    formatter = create_formatter('text')\n    formatter.headings(fields)\n    while True:\n        rec = yield from receive(Ticker)\n        row = [getattr(rec, name) for name in fields]\n        formatter.row(row)\n\nif __name__ == '__main__':\n    follow('../../Data/stocklog.csv',\n           to_csv(\n           create_ticker(\n           negchange(\n           ticker('text', ['name','price','change'])))))\n"
  },
  {
    "path": "Solutions/8_6/server.py",
    "content": "# server.py\n\nfrom socket import *\nfrom select import select\nfrom collections import deque\n\ntasks = deque()\nrecv_wait = {}   #  sock -> task\nsend_wait = {}   #  sock -> task\n\ndef run():\n    while any([tasks, recv_wait, send_wait]):\n        while not tasks:\n            can_recv, can_send, _ = select(recv_wait, send_wait, [])\n            for s in can_recv:\n                tasks.append(recv_wait.pop(s))\n            for s in can_send:\n                tasks.append(send_wait.pop(s))\n        task = tasks.popleft()\n        try:\n            reason, resource = task.send(None)\n            if reason == 'recv':\n                recv_wait[resource] = task\n            elif reason == 'send':\n                send_wait[resource] = task\n            else:\n                raise RuntimeError('Unknown reason %r' % reason)\n        except StopIteration:\n            print('Task done')\n\nclass GenSocket:\n    def __init__(self, sock):\n        self.sock = sock\n\n    def accept(self):\n        yield 'recv', self.sock\n        client, addr = self.sock.accept()\n        return GenSocket(client), addr\n\n    def recv(self, maxsize):\n        yield 'recv', self.sock\n        return self.sock.recv(maxsize)\n\n    def send(self, data):\n        yield 'send', self.sock\n        return self.sock.send(data)\n\n    def __getattr__(self, name):\n        return getattr(self.sock, name)\n\ndef tcp_server(address, handler):\n    sock = GenSocket(socket(AF_INET, SOCK_STREAM))\n    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)\n    sock.bind(address)\n    sock.listen(5)\n    while True:\n        client, addr = yield from sock.accept()\n        tasks.append(handler(client, addr))\n        \ndef echo_handler(client, address):\n    print('Connection from', address)\n    while True:\n        data = yield from client.recv(1000)\n        if not data:\n            break\n        yield from client.send(b'GOT:' + data)\n    print('Connection closed')\n\nif __name__ == '__main__':\n    tasks.append(tcp_server(('',25000), echo_handler))\n    run()\n\n"
  },
  {
    "path": "Solutions/8_6/structure.py",
    "content": "# structure.py\n\nfrom validate import Validator, validated\nfrom collections import ChainMap\n\nclass StructureMeta(type):\n    @classmethod\n    def __prepare__(meta, clsname, bases):\n        return ChainMap({}, Validator.validators)\n        \n    @staticmethod\n    def __new__(meta, name, bases, methods):\n        methods = methods.maps[0]\n        return super().__new__(meta, name, bases, methods)\n\nclass Structure(metaclass=StructureMeta):\n    _fields = ()\n    _types = ()\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    def __iter__(self):\n        for name in self._fields:\n            yield getattr(self, name)\n\n    def __eq__(self, other):\n        return isinstance(other, type(self)) and tuple(self) == tuple(other)\n\n    @classmethod\n    def from_row(cls, row):\n        rowdata = [ func(val) for func, val in zip(cls._types, row) ]\n        return cls(*rowdata)\n\n    @classmethod\n    def create_init(cls):\n        '''\n        Create an __init__ method from _fields\n        '''\n        args = ','.join(cls._fields)\n        code = f'def __init__(self, {args}):\\n'\n        for name in cls._fields:\n            code += f'    self.{name} = {name}\\n'\n        locs = { }\n        exec(code, locs)\n        cls.__init__ = locs['__init__']\n\n    @classmethod\n    def __init_subclass__(cls):\n        # Apply the validated decorator to subclasses\n        validate_attributes(cls)\n\ndef validate_attributes(cls):\n    '''\n    Class decorator that scans a class definition for Validators\n    and builds a _fields variable that captures their definition order.\n    '''\n    validators = []\n    for name, val in vars(cls).items():\n        if isinstance(val, Validator):\n            validators.append(val)\n\n        # Apply validated decorator to any callable with annotations\n        elif callable(val) and val.__annotations__:\n            setattr(cls, name, validated(val))\n\n    # Collect all of the field names\n    cls._fields = tuple([v.name for v in validators])\n\n    # Collect type conversions. The lambda x:x is an identity\n    # function that's used in case no expected_type is found.\n    cls._types = tuple([ getattr(v, 'expected_type', lambda x: x)\n                   for v in validators ])\n\n    # Create the __init__ method\n    if cls._fields:\n        cls.create_init()\n\n    \n    return cls\n\ndef typed_structure(clsname, **validators):\n    cls = type(clsname, (Structure,), validators)\n    return cls\n"
  },
  {
    "path": "Solutions/8_6/tableformat.py",
    "content": "# tableformat.py\nfrom abc import ABC, abstractmethod\n\ndef print_table(records, fields, formatter):\n    if not isinstance(formatter, TableFormatter):\n        raise RuntimeError('Expected a TableFormatter')\n\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n\nclass TableFormatter(ABC):\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n\nclass TextTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(' '.join('%10s' % h for h in headers))\n        print(('-'*10 + ' ')*len(headers))\n    \n    def row(self, rowdata):\n        print(' '.join('%10s' % d for d in rowdata))\n\nclass CSVTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(str(d) for d in rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print('<tr>', end=' ')\n        for h in headers:\n            print('<th>%s</th>' % h, end=' ')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end=' ')\n        for d in rowdata:\n            print('<td>%s</td>' % d, end=' ')\n        print('</tr>')\n\nclass ColumnFormatMixin:\n    formats = []\n    def row(self, rowdata):\n        rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)]\n        super().row(rowdata)\n\nclass UpperHeadersMixin:\n    def headings(self, headers):\n        super().headings([h.upper() for h in headers])\n\ndef create_formatter(name, column_formats=None, upper_headers=False):\n    if name == 'text':\n        formatter_cls = TextTableFormatter\n    elif name == 'csv':\n        formatter_cls = CSVTableFormatter\n    elif name == 'html':\n        formatter_cls = HTMLTableFormatter\n    else:\n        raise RuntimeError('Unknown format %s' % name)\n\n    if column_formats:\n        class formatter_cls(ColumnFormatMixin, formatter_cls):\n              formats = column_formats\n\n    if upper_headers:\n        class formatter_cls(UpperHeadersMixin, formatter_cls):\n            pass\n\n    return formatter_cls()\n\n\n\n\n"
  },
  {
    "path": "Solutions/8_6/validate.py",
    "content": "# validate.py\n\nclass Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance, value):\n        instance.__dict__[self.name] = self.check(value)\n\n    # Collect all derived classes into a dict\n    validators = { }\n    @classmethod\n    def __init_subclass__(cls):\n        cls.validators[cls.__name__] = cls\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        return super().check(value)\n\n_typed_classes = [\n    ('Integer', int),\n    ('Float', float),\n    ('String', str) ]\n\nglobals().update((name, type(name, (Typed,), {'expected_type':ty}))\n                 for name, ty in _typed_classes)\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\nfrom inspect import signature\nfrom functools import wraps\n\ndef isvalidator(item):\n    return isinstance(item, type) and issubclass(item, Validator)\n\ndef validated(func):\n    sig = signature(func)\n\n    # Gather the function annotations\n    annotations = { name:val for name, val in func.__annotations__.items()\n                    if isvalidator(val) }\n\n    # Get the return annotation (if any)\n    retcheck = annotations.pop('return', None)\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        bound = sig.bind(*args, **kwargs)\n        errors = []\n\n        # Enforce argument checks\n        for name, validator in annotations.items():\n            try:\n                validator.check(bound.arguments[name])\n            except Exception as e:\n                errors.append(f'  {name}: {e}')\n\n        if errors:\n            raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n        result = func(*args, **kwargs)\n\n        # Enforce return check (if any)\n        if retcheck:\n            try:\n                retcheck.check(result)\n            except Exception as e:\n                raise TypeError(f'Bad return: {e}') from None\n        return result\n\n    return wrapper\n\ndef enforce(**annotations):\n    retcheck = annotations.pop('return_', None)\n\n    def decorate(func):\n        sig = signature(func)\n\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            bound = sig.bind(*args, **kwargs)\n            errors = []\n\n            # Enforce argument checks\n            for name, validator in annotations.items():\n                try:\n                    validator.check(bound.arguments[name])\n                except Exception as e:\n                    errors.append(f'    {name}: {e}')\n\n            if errors:\n                raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n            result = func(*args, **kwargs)\n\n            if retcheck:\n                try:\n                    retcheck.check(result)\n                except Exception as e:\n                    raise TypeError(f'Bad return: {e}') from None\n            return result\n        return wrapper\n    return decorate\n\n# Examples\nif __name__ == '__main__':\n    @validated\n    def add(x:Integer, y:Integer) -> Integer:\n        return x + y\n\n    @validated\n    def div(x:Integer, y:Integer) -> Integer:\n        return x / y\n\n    @enforce(x=Integer, y=Integer)\n    def sub(x, y):\n        return x - y\n\n    class Stock:\n        name = NonEmptyString()\n        shares = PositiveInteger()\n        price = PositiveFloat()\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        @validated\n        def sell(self, nshares:PositiveInteger):\n            self.shares -= nshares\n\n    \n\n    \n"
  },
  {
    "path": "Solutions/9_1/simplemod.py",
    "content": "# simplemod.py\n\nx = 42        # A global variable\n\n# A simple function\ndef foo():   \n    print(\"x is %s\" % x)\n\n# A simple class\nclass Spam:\n    def yow(self):\n        print('Yow!')\n\n# A scripting statement\nprint(\"Loaded simplemod\")\n"
  },
  {
    "path": "Solutions/9_2/stock.py",
    "content": "# stock.py\n\nfrom structly.structure import Structure\n\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n    \n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares: PositiveInteger):\n        self.shares -= nshares\n\nif __name__ == '__main__':\n    from structly.reader import read_csv_as_instances\n    from structly.tableformat import create_formatter, print_table\n    portfolio = read_csv_as_instances('../../Data/portfolio.csv', Stock)\n    formatter = create_formatter('text')\n    print_table(portfolio, ['name','shares','price'], formatter)\n\n"
  },
  {
    "path": "Solutions/9_2/structly/__init__.py",
    "content": ""
  },
  {
    "path": "Solutions/9_2/structly/reader.py",
    "content": "# reader.py\n\nimport csv\nimport logging\n\nlog = logging.getLogger(__name__)\n\ndef convert_csv(lines, converter, *, headers=None):\n    rows = csv.reader(lines)\n    if headers is None:\n        headers = next(rows)\n\n    records = []\n    for rowno, row in enumerate(rows, start=1):\n        try:\n            records.append(converter(headers, row))\n        except ValueError as e:\n            log.warning('Row %s: Bad row: %s', rowno, row)\n            log.debug('Row %s: Reason: %s', rowno, row)\n    return records\n\ndef csv_as_dicts(lines, types, *, headers=None):\n    return convert_csv(lines, \n                       lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) })\n\ndef csv_as_instances(lines, cls, *, headers=None):\n    return convert_csv(lines,\n                       lambda headers, row: cls.from_row(row))\n\ndef read_csv_as_dicts(filename, types, *, headers=None):\n    '''\n    Read CSV data into a list of dictionaries with optional type conversion\n    '''\n    with open(filename) as file:\n        return csv_as_dicts(file, types, headers=headers)\n\ndef read_csv_as_instances(filename, cls, *, headers=None):\n    '''\n    Read CSV data into a list of instances\n    '''\n    with open(filename) as file:\n        return csv_as_instances(file, cls, headers=headers)\n\n"
  },
  {
    "path": "Solutions/9_2/structly/structure.py",
    "content": "# structure.py\n\nfrom .validate import Validator, validated\nfrom collections import ChainMap\n\nclass StructureMeta(type):\n    @classmethod\n    def __prepare__(meta, clsname, bases):\n        return ChainMap({}, Validator.validators)\n        \n    @staticmethod\n    def __new__(meta, name, bases, methods):\n        methods = methods.maps[0]\n        return super().__new__(meta, name, bases, methods)\n\nclass Structure(metaclass=StructureMeta):\n    _fields = ()\n    _types = ()\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    def __iter__(self):\n        for name in self._fields:\n            yield getattr(self, name)\n\n    def __eq__(self, other):\n        return isinstance(other, type(self)) and tuple(self) == tuple(other)\n\n    @classmethod\n    def from_row(cls, row):\n        rowdata = [ func(val) for func, val in zip(cls._types, row) ]\n        return cls(*rowdata)\n\n    @classmethod\n    def create_init(cls):\n        '''\n        Create an __init__ method from _fields\n        '''\n        args = ','.join(cls._fields)\n        code = f'def __init__(self, {args}):\\n'\n        for name in cls._fields:\n            code += f'    self.{name} = {name}\\n'\n        locs = { }\n        exec(code, locs)\n        cls.__init__ = locs['__init__']\n\n    @classmethod\n    def __init_subclass__(cls):\n        # Apply the validated decorator to subclasses\n        validate_attributes(cls)\n\ndef validate_attributes(cls):\n    '''\n    Class decorator that scans a class definition for Validators\n    and builds a _fields variable that captures their definition order.\n    '''\n    validators = []\n    for name, val in vars(cls).items():\n        if isinstance(val, Validator):\n            validators.append(val)\n\n        # Apply validated decorator to any callable with annotations\n        elif callable(val) and val.__annotations__:\n            setattr(cls, name, validated(val))\n\n    # Collect all of the field names\n    cls._fields = tuple([v.name for v in validators])\n\n    # Collect type conversions. The lambda x:x is an identity\n    # function that's used in case no expected_type is found.\n    cls._types = tuple([ getattr(v, 'expected_type', lambda x: x)\n                   for v in validators ])\n\n    # Create the __init__ method\n    if cls._fields:\n        cls.create_init()\n\n    \n    return cls\n\ndef typed_structure(clsname, **validators):\n    cls = type(clsname, (Structure,), validators)\n    return cls\n"
  },
  {
    "path": "Solutions/9_2/structly/tableformat.py",
    "content": "# tableformat.py\nfrom abc import ABC, abstractmethod\n\ndef print_table(records, fields, formatter):\n    if not isinstance(formatter, TableFormatter):\n        raise RuntimeError('Expected a TableFormatter')\n\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n\nclass TableFormatter(ABC):\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n\n\nclass TextTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(' '.join('%10s' % h for h in headers))\n        print(('-'*10 + ' ')*len(headers))\n    \n    def row(self, rowdata):\n        print(' '.join('%10s' % d for d in rowdata))\n\nclass CSVTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(str(d) for d in rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print('<tr>', end=' ')\n        for h in headers:\n            print('<th>%s</th>' % h, end=' ')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end=' ')\n        for d in rowdata:\n            print('<td>%s</td>' % d, end=' ')\n        print('</tr>')\n\nclass ColumnFormatMixin:\n    formats = []\n    def row(self, rowdata):\n        rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)]\n        super().row(rowdata)\n\nclass UpperHeadersMixin:\n    def headings(self, headers):\n        super().headings([h.upper() for h in headers])\n\ndef create_formatter(name, column_formats=None, upper_headers=False):\n    if name == 'text':\n        formatter_cls = TextTableFormatter\n    elif name == 'csv':\n        formatter_cls = CSVTableFormatter\n    elif name == 'html':\n        formatter_cls = HTMLTableFormatter\n    else:\n        raise RuntimeError('Unknown format %s' % name)\n\n    if column_formats:\n        class formatter_cls(ColumnFormatMixin, formatter_cls):\n              formats = column_formats\n\n    if upper_headers:\n        class formatter_cls(UpperHeadersMixin, formatter_cls):\n            pass\n\n    return formatter_cls()\n\n\n\n\n"
  },
  {
    "path": "Solutions/9_2/structly/validate.py",
    "content": "# validate.py\n\nclass Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance, value):\n        instance.__dict__[self.name] = self.check(value)\n\n    # Collect all derived classes into a dict\n    validators = { }\n    @classmethod\n    def __init_subclass__(cls):\n        cls.validators[cls.__name__] = cls\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        return super().check(value)\n\n_typed_classes = [\n    ('Integer', int),\n    ('Float', float),\n    ('String', str) ]\n\nglobals().update((name, type(name, (Typed,), {'expected_type':ty}))\n                 for name, ty in _typed_classes)\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\nfrom inspect import signature\nfrom functools import wraps\n\ndef isvalidator(item):\n    return isinstance(item, type) and issubclass(item, Validator)\n\ndef validated(func):\n    sig = signature(func)\n\n    # Gather the function annotations\n    annotations = { name:val for name, val in func.__annotations__.items()\n                    if isvalidator(val) }\n\n    # Get the return annotation (if any)\n    retcheck = annotations.pop('return', None)\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        bound = sig.bind(*args, **kwargs)\n        errors = []\n\n        # Enforce argument checks\n        for name, validator in annotations.items():\n            try:\n                validator.check(bound.arguments[name])\n            except Exception as e:\n                errors.append(f'  {name}: {e}')\n\n        if errors:\n            raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n        result = func(*args, **kwargs)\n\n        # Enforce return check (if any)\n        if retcheck:\n            try:\n                retcheck.check(result)\n            except Exception as e:\n                raise TypeError(f'Bad return: {e}') from None\n        return result\n\n    return wrapper\n\ndef enforce(**annotations):\n    retcheck = annotations.pop('return_', None)\n\n    def decorate(func):\n        sig = signature(func)\n\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            bound = sig.bind(*args, **kwargs)\n            errors = []\n\n            # Enforce argument checks\n            for name, validator in annotations.items():\n                try:\n                    validator.check(bound.arguments[name])\n                except Exception as e:\n                    errors.append(f'    {name}: {e}')\n\n            if errors:\n                raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n            result = func(*args, **kwargs)\n\n            if retcheck:\n                try:\n                    retcheck.check(result)\n                except Exception as e:\n                    raise TypeError(f'Bad return: {e}') from None\n            return result\n        return wrapper\n    return decorate\n\n# Examples\nif __name__ == '__main__':\n    @validated\n    def add(x:Integer, y:Integer) -> Integer:\n        return x + y\n\n    @validated\n    def div(x:Integer, y:Integer) -> Integer:\n        return x / y\n\n    @enforce(x=Integer, y=Integer)\n    def sub(x, y):\n        return x - y\n\n    class Stock:\n        name = NonEmptyString()\n        shares = PositiveInteger()\n        price = PositiveFloat()\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        @validated\n        def sell(self, nshares:PositiveInteger):\n            self.shares -= nshares\n\n    \n\n    \n"
  },
  {
    "path": "Solutions/9_3/stock.py",
    "content": "# stock.py\n\nfrom structly import *\n\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n    \n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares: PositiveInteger):\n        self.shares -= nshares\n\nif __name__ == '__main__':\n    portfolio = read_csv_as_instances('../../Data/portfolio.csv', Stock)\n    formatter = create_formatter('text')\n    print_table(portfolio, ['name','shares','price'], formatter)\n\n"
  },
  {
    "path": "Solutions/9_3/structly/__init__.py",
    "content": "# structly/__init__.py\n\nfrom .structure import *\nfrom .reader import *\nfrom .tableformat import *\n\n__all__ = [  *structure.__all__,\n            *reader.__all__,\n            *tableformat.__all__ ]\n"
  },
  {
    "path": "Solutions/9_3/structly/reader.py",
    "content": "# reader.py\n\nimport csv\nimport logging\n\nlog = logging.getLogger(__name__)\n\ndef convert_csv(lines, converter, *, headers=None):\n    rows = csv.reader(lines)\n    if headers is None:\n        headers = next(rows)\n\n    records = []\n    for rowno, row in enumerate(rows, start=1):\n        try:\n            records.append(converter(headers, row))\n        except ValueError as e:\n            log.warning('Row %s: Bad row: %s', rowno, row)\n            log.debug('Row %s: Reason: %s', rowno, row)\n    return records\n\ndef csv_as_dicts(lines, types, *, headers=None):\n    return convert_csv(lines, \n                       lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) })\n\ndef csv_as_instances(lines, cls, *, headers=None):\n    return convert_csv(lines,\n                       lambda headers, row: cls.from_row(row))\n\ndef read_csv_as_dicts(filename, types, *, headers=None):\n    '''\n    Read CSV data into a list of dictionaries with optional type conversion\n    '''\n    with open(filename) as file:\n        return csv_as_dicts(file, types, headers=headers)\n\ndef read_csv_as_instances(filename, cls, *, headers=None):\n    '''\n    Read CSV data into a list of instances\n    '''\n    with open(filename) as file:\n        return csv_as_instances(file, cls, headers=headers)\n\n"
  },
  {
    "path": "Solutions/9_3/structly/structure.py",
    "content": "# structure.py\n\n__all__ = [ 'Structure' ]\n\nfrom .validate import Validator, validated\nfrom collections import ChainMap\n\nclass StructureMeta(type):\n    @classmethod\n    def __prepare__(meta, clsname, bases):\n        return ChainMap({}, Validator.validators)\n        \n    @staticmethod\n    def __new__(meta, name, bases, methods):\n        methods = methods.maps[0]\n        return super().__new__(meta, name, bases, methods)\n\nclass Structure(metaclass=StructureMeta):\n    _fields = ()\n    _types = ()\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    def __iter__(self):\n        for name in self._fields:\n            yield getattr(self, name)\n\n    def __eq__(self, other):\n        return isinstance(other, type(self)) and tuple(self) == tuple(other)\n\n    @classmethod\n    def from_row(cls, row):\n        rowdata = [ func(val) for func, val in zip(cls._types, row) ]\n        return cls(*rowdata)\n\n    @classmethod\n    def create_init(cls):\n        '''\n        Create an __init__ method from _fields\n        '''\n        args = ','.join(cls._fields)\n        code = f'def __init__(self, {args}):\\n'\n        for name in cls._fields:\n            code += f'    self.{name} = {name}\\n'\n        locs = { }\n        exec(code, locs)\n        cls.__init__ = locs['__init__']\n\n    @classmethod\n    def __init_subclass__(cls):\n        # Apply the validated decorator to subclasses\n        validate_attributes(cls)\n\ndef validate_attributes(cls):\n    '''\n    Class decorator that scans a class definition for Validators\n    and builds a _fields variable that captures their definition order.\n    '''\n    validators = []\n    for name, val in vars(cls).items():\n        if isinstance(val, Validator):\n            validators.append(val)\n\n        # Apply validated decorator to any callable with annotations\n        elif callable(val) and val.__annotations__:\n            setattr(cls, name, validated(val))\n\n    # Collect all of the field names\n    cls._fields = tuple([v.name for v in validators])\n\n    # Collect type conversions. The lambda x:x is an identity\n    # function that's used in case no expected_type is found.\n    cls._types = tuple([ getattr(v, 'expected_type', lambda x: x)\n                   for v in validators ])\n\n    # Create the __init__ method\n    if cls._fields:\n        cls.create_init()\n\n    \n    return cls\n\ndef typed_structure(clsname, **validators):\n    cls = type(clsname, (Structure,), validators)\n    return cls\n"
  },
  {
    "path": "Solutions/9_3/structly/tableformat/__init__.py",
    "content": "# __init__.py\n\nfrom .formatter import print_table, create_formatter\n\n__all__ = [ 'print_table', 'create_formatter' ]\n"
  },
  {
    "path": "Solutions/9_3/structly/tableformat/formats/__init__.py",
    "content": "# formats/__init__.py\n\n"
  },
  {
    "path": "Solutions/9_3/structly/tableformat/formats/csv.py",
    "content": "# csv.py\n\nfrom ..formatter import TableFormatter\n\nclass CSVTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(str(d) for d in rowdata))\n"
  },
  {
    "path": "Solutions/9_3/structly/tableformat/formats/html.py",
    "content": "# html.py\n\nfrom ..formatter import TableFormatter\n\nclass HTMLTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print('<tr>', end=' ')\n        for h in headers:\n            print('<th>%s</th>' % h, end=' ')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end=' ')\n        for d in rowdata:\n            print('<td>%s</td>' % d, end=' ')\n        print('</tr>')\n"
  },
  {
    "path": "Solutions/9_3/structly/tableformat/formats/text.py",
    "content": "# text.py\n\nfrom ..formatter import TableFormatter\n\nclass TextTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(' '.join('%10s' % h for h in headers))\n        print(('-'*10 + ' ')*len(headers))\n    \n    def row(self, rowdata):\n        print(' '.join('%10s' % d for d in rowdata))\n"
  },
  {
    "path": "Solutions/9_3/structly/tableformat/formatter.py",
    "content": "# tableformat.py\nfrom abc import ABC, abstractmethod\n\ndef print_table(records, fields, formatter):\n    if not isinstance(formatter, TableFormatter):\n        raise RuntimeError('Expected a TableFormatter')\n\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n\nclass TableFormatter(ABC):\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n\nfrom .formats.text import TextTableFormatter\nfrom .formats.csv import CSVTableFormatter\nfrom .formats.html import HTMLTableFormatter\n\nclass ColumnFormatMixin:\n    formats = []\n    def row(self, rowdata):\n        rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)]\n        super().row(rowdata)\n\nclass UpperHeadersMixin:\n    def headings(self, headers):\n        super().headings([h.upper() for h in headers])\n\ndef create_formatter(name, column_formats=None, upper_headers=False):\n    if name == 'text':\n        formatter_cls = TextTableFormatter\n    elif name == 'csv':\n        formatter_cls = CSVTableFormatter\n    elif name == 'html':\n        formatter_cls = HTMLTableFormatter\n    else:\n        raise RuntimeError('Unknown format %s' % name)\n\n    if column_formats:\n        class formatter_cls(ColumnFormatMixin, formatter_cls):\n              formats = column_formats\n\n    if upper_headers:\n        class formatter_cls(UpperHeadersMixin, formatter_cls):\n            pass\n\n    return formatter_cls()\n\n\n\n"
  },
  {
    "path": "Solutions/9_3/structly/validate.py",
    "content": "# validate.py\n\nclass Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance, value):\n        instance.__dict__[self.name] = self.check(value)\n\n    # Collect all derived classes into a dict\n    validators = { }\n    @classmethod\n    def __init_subclass__(cls):\n        cls.validators[cls.__name__] = cls\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        return super().check(value)\n\n_typed_classes = [\n    ('Integer', int),\n    ('Float', float),\n    ('String', str) ]\n\nglobals().update((name, type(name, (Typed,), {'expected_type':ty}))\n                 for name, ty in _typed_classes)\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\nfrom inspect import signature\nfrom functools import wraps\n\ndef isvalidator(item):\n    return isinstance(item, type) and issubclass(item, Validator)\n\ndef validated(func):\n    sig = signature(func)\n\n    # Gather the function annotations\n    annotations = { name:val for name, val in func.__annotations__.items()\n                    if isvalidator(val) }\n\n    # Get the return annotation (if any)\n    retcheck = annotations.pop('return', None)\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        bound = sig.bind(*args, **kwargs)\n        errors = []\n\n        # Enforce argument checks\n        for name, validator in annotations.items():\n            try:\n                validator.check(bound.arguments[name])\n            except Exception as e:\n                errors.append(f'  {name}: {e}')\n\n        if errors:\n            raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n        result = func(*args, **kwargs)\n\n        # Enforce return check (if any)\n        if retcheck:\n            try:\n                retcheck.check(result)\n            except Exception as e:\n                raise TypeError(f'Bad return: {e}') from None\n        return result\n\n    return wrapper\n\ndef enforce(**annotations):\n    retcheck = annotations.pop('return_', None)\n\n    def decorate(func):\n        sig = signature(func)\n\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            bound = sig.bind(*args, **kwargs)\n            errors = []\n\n            # Enforce argument checks\n            for name, validator in annotations.items():\n                try:\n                    validator.check(bound.arguments[name])\n                except Exception as e:\n                    errors.append(f'    {name}: {e}')\n\n            if errors:\n                raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n            result = func(*args, **kwargs)\n\n            if retcheck:\n                try:\n                    retcheck.check(result)\n                except Exception as e:\n                    raise TypeError(f'Bad return: {e}') from None\n            return result\n        return wrapper\n    return decorate\n\n# Examples\nif __name__ == '__main__':\n    @validated\n    def add(x:Integer, y:Integer) -> Integer:\n        return x + y\n\n    @validated\n    def div(x:Integer, y:Integer) -> Integer:\n        return x / y\n\n    @enforce(x=Integer, y=Integer)\n    def sub(x, y):\n        return x - y\n\n    class Stock:\n        name = NonEmptyString()\n        shares = PositiveInteger()\n        price = PositiveFloat()\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        @validated\n        def sell(self, nshares:PositiveInteger):\n            self.shares -= nshares\n\n    \n\n    \n"
  },
  {
    "path": "Solutions/9_4/stock.py",
    "content": "# stock.py\n\nfrom structly import *\n\nclass Stock(Structure):\n    name = String()\n    shares = PositiveInteger()\n    price = PositiveFloat()\n    \n    @property\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares: PositiveInteger):\n        self.shares -= nshares\n\nif __name__ == '__main__':\n    portfolio = read_csv_as_instances('../../Data/portfolio.csv', Stock)\n    formatter = create_formatter('text')\n    print_table(portfolio, ['name','shares','price'], formatter)\n\n"
  },
  {
    "path": "Solutions/9_4/structly/__init__.py",
    "content": "# structly/__init__.py\n\nfrom .structure import *\nfrom .reader import *\nfrom .tableformat import *\n\n__all__ = [ *structure.__all__,\n            *reader.__all__,\n            *tableformat.__all__ ]\n"
  },
  {
    "path": "Solutions/9_4/structly/reader.py",
    "content": "# reader.py\n\nimport csv\nimport logging\n\nlog = logging.getLogger(__name__)\n\ndef convert_csv(lines, converter, *, headers=None):\n    rows = csv.reader(lines)\n    if headers is None:\n        headers = next(rows)\n\n    records = []\n    for rowno, row in enumerate(rows, start=1):\n        try:\n            records.append(converter(headers, row))\n        except ValueError as e:\n            log.warning('Row %s: Bad row: %s', rowno, row)\n            log.debug('Row %s: Reason: %s', rowno, row)\n    return records\n\ndef csv_as_dicts(lines, types, *, headers=None):\n    return convert_csv(lines, \n                       lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row) })\n\ndef csv_as_instances(lines, cls, *, headers=None):\n    return convert_csv(lines,\n                       lambda headers, row: cls.from_row(row))\n\ndef read_csv_as_dicts(filename, types, *, headers=None):\n    '''\n    Read CSV data into a list of dictionaries with optional type conversion\n    '''\n    with open(filename) as file:\n        return csv_as_dicts(file, types, headers=headers)\n\ndef read_csv_as_instances(filename, cls, *, headers=None):\n    '''\n    Read CSV data into a list of instances\n    '''\n    with open(filename) as file:\n        return csv_as_instances(file, cls, headers=headers)\n\n"
  },
  {
    "path": "Solutions/9_4/structly/structure.py",
    "content": "# structure.py\n\n__all__ = [ 'Structure' ]\n\nfrom .validate import Validator, validated\nfrom collections import ChainMap\n\nclass StructureMeta(type):\n    @classmethod\n    def __prepare__(meta, clsname, bases):\n        return ChainMap({}, Validator.validators)\n        \n    @staticmethod\n    def __new__(meta, name, bases, methods):\n        methods = methods.maps[0]\n        return super().__new__(meta, name, bases, methods)\n\nclass Structure(metaclass=StructureMeta):\n    _fields = ()\n    _types = ()\n\n    def __setattr__(self, name, value):\n        if name.startswith('_') or name in self._fields:\n            super().__setattr__(name, value)\n        else:\n            raise AttributeError('No attribute %s' % name)\n\n    def __repr__(self):\n        return '%s(%s)' % (type(self).__name__,\n                           ', '.join(repr(getattr(self, name)) for name in self._fields))\n\n    def __iter__(self):\n        for name in self._fields:\n            yield getattr(self, name)\n\n    def __eq__(self, other):\n        return isinstance(other, type(self)) and tuple(self) == tuple(other)\n\n    @classmethod\n    def from_row(cls, row):\n        rowdata = [ func(val) for func, val in zip(cls._types, row) ]\n        return cls(*rowdata)\n\n    @classmethod\n    def create_init(cls):\n        '''\n        Create an __init__ method from _fields\n        '''\n        args = ','.join(cls._fields)\n        code = f'def __init__(self, {args}):\\n'\n        for name in cls._fields:\n            code += f'    self.{name} = {name}\\n'\n        locs = { }\n        exec(code, locs)\n        cls.__init__ = locs['__init__']\n\n    @classmethod\n    def __init_subclass__(cls):\n        # Apply the validated decorator to subclasses\n        validate_attributes(cls)\n\ndef validate_attributes(cls):\n    '''\n    Class decorator that scans a class definition for Validators\n    and builds a _fields variable that captures their definition order.\n    '''\n    validators = []\n    for name, val in vars(cls).items():\n        if isinstance(val, Validator):\n            validators.append(val)\n\n        # Apply validated decorator to any callable with annotations\n        elif callable(val) and val.__annotations__:\n            setattr(cls, name, validated(val))\n\n    # Collect all of the field names\n    cls._fields = tuple([v.name for v in validators])\n\n    # Collect type conversions. The lambda x:x is an identity\n    # function that's used in case no expected_type is found.\n    cls._types = tuple([ getattr(v, 'expected_type', lambda x: x)\n                   for v in validators ])\n\n    # Create the __init__ method\n    if cls._fields:\n        cls.create_init()\n\n    \n    return cls\n\ndef typed_structure(clsname, **validators):\n    cls = type(clsname, (Structure,), validators)\n    return cls\n"
  },
  {
    "path": "Solutions/9_4/structly/tableformat/__init__.py",
    "content": "# __init__.py\n\nfrom .formatter import print_table, create_formatter\n\n__all__ = [ 'print_table', 'create_formatter' ]\n"
  },
  {
    "path": "Solutions/9_4/structly/tableformat/formats/__init__.py",
    "content": "# formats/__init__.py\n\n"
  },
  {
    "path": "Solutions/9_4/structly/tableformat/formats/csv.py",
    "content": "# csv.py\n\nfrom ..formatter import TableFormatter\n\nclass CSVTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(str(d) for d in rowdata))\n"
  },
  {
    "path": "Solutions/9_4/structly/tableformat/formats/html.py",
    "content": "# html.py\n\nfrom ..formatter import TableFormatter\n\nclass HTMLTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print('<tr>', end=' ')\n        for h in headers:\n            print('<th>%s</th>' % h, end=' ')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end=' ')\n        for d in rowdata:\n            print('<td>%s</td>' % d, end=' ')\n        print('</tr>')\n"
  },
  {
    "path": "Solutions/9_4/structly/tableformat/formats/text.py",
    "content": "# text.py\n\nfrom ..formatter import TableFormatter\n\nclass TextTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print(' '.join('%10s' % h for h in headers))\n        print(('-'*10 + ' ')*len(headers))\n    \n    def row(self, rowdata):\n        print(' '.join('%10s' % d for d in rowdata))\n"
  },
  {
    "path": "Solutions/9_4/structly/tableformat/formats/tsv.py",
    "content": "# tsv.py\n\nfrom ..formatter import TableFormatter\n\nclass TSVTableFormatter(TableFormatter):\n    def headings(self, headers):\n        print('\\t'.join(headers))\n    def row(self, rowdata):\n        print('\\t'.join(str(d) for d in rowdata))\n"
  },
  {
    "path": "Solutions/9_4/structly/tableformat/formatter.py",
    "content": "# formatter.py\nfrom abc import ABC, abstractmethod\n\ndef print_table(records, fields, formatter):\n    if not isinstance(formatter, TableFormatter):\n        raise RuntimeError('Expected a TableFormatter')\n\n    formatter.headings(fields)\n    for r in records:\n        rowdata = [getattr(r, fieldname) for fieldname in fields]\n        formatter.row(rowdata)\n\nclass TableFormatter(ABC):\n    _formats = { }\n\n    @classmethod\n    def __init_subclass__(cls):\n        name = cls.__module__.split('.')[-1]\n        TableFormatter._formats[name] = cls\n\n    @abstractmethod\n    def headings(self, headers):\n        pass\n\n    @abstractmethod\n    def row(self, rowdata):\n        pass\n\nclass ColumnFormatMixin:\n    formats = []\n    def row(self, rowdata):\n        rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)]\n        super().row(rowdata)\n\nclass UpperHeadersMixin:\n    def headings(self, headers):\n        super().headings([h.upper() for h in headers])\n\ndef create_formatter(name, column_formats=None, upper_headers=False):\n    if name not in TableFormatter._formats:\n        __import__(f'{__package__}.formats.{name}')\n        \n    formatter_cls = TableFormatter._formats.get(name)\n    if not formatter_cls:\n        raise RuntimeError('Unknown format %s' % name)\n\n    if column_formats:\n        class formatter_cls(ColumnFormatMixin, formatter_cls):\n              formats = column_formats\n\n    if upper_headers:\n        class formatter_cls(UpperHeadersMixin, formatter_cls):\n            pass\n\n    return formatter_cls()\n\n\n\n"
  },
  {
    "path": "Solutions/9_4/structly/validate.py",
    "content": "# validate.py\n\nclass Validator:\n    def __init__(self, name=None):\n        self.name = name\n\n    def __set_name__(self, cls, name):\n        self.name = name\n\n    @classmethod\n    def check(cls, value):\n        return value\n\n    def __set__(self, instance, value):\n        instance.__dict__[self.name] = self.check(value)\n\n    # Collect all derived classes into a dict\n    validators = { }\n    @classmethod\n    def __init_subclass__(cls):\n        cls.validators[cls.__name__] = cls\n\nclass Typed(Validator):\n    expected_type = object\n    @classmethod\n    def check(cls, value):\n        if not isinstance(value, cls.expected_type):\n            raise TypeError(f'expected {cls.expected_type}')\n        return super().check(value)\n\n_typed_classes = [\n    ('Integer', int),\n    ('Float', float),\n    ('String', str) ]\n\nglobals().update((name, type(name, (Typed,), {'expected_type':ty}))\n                 for name, ty in _typed_classes)\n\nclass Positive(Validator):\n    @classmethod\n    def check(cls, value):\n        if value < 0:\n            raise ValueError('must be >= 0')\n        return super().check(value)\n\nclass NonEmpty(Validator):\n    @classmethod\n    def check(cls, value):\n        if len(value) == 0:\n            raise ValueError('must be non-empty')\n        return super().check(value)\n\nclass PositiveInteger(Integer, Positive):\n    pass\n\nclass PositiveFloat(Float, Positive):\n    pass\n\nclass NonEmptyString(String, NonEmpty):\n    pass\n\nfrom inspect import signature\nfrom functools import wraps\n\ndef isvalidator(item):\n    return isinstance(item, type) and issubclass(item, Validator)\n\ndef validated(func):\n    sig = signature(func)\n\n    # Gather the function annotations\n    annotations = { name:val for name, val in func.__annotations__.items()\n                    if isvalidator(val) }\n\n    # Get the return annotation (if any)\n    retcheck = annotations.pop('return', None)\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        bound = sig.bind(*args, **kwargs)\n        errors = []\n\n        # Enforce argument checks\n        for name, validator in annotations.items():\n            try:\n                validator.check(bound.arguments[name])\n            except Exception as e:\n                errors.append(f'  {name}: {e}')\n\n        if errors:\n            raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n        result = func(*args, **kwargs)\n\n        # Enforce return check (if any)\n        if retcheck:\n            try:\n                retcheck.check(result)\n            except Exception as e:\n                raise TypeError(f'Bad return: {e}') from None\n        return result\n\n    return wrapper\n\ndef enforce(**annotations):\n    retcheck = annotations.pop('return_', None)\n\n    def decorate(func):\n        sig = signature(func)\n\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            bound = sig.bind(*args, **kwargs)\n            errors = []\n\n            # Enforce argument checks\n            for name, validator in annotations.items():\n                try:\n                    validator.check(bound.arguments[name])\n                except Exception as e:\n                    errors.append(f'    {name}: {e}')\n\n            if errors:\n                raise TypeError('Bad Arguments\\n' + '\\n'.join(errors))\n\n            result = func(*args, **kwargs)\n\n            if retcheck:\n                try:\n                    retcheck.check(result)\n                except Exception as e:\n                    raise TypeError(f'Bad return: {e}') from None\n            return result\n        return wrapper\n    return decorate\n\n# Examples\nif __name__ == '__main__':\n    @validated\n    def add(x:Integer, y:Integer) -> Integer:\n        return x + y\n\n    @validated\n    def div(x:Integer, y:Integer) -> Integer:\n        return x / y\n\n    @enforce(x=Integer, y=Integer)\n    def sub(x, y):\n        return x - y\n\n    class Stock:\n        name = NonEmptyString()\n        shares = PositiveInteger()\n        price = PositiveFloat()\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n        def __repr__(self):\n            return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n        @property\n        def cost(self):\n            return self.shares * self.price\n\n        @validated\n        def sell(self, nshares:PositiveInteger):\n            self.shares -= nshares\n\n    \n\n    \n"
  },
  {
    "path": "Solutions/README.md",
    "content": "# Solutions\n\nThis directory contains fully worked out solution code for every\nexercise.  The code is written to be runnable from this directory.\nWhen taking the course, I would encourage you to try and come up\nwith your own solution first.   However, if you're stuck or if\neverything has become broken, you can come here to get a fresh\ncopy of code to work with.\n\n\n\n\n\n"
  }
]