├── .gitignore ├── LICENSE.md ├── Notes ├── 00_Setup.md ├── 01_Introduction │ ├── 00_Overview.md │ ├── 01_Python.md │ ├── 02_Hello_world.md │ ├── 03_Numbers.md │ ├── 04_Strings.md │ ├── 05_Lists.md │ ├── 06_Files.md │ └── 07_Functions.md ├── 02_Working_with_data │ ├── 00_Overview.md │ ├── 01_Datatypes.md │ ├── 02_Containers.md │ ├── 03_Formatting.md │ ├── 04_Sequences.md │ ├── 05_Collections.md │ ├── 06_List_comprehension.md │ ├── 07_Objects.md │ ├── references.png │ └── shallow.png ├── 03_Program_organization │ ├── 00_Overview.md │ ├── 01_Script.md │ ├── 02_More_functions.md │ ├── 03_Error_checking.md │ ├── 04_Modules.md │ ├── 05_Main_module.md │ └── 06_Design_discussion.md ├── 04_Classes_objects │ ├── 00_Overview.md │ ├── 01_Class.md │ ├── 02_Inheritance.md │ ├── 03_Special_methods.md │ └── 04_Defining_exceptions.md ├── 05_Object_model │ ├── 00_Overview.md │ ├── 01_Dicts_revisited.md │ └── 02_Classes_encapsulation.md ├── 06_Generators │ ├── 00_Overview.md │ ├── 01_Iteration_protocol.md │ ├── 02_Customizing_iteration.md │ ├── 03_Producers_consumers.md │ └── 04_More_generators.md ├── 07_Advanced_Topics │ ├── 00_Overview.md │ ├── 01_Variable_arguments.md │ ├── 02_Anonymous_function.md │ ├── 03_Returning_functions.md │ ├── 04_Function_decorators.md │ └── 05_Decorated_methods.md ├── 08_Testing_debugging │ ├── 00_Overview.md │ ├── 01_Testing.md │ ├── 02_Logging.md │ └── 03_Debugging.md ├── 09_Packages │ ├── 00_Overview.md │ ├── 01_Packages.md │ ├── 02_Third_party.md │ ├── 03_Distribution.md │ └── TheEnd.md ├── Contents.md └── InstructorNotes.md ├── README.md ├── Solutions ├── 1_10 │ └── mortgage.py ├── 1_27 │ └── pcost.py ├── 1_33 │ └── pcost.py ├── 1_5 │ └── bounce.py ├── 2_11 │ └── report.py ├── 2_16 │ ├── pcost.py │ └── report.py ├── 2_7 │ └── report.py ├── 3_10 │ └── fileparse.py ├── 3_14 │ ├── fileparse.py │ ├── pcost.py │ └── report.py ├── 3_16 │ ├── fileparse.py │ ├── pcost.py │ └── report.py ├── 3_18 │ ├── fileparse.py │ ├── pcost.py │ └── report.py ├── 3_2 │ └── report.py ├── 3_7 │ └── fileparse.py ├── 4_10 │ ├── fileparse.py │ ├── pcost.py │ ├── report.py │ ├── stock.py │ └── tableformat.py ├── 4_4 │ ├── fileparse.py │ ├── pcost.py │ ├── report.py │ └── stock.py ├── 5_8 │ ├── fileparse.py │ ├── pcost.py │ ├── report.py │ ├── stock.py │ └── tableformat.py ├── 6_12 │ ├── fileparse.py │ ├── follow.py │ ├── pcost.py │ ├── portfolio.py │ ├── report.py │ ├── stock.py │ ├── tableformat.py │ └── ticker.py ├── 6_15 │ ├── fileparse.py │ ├── follow.py │ ├── pcost.py │ ├── portfolio.py │ ├── report.py │ ├── stock.py │ ├── tableformat.py │ └── ticker.py ├── 6_3 │ ├── fileparse.py │ ├── pcost.py │ ├── portfolio.py │ ├── report.py │ ├── stock.py │ └── tableformat.py ├── 6_7 │ ├── fileparse.py │ ├── follow.py │ ├── pcost.py │ ├── portfolio.py │ ├── report.py │ ├── stock.py │ └── tableformat.py ├── 7_10 │ └── timethis.py ├── 7_11 │ ├── fileparse.py │ ├── follow.py │ ├── pcost.py │ ├── portfolio.py │ ├── report.py │ ├── stock.py │ ├── tableformat.py │ ├── ticker.py │ ├── timethis.py │ └── typedproperty.py ├── 7_4 │ ├── fileparse.py │ ├── follow.py │ ├── pcost.py │ ├── portfolio.py │ ├── report.py │ ├── stock.py │ ├── tableformat.py │ └── ticker.py ├── 7_9 │ ├── fileparse.py │ ├── follow.py │ ├── pcost.py │ ├── portfolio.py │ ├── report.py │ ├── stock.py │ ├── tableformat.py │ ├── ticker.py │ └── typedproperty.py ├── 8_1 │ ├── fileparse.py │ ├── follow.py │ ├── pcost.py │ ├── portfolio.py │ ├── report.py │ ├── stock.py │ ├── tableformat.py │ ├── test_stock.py │ ├── ticker.py │ ├── timethis.py │ └── typedproperty.py ├── 8_2 │ ├── fileparse.py │ ├── follow.py │ ├── pcost.py │ ├── portfolio.py │ ├── report.py │ ├── stock.py │ ├── tableformat.py │ ├── test_stock.py │ ├── ticker.py │ ├── timethis.py │ └── typedproperty.py ├── 9_3 │ └── porty-app │ │ ├── README.txt │ │ ├── portfolio.csv │ │ ├── porty │ │ ├── __init__.py │ │ ├── fileparse.py │ │ ├── follow.py │ │ ├── pcost.py │ │ ├── portfolio.py │ │ ├── report.py │ │ ├── stock.py │ │ ├── tableformat.py │ │ ├── test_stock.py │ │ ├── ticker.py │ │ └── typedproperty.py │ │ ├── prices.csv │ │ └── print-report.py ├── 9_5 │ └── porty-app │ │ ├── MANIFEST.in │ │ ├── README.txt │ │ ├── portfolio.csv │ │ ├── porty │ │ ├── __init__.py │ │ ├── fileparse.py │ │ ├── follow.py │ │ ├── pcost.py │ │ ├── portfolio.py │ │ ├── report.py │ │ ├── stock.py │ │ ├── tableformat.py │ │ ├── test_stock.py │ │ ├── ticker.py │ │ └── typedproperty.py │ │ ├── prices.csv │ │ ├── print-report.py │ │ └── setup.py └── README.md ├── Work ├── Data │ ├── dowstocks.csv │ ├── missing.csv │ ├── portfolio.csv │ ├── portfolio.csv.gz │ ├── portfolio.dat │ ├── portfolio2.csv │ ├── portfolioblank.csv │ ├── portfoliodate.csv │ ├── prices.csv │ └── stocksim.py ├── README.md ├── bounce.py ├── fileparse.py ├── mortgage.py ├── pcost.py └── report.py ├── _config.yml └── _layouts └── default.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /Notes/01_Introduction/00_Overview.md: -------------------------------------------------------------------------------- 1 | [Contents](../Contents.md) \| [Next (2 Working With Data)](../02_Working_with_data/00_Overview.md) 2 | 3 | ## 1. Introduction to Python 4 | 5 | The goal of this first section is to introduce some Python basics from 6 | the ground up. Starting with nothing, you'll learn how to edit, run, 7 | and debug small programs. Ultimately, you'll write a short script that 8 | reads a CSV data file and performs a simple calculation. 9 | 10 | * [1.1 Introducing Python](01_Python.md) 11 | * [1.2 A First Program](02_Hello_world.md) 12 | * [1.3 Numbers](03_Numbers.md) 13 | * [1.4 Strings](04_Strings.md) 14 | * [1.5 Lists](05_Lists.md) 15 | * [1.6 Files](06_Files.md) 16 | * [1.7 Functions](07_Functions.md) 17 | 18 | [Contents](../Contents.md) \| [Next (2 Working With Data)](../02_Working_with_data/00_Overview.md) 19 | -------------------------------------------------------------------------------- /Notes/02_Working_with_data/00_Overview.md: -------------------------------------------------------------------------------- 1 | [Contents](../Contents.md) \| [Prev (1 Introduction to Python)](../01_Introduction/00_Overview.md) \| [Next (3 Program Organization)](../03_Program_organization/00_Overview.md) 2 | 3 | # 2. Working With Data 4 | 5 | To write useful programs, you need to be able to work with data. 6 | This section introduces Python's core data structures of tuples, 7 | lists, sets, and dictionaries and discusses common data handling 8 | idioms. The last part of this section dives a little deeper 9 | into Python's underlying object model. 10 | 11 | * [2.1 Datatypes and Data Structures](01_Datatypes.md) 12 | * [2.2 Containers](02_Containers.md) 13 | * [2.3 Formatted Output](03_Formatting.md) 14 | * [2.4 Sequences](04_Sequences.md) 15 | * [2.5 Collections module](05_Collections.md) 16 | * [2.6 List comprehensions](06_List_comprehension.md) 17 | * [2.7 Object model](07_Objects.md) 18 | 19 | [Contents](../Contents.md) \| [Prev (1 Introduction to Python)](../01_Introduction/00_Overview.md) \| [Next (3 Program Organization)](../03_Program_organization/00_Overview.md) 20 | -------------------------------------------------------------------------------- /Notes/02_Working_with_data/references.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabeaz-course/practical-python/3c00ff81b7b61eae7ef79a760d60687f9fcf0a81/Notes/02_Working_with_data/references.png -------------------------------------------------------------------------------- /Notes/02_Working_with_data/shallow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabeaz-course/practical-python/3c00ff81b7b61eae7ef79a760d60687f9fcf0a81/Notes/02_Working_with_data/shallow.png -------------------------------------------------------------------------------- /Notes/03_Program_organization/00_Overview.md: -------------------------------------------------------------------------------- 1 | [Contents](../Contents.md) \| [Prev (2 Working With Data)](../02_Working_with_data/00_Overview.md) \| [Next (4 Classes and Objects)](../04_Classes_objects/00_Overview.md) 2 | 3 | # 3. Program Organization 4 | 5 | So far, we've learned some Python basics and have written some short scripts. 6 | However, as you start to write larger programs, you'll want to get organized. 7 | This section dives into greater details on writing functions, handling errors, 8 | and introduces modules. By the end you should be able to write programs 9 | that are subdivided into functions across multiple files. We'll also give 10 | some useful code templates for writing more useful scripts. 11 | 12 | * [3.1 Functions and Script Writing](01_Script.md) 13 | * [3.2 More Detail on Functions](02_More_functions.md) 14 | * [3.3 Exception Handling](03_Error_checking.md) 15 | * [3.4 Modules](04_Modules.md) 16 | * [3.5 Main module](05_Main_module.md) 17 | * [3.6 Design Discussion about Embracing Flexibility](06_Design_discussion.md) 18 | 19 | [Contents](../Contents.md) \| [Prev (2 Working With Data)](../02_Working_with_data/00_Overview.md) \| [Next (4 Classes and Objects)](../04_Classes_objects/00_Overview.md) 20 | 21 | -------------------------------------------------------------------------------- /Notes/04_Classes_objects/00_Overview.md: -------------------------------------------------------------------------------- 1 | [Contents](../Contents.md) \| [Prev (3 Program Organization)](../03_Program_organization/00_Overview.md) \| [Next (5 Inner Workings of Python Objects)](../05_Object_model/00_Overview.md) 2 | 3 | # 4. Classes and Objects 4 | 5 | So far, our programs have only used built-in Python datatypes. In 6 | this section, we introduce the concept of classes and objects. You'll 7 | learn about the `class` statement that allows you to make new objects. 8 | We'll also introduce the concept of inheritance, a tool that is commonly 9 | use to build extensible programs. Finally, we'll look at a few other 10 | features of classes including special methods, dynamic attribute lookup, 11 | and defining new exceptions. 12 | 13 | * [4.1 Introducing Classes](01_Class.md) 14 | * [4.2 Inheritance](02_Inheritance.md) 15 | * [4.3 Special Methods](03_Special_methods.md) 16 | * [4.4 Defining new Exception](04_Defining_exceptions.md) 17 | 18 | [Contents](../Contents.md) \| [Prev (3 Program Organization)](../03_Program_organization/00_Overview.md) \| [Next (5 Inner Workings of Python Objects)](../05_Object_model/00_Overview.md) 19 | -------------------------------------------------------------------------------- /Notes/04_Classes_objects/04_Defining_exceptions.md: -------------------------------------------------------------------------------- 1 | [Contents](../Contents.md) \| [Previous (4.3 Special methods)](03_Special_methods.md) \| [Next (5 Object Model)](../05_Object_model/00_Overview.md) 2 | 3 | # 4.4 Defining Exceptions 4 | 5 | User defined exceptions are defined by classes. 6 | 7 | ```python 8 | class NetworkError(Exception): 9 | pass 10 | ``` 11 | 12 | **Exceptions always inherit from `Exception`.** 13 | 14 | Usually they are empty classes. Use `pass` for the body. 15 | 16 | You can also make a hierarchy of your exceptions. 17 | 18 | ```python 19 | class AuthenticationError(NetworkError): 20 | pass 21 | 22 | class ProtocolError(NetworkError): 23 | pass 24 | ``` 25 | 26 | ## Exercises 27 | 28 | ### Exercise 4.11: Defining a custom exception 29 | 30 | It is often good practice for libraries to define their own exceptions. 31 | 32 | This makes it easier to distinguish between Python exceptions raised 33 | in response to common programming errors versus exceptions 34 | intentionally raised by a library to a signal a specific usage 35 | problem. 36 | 37 | Modify the `create_formatter()` function from the last exercise so 38 | that it raises a custom `FormatError` exception when the user provides 39 | a bad format name. 40 | 41 | For example: 42 | 43 | ```python 44 | >>> from tableformat import create_formatter 45 | >>> formatter = create_formatter('xls') 46 | Traceback (most recent call last): 47 | File "", line 1, in 48 | File "tableformat.py", line 71, in create_formatter 49 | raise FormatError('Unknown table format %s' % name) 50 | FormatError: Unknown table format xls 51 | >>> 52 | ``` 53 | 54 | [Contents](../Contents.md) \| [Previous (4.3 Special methods)](03_Special_methods.md) \| [Next (5 Object Model)](../05_Object_model/00_Overview.md) 55 | -------------------------------------------------------------------------------- /Notes/05_Object_model/00_Overview.md: -------------------------------------------------------------------------------- 1 | [Contents](../Contents.md) \| [Prev (4 Classes and Objects)](../04_Classes_objects/00_Overview.md) \| [Next (6 Generators)](../06_Generators/00_Overview.md) 2 | 3 | # 5. Inner Workings of Python Objects 4 | 5 | This section covers some of the inner workings of Python objects. 6 | Programmers coming from other programming languages often find 7 | Python's notion of classes lacking in features. For example, there is 8 | no notion of access-control (e.g., private, protected), the whole 9 | `self` argument feels weird, and frankly, working with objects 10 | sometimes feel like a "free for all." Maybe that's true, but we'll 11 | find out how it all works as well as some common programming idioms to 12 | better encapsulate the internals of objects. 13 | 14 | It's not necessary to worry about the inner details to be productive. 15 | However, most Python coders have a basic awareness of how classes 16 | work. So, that's why we're covering it. 17 | 18 | * [5.1 Dictionaries Revisited (Object Implementation)](01_Dicts_revisited.md) 19 | * [5.2 Encapsulation Techniques](02_Classes_encapsulation.md) 20 | 21 | [Contents](../Contents.md) \| [Prev (4 Classes and Objects)](../04_Classes_objects/00_Overview.md) \| [Next (6 Generators)](../06_Generators/00_Overview.md) 22 | 23 | -------------------------------------------------------------------------------- /Notes/06_Generators/00_Overview.md: -------------------------------------------------------------------------------- 1 | [Contents](../Contents.md) \| [Prev (5 Inner Workings of Python Objects)](../05_Object_model/00_Overview.md) \| [Next (7 Advanced Topics)](../07_Advanced_Topics/00_Overview.md) 2 | 3 | # 6. Generators 4 | 5 | Iteration (the `for`-loop) is one of the most common programming 6 | patterns in Python. Programs do a lot of iteration to process lists, 7 | read files, query databases, and more. One of the most powerful 8 | features of Python is the ability to customize and redefine iteration 9 | in the form of a so-called "generator function." This section 10 | introduces this topic. By the end, you'll write some programs that 11 | process some real-time streaming data in an interesting way. 12 | 13 | * [6.1 Iteration Protocol](01_Iteration_protocol.md) 14 | * [6.2 Customizing Iteration with Generators](02_Customizing_iteration.md) 15 | * [6.3 Producer/Consumer Problems and Workflows](03_Producers_consumers.md) 16 | * [6.4 Generator Expressions](04_More_generators.md) 17 | 18 | [Contents](../Contents.md) \| [Prev (5 Inner Workings of Python Objects)](../05_Object_model/00_Overview.md) \| [Next (7 Advanced Topics)](../07_Advanced_Topics/00_Overview.md) 19 | -------------------------------------------------------------------------------- /Notes/07_Advanced_Topics/00_Overview.md: -------------------------------------------------------------------------------- 1 | [Contents](../Contents.md) \| [Prev (6 Generators)](../06_Generators/00_Overview.md) \| [Next (8 Testing and Debugging)](../08_Testing_debugging/00_Overview.md) 2 | 3 | # 7. Advanced Topics 4 | 5 | In this section, we look at a small set of somewhat more advanced 6 | Python features that you might encounter in your day-to-day coding. 7 | Many of these topics could have been covered in earlier course 8 | sections, but weren't in order to spare you further head-explosion at 9 | the time. 10 | 11 | It should be emphasized that the topics in this section are only meant 12 | to serve as a very basic introduction to these ideas. You will need 13 | to seek more advanced material to fill out details. 14 | 15 | * [7.1 Variable argument functions](01_Variable_arguments.md) 16 | * [7.2 Anonymous functions and lambda](02_Anonymous_function.md) 17 | * [7.3 Returning function and closures](03_Returning_functions.md) 18 | * [7.4 Function decorators](04_Function_decorators.md) 19 | * [7.5 Static and class methods](05_Decorated_methods.md) 20 | 21 | [Contents](../Contents.md) \| [Prev (6 Generators)](../06_Generators/00_Overview.md) \| [Next (8 Testing and Debugging)](../08_Testing_debugging/00_Overview.md) 22 | -------------------------------------------------------------------------------- /Notes/08_Testing_debugging/00_Overview.md: -------------------------------------------------------------------------------- 1 | [Contents](../Contents.md) \| [Prev (7 Advanced Topics)](../07_Advanced_Topics/00_Overview.md) \| [Next (9 Packages)](../09_Packages/00_Overview.md) 2 | 3 | # 8. Testing and debugging 4 | 5 | This section introduces a few basic topics related to testing, 6 | logging, and debugging. 7 | 8 | * [8.1 Testing](01_Testing.md) 9 | * [8.2 Logging, error handling and diagnostics](02_Logging.md) 10 | * [8.3 Debugging](03_Debugging.md) 11 | 12 | [Contents](../Contents.md) \| [Prev (7 Advanced Topics)](../07_Advanced_Topics/00_Overview.md) \| [Next (9 Packages)](../09_Packages/00_Overview.md) 13 | -------------------------------------------------------------------------------- /Notes/09_Packages/00_Overview.md: -------------------------------------------------------------------------------- 1 | [Contents](../Contents.md) \| [Prev (8 Testing and Debugging)](../08_Testing_debugging/00_Overview.md) 2 | 3 | # 9 Packages 4 | 5 | We conclude the course with a few details on how to organize your code 6 | into a package structure. We'll also discuss the installation of 7 | third party packages and preparing to give your own code away to others. 8 | 9 | The subject of packaging is an ever-evolving, overly complex part of 10 | Python development. Rather than focus on specific tools, the main 11 | focus of this section is on some general code organization principles 12 | that will prove useful no matter what tools you later use to give code 13 | away or manage dependencies. 14 | 15 | * [9.1 Packages](01_Packages.md) 16 | * [9.2 Third Party Modules](02_Third_party.md) 17 | * [9.3 Giving your code to others](03_Distribution.md) 18 | 19 | [Contents](../Contents.md) \| [Prev (8 Testing and Debugging)](../08_Testing_debugging/00_Overview.md) 20 | -------------------------------------------------------------------------------- /Notes/09_Packages/03_Distribution.md: -------------------------------------------------------------------------------- 1 | [Contents](../Contents.md) \| [Previous (9.2 Third Party Packages)](02_Third_party.md) \| [Next (The End)](TheEnd.md) 2 | 3 | # 9.3 Distribution 4 | 5 | At some point you might want to give your code to someone else, possibly just a co-worker. 6 | This section gives the most basic technique of doing that. For more detailed 7 | information, you'll need to consult the [Python Packaging User Guide](https://packaging.python.org). 8 | 9 | ### Creating a setup.py file 10 | 11 | Add a `setup.py` file to the top-level of your project directory. 12 | 13 | ```python 14 | # setup.py 15 | import setuptools 16 | 17 | setuptools.setup( 18 | name="porty", 19 | version="0.0.1", 20 | author="Your Name", 21 | author_email="you@example.com", 22 | description="Practical Python Code", 23 | packages=setuptools.find_packages(), 24 | ) 25 | ``` 26 | 27 | ### Creating MANIFEST.in 28 | 29 | If there are additional files associated with your project, specify them with a `MANIFEST.in` file. 30 | For example: 31 | 32 | ``` 33 | # MANIFEST.in 34 | include *.csv 35 | ``` 36 | 37 | Put the `MANIFEST.in` file in the same directory as `setup.py`. 38 | 39 | ### Creating a source distribution 40 | 41 | To create a distribution of your code, use the `setup.py` file. For example: 42 | 43 | ``` 44 | bash % python setup.py sdist 45 | ``` 46 | 47 | This will create a `.tar.gz` or `.zip` file in the directory `dist/`. That file is something 48 | that you can now give away to others. 49 | 50 | ### Installing your code 51 | 52 | Others can install your Python code using `pip` in the same way that they do for other 53 | packages. They simply need to supply the file created in the previous step. 54 | For example: 55 | 56 | ``` 57 | bash % python -m pip install porty-0.0.1.tar.gz 58 | ``` 59 | 60 | ### Commentary 61 | 62 | The steps above describe the absolute most minimal basics of creating 63 | a package of Python code that you can give to another person. In 64 | reality, it can be much more complicated depending on third-party 65 | dependencies, whether or not your application includes foreign code 66 | (i.e., C/C++), and so forth. Covering that is outside the scope of 67 | this course. We've only taken a tiny first step. 68 | 69 | ## Exercises 70 | 71 | ### Exercise 9.5: Make a package 72 | 73 | Take the `porty-app/` code you created for Exercise 9.3 and see if you 74 | can recreate the steps described here. Specifically, add a `setup.py` 75 | file and a `MANIFEST.in` file to the top-level directory. 76 | Create a source distribution file by running `python setup.py sdist`. 77 | 78 | As a final step, see if you can install your package into a Python 79 | virtual environment. 80 | 81 | [Contents](../Contents.md) \| [Previous (9.2 Third Party Packages)](02_Third_party.md) \| [Next (The End)](TheEnd.md) 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /Notes/09_Packages/TheEnd.md: -------------------------------------------------------------------------------- 1 | # The End! 2 | 3 | You've made it to the end of the course. Thanks for your time and your attention. 4 | May your future Python hacking be fun and productive! 5 | 6 | I'm always happy to get feedback. You can find me at [https://dabeaz.com](https://dabeaz.com) 7 | or on Twitter at [@dabeaz](https://twitter.com/dabeaz). - David Beazley. 8 | 9 | [Contents](../Contents.md) \| [Home](../..) 10 | 11 | -------------------------------------------------------------------------------- /Notes/Contents.md: -------------------------------------------------------------------------------- 1 | # Practical Python Programming 2 | 3 | ## Table of Contents 4 | 5 | * [0. Course Setup (READ FIRST!)](00_Setup.md) 6 | * [1. Introduction to Python](01_Introduction/00_Overview.md) 7 | * [2. Working with Data](02_Working_with_data/00_Overview.md) 8 | * [3. Program Organization](03_Program_organization/00_Overview.md) 9 | * [4. Classes and Objects](04_Classes_objects/00_Overview.md) 10 | * [5. The Inner Workings of Python Objects](05_Object_model/00_Overview.md) 11 | * [6. Generators](06_Generators/00_Overview.md) 12 | * [7. A Few Advanced Topics](07_Advanced_Topics/00_Overview.md) 13 | * [8. Testing, Logging, and Debugging](08_Testing_debugging/00_Overview.md) 14 | * [9. Packages](09_Packages/00_Overview.md) 15 | 16 | Please see the [Instructor Notes](InstructorNotes.md) if you plan on 17 | teaching the course. 18 | 19 | [Home](../README.md) 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Solutions/1_10/mortgage.py: -------------------------------------------------------------------------------- 1 | # mortgage.py 2 | 3 | principal = 500000.0 4 | rate = 0.05 5 | payment = 2684.11 6 | total_paid = 0.0 7 | month = 0 8 | 9 | extra_payment = 1000.0 10 | extra_payment_start_month = 61 11 | extra_payment_end_month = 108 12 | 13 | while principal > 0: 14 | month = month + 1 15 | principal = principal * (1+rate/12) - payment 16 | total_paid = total_paid + payment 17 | 18 | if month >= extra_payment_start_month and month <= extra_payment_end_month: 19 | principal = principal - extra_payment 20 | total_paid = total_paid + extra_payment 21 | 22 | print(month, round(total_paid,2), round(principal, 2)) 23 | 24 | print('Total paid', round(total_paid, 2)) 25 | print('Months', month) 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Solutions/1_27/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | total_cost = 0.0 4 | 5 | with open('../../Work/Data/portfolio.csv', 'rt') as f: 6 | headers = next(f) 7 | for line in f: 8 | row = line.split(',') 9 | nshares = int(row[1]) 10 | price = float(row[2]) 11 | total_cost += nshares * price 12 | 13 | print('Total cost', total_cost) 14 | -------------------------------------------------------------------------------- /Solutions/1_33/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import csv 4 | def portfolio_cost(filename): 5 | ''' 6 | Computes the total cost (shares*price) of a portfolio file 7 | ''' 8 | total_cost = 0.0 9 | 10 | with open(filename, 'rt') as f: 11 | rows = csv.reader(f) 12 | headers = next(rows) 13 | for row in rows: 14 | try: 15 | nshares = int(row[1]) 16 | price = float(row[2]) 17 | total_cost += nshares * price 18 | # This catches errors in int() and float() conversions above 19 | except ValueError: 20 | print('Bad row:', row) 21 | 22 | return total_cost 23 | 24 | import sys 25 | if len(sys.argv) == 2: 26 | filename = sys.argv[1] 27 | else: 28 | filename = input('Enter a filename:') 29 | 30 | cost = portfolio_cost(filename) 31 | print('Total cost:', cost) 32 | -------------------------------------------------------------------------------- /Solutions/1_5/bounce.py: -------------------------------------------------------------------------------- 1 | # bounce.py 2 | 3 | height = 100 4 | bounce = 1 5 | while bounce <= 10: 6 | height = height * (3/5) 7 | print(bounce, round(height, 4)) 8 | bounce += 1 9 | -------------------------------------------------------------------------------- /Solutions/2_11/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | import csv 3 | 4 | def read_portfolio(filename): 5 | ''' 6 | Read a stock portfolio file into a list of dictionaries with keys 7 | name, shares, and price. 8 | ''' 9 | portfolio = [] 10 | with open(filename) as f: 11 | rows = csv.reader(f) 12 | headers = next(rows) 13 | 14 | for row in rows: 15 | stock = { 16 | 'name' : row[0], 17 | 'shares' : int(row[1]), 18 | 'price' : float(row[2]) 19 | } 20 | portfolio.append(stock) 21 | 22 | return portfolio 23 | 24 | def read_prices(filename): 25 | ''' 26 | Read a CSV file of price data into a dict mapping names to prices. 27 | ''' 28 | prices = {} 29 | with open(filename) as f: 30 | rows = csv.reader(f) 31 | for row in rows: 32 | try: 33 | prices[row[0]] = float(row[1]) 34 | except IndexError: 35 | pass 36 | 37 | return prices 38 | 39 | def make_report_data(portfolio, prices): 40 | ''' 41 | Make a list of (name, shares, price, change) tuples given a portfolio list 42 | and prices dictionary. 43 | ''' 44 | rows = [] 45 | for stock in portfolio: 46 | current_price = prices[stock['name']] 47 | change = current_price - stock['price'] 48 | summary = (stock['name'], stock['shares'], current_price, change) 49 | rows.append(summary) 50 | return rows 51 | 52 | # Read data files and create the report data 53 | 54 | portfolio = read_portfolio('../../Work/Data/portfolio.csv') 55 | prices = read_prices('../../Work/Data/prices.csv') 56 | 57 | # Generate the report data 58 | 59 | report = make_report_data(portfolio, prices) 60 | 61 | # Output the report 62 | headers = ('Name', 'Shares', 'Price', 'Change') 63 | print('%10s %10s %10s %10s' % headers) 64 | print(('-' * 10 + ' ') * len(headers)) 65 | for row in report: 66 | print('%10s %10d %10.2f %10.2f' % row) 67 | -------------------------------------------------------------------------------- /Solutions/2_16/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import csv 4 | def portfolio_cost(filename): 5 | ''' 6 | Computes the total cost (shares*price) of a portfolio file 7 | ''' 8 | total_cost = 0.0 9 | 10 | with open(filename) as f: 11 | rows = csv.reader(f) 12 | headers = next(rows) 13 | for rowno, row in enumerate(rows, start=1): 14 | record = dict(zip(headers, row)) 15 | try: 16 | nshares = int(record['shares']) 17 | price = float(record['price']) 18 | total_cost += nshares * price 19 | # This catches errors in int() and float() conversions above 20 | except ValueError: 21 | print(f'Row {rowno}: Bad row: {row}') 22 | 23 | return total_cost 24 | 25 | import sys 26 | if len(sys.argv) == 2: 27 | filename = sys.argv[1] 28 | else: 29 | filename = input('Enter a filename:') 30 | 31 | cost = portfolio_cost(filename) 32 | print('Total cost:', cost) 33 | -------------------------------------------------------------------------------- /Solutions/2_16/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | import csv 3 | 4 | def read_portfolio(filename): 5 | ''' 6 | Read a stock portfolio file into a list of dictionaries with keys 7 | name, shares, and price. 8 | ''' 9 | portfolio = [] 10 | with open(filename) as f: 11 | rows = csv.reader(f) 12 | headers = next(rows) 13 | 14 | for row in rows: 15 | record = dict(zip(headers, row)) 16 | stock = { 17 | 'name' : record['name'], 18 | 'shares' : int(record['shares']), 19 | 'price' : float(record['price']) 20 | } 21 | portfolio.append(stock) 22 | 23 | return portfolio 24 | 25 | def read_prices(filename): 26 | ''' 27 | Read a CSV file of price data into a dict mapping names to prices. 28 | ''' 29 | prices = {} 30 | with open(filename) as f: 31 | rows = csv.reader(f) 32 | for row in rows: 33 | try: 34 | prices[row[0]] = float(row[1]) 35 | except IndexError: 36 | pass 37 | 38 | return prices 39 | 40 | def make_report_data(portfolio, prices): 41 | ''' 42 | Make a list of (name, shares, price, change) tuples given a portfolio list 43 | and prices dictionary. 44 | ''' 45 | rows = [] 46 | for stock in portfolio: 47 | current_price = prices[stock['name']] 48 | change = current_price - stock['price'] 49 | summary = (stock['name'], stock['shares'], current_price, change) 50 | rows.append(summary) 51 | return rows 52 | 53 | # Read data files and create the report data 54 | 55 | portfolio = read_portfolio('../../Work/Data/portfolio.csv') 56 | prices = read_prices('../../Work/Data/prices.csv') 57 | 58 | # Generate the report data 59 | 60 | report = make_report_data(portfolio, prices) 61 | 62 | # Output the report 63 | headers = ('Name', 'Shares', 'Price', 'Change') 64 | print('%10s %10s %10s %10s' % headers) 65 | print(('-' * 10 + ' ') * len(headers)) 66 | for row in report: 67 | print('%10s %10d %10.2f %10.2f' % row) 68 | -------------------------------------------------------------------------------- /Solutions/2_7/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | import csv 3 | 4 | def read_portfolio(filename): 5 | ''' 6 | Read a stock portfolio file into a list of dictionaries with keys 7 | name, shares, and price. 8 | ''' 9 | portfolio = [] 10 | with open(filename) as f: 11 | rows = csv.reader(f) 12 | headers = next(rows) 13 | 14 | for row in rows: 15 | stock = { 16 | 'name' : row[0], 17 | 'shares' : int(row[1]), 18 | 'price' : float(row[2]) 19 | } 20 | portfolio.append(stock) 21 | 22 | return portfolio 23 | 24 | def read_prices(filename): 25 | ''' 26 | Read a CSV file of price data into a dict mapping names to prices. 27 | ''' 28 | prices = {} 29 | with open(filename) as f: 30 | rows = csv.reader(f) 31 | for row in rows: 32 | try: 33 | prices[row[0]] = float(row[1]) 34 | except IndexError: 35 | pass 36 | 37 | return prices 38 | 39 | portfolio = read_portfolio('../../Work/Data/portfolio.csv') 40 | prices = read_prices('../../Work/Data/prices.csv') 41 | 42 | # Calculate the total cost of the portfolio 43 | total_cost = 0.0 44 | for s in portfolio: 45 | total_cost += s['shares']*s['price'] 46 | 47 | print('Total cost', total_cost) 48 | 49 | # Compute the current value of the portfolio 50 | total_value = 0.0 51 | for s in portfolio: 52 | total_value += s['shares']*prices[s['name']] 53 | 54 | print('Current value', total_value) 55 | print('Gain', total_value - total_cost) 56 | -------------------------------------------------------------------------------- /Solutions/3_10/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(filename, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | if select and not has_headers: 9 | raise RuntimeError('select requires column headers') 10 | 11 | with open(filename) as f: 12 | rows = csv.reader(f, delimiter=delimiter) 13 | 14 | # Read the file headers (if any) 15 | headers = next(rows) if has_headers else [] 16 | 17 | # If specific columns have been selected, make indices for filtering and set output columns 18 | if select: 19 | indices = [ headers.index(colname) for colname in select ] 20 | headers = select 21 | 22 | records = [] 23 | for rowno, row in enumerate(rows, 1): 24 | if not row: # Skip rows with no data 25 | continue 26 | 27 | # If specific column indices are selected, pick them out 28 | if select: 29 | row = [ row[index] for index in indices] 30 | 31 | # Apply type conversion to the row 32 | if types: 33 | try: 34 | row = [func(val) for func, val in zip(types, row)] 35 | except ValueError as e: 36 | if not silence_errors: 37 | print(f"Row {rowno}: Couldn't convert {row}") 38 | print(f"Row {rowno}: Reason {e}") 39 | continue 40 | 41 | # Make a dictionary or a tuple 42 | if headers: 43 | record = dict(zip(headers, row)) 44 | else: 45 | record = tuple(row) 46 | records.append(record) 47 | 48 | return records 49 | -------------------------------------------------------------------------------- /Solutions/3_14/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(filename, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | if select and not has_headers: 9 | raise RuntimeError('select requires column headers') 10 | 11 | with open(filename) as f: 12 | rows = csv.reader(f, delimiter=delimiter) 13 | 14 | # Read the file headers (if any) 15 | headers = next(rows) if has_headers else [] 16 | 17 | # If specific columns have been selected, make indices for filtering and set output columns 18 | if select: 19 | indices = [ headers.index(colname) for colname in select ] 20 | headers = select 21 | 22 | records = [] 23 | for rowno, row in enumerate(rows, 1): 24 | if not row: # Skip rows with no data 25 | continue 26 | 27 | # If specific column indices are selected, pick them out 28 | if select: 29 | row = [ row[index] for index in indices] 30 | 31 | # Apply type conversion to the row 32 | if types: 33 | try: 34 | row = [func(val) for func, val in zip(types, row)] 35 | except ValueError as e: 36 | if not silence_errors: 37 | print(f"Row {rowno}: Couldn't convert {row}") 38 | print(f"Row {rowno}: Reason {e}") 39 | continue 40 | 41 | # Make a dictionary or a tuple 42 | if headers: 43 | record = dict(zip(headers, row)) 44 | else: 45 | record = tuple(row) 46 | records.append(record) 47 | 48 | return records 49 | -------------------------------------------------------------------------------- /Solutions/3_14/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return sum([s['shares']*s['price'] for s in portfolio]) 11 | 12 | import sys 13 | if len(sys.argv) == 2: 14 | filename = sys.argv[1] 15 | else: 16 | filename = input('Enter a filename:') 17 | 18 | cost = portfolio_cost(filename) 19 | print('Total cost:', cost) 20 | -------------------------------------------------------------------------------- /Solutions/3_14/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | import fileparse 4 | 5 | def read_portfolio(filename): 6 | ''' 7 | Read a stock portfolio file into a list of dictionaries with keys 8 | name, shares, and price. 9 | ''' 10 | return fileparse.parse_csv(filename, select=['name','shares','price'], types=[str,int,float]) 11 | 12 | def read_prices(filename): 13 | ''' 14 | Read a CSV file of price data into a dict mapping names to prices. 15 | ''' 16 | return dict(fileparse.parse_csv(filename,types=[str,float], has_headers=False)) 17 | 18 | def make_report_data(portfolio,prices): 19 | ''' 20 | Make a list of (name, shares, price, change) tuples given a portfolio list 21 | and prices dictionary. 22 | ''' 23 | rows = [] 24 | for stock in portfolio: 25 | current_price = prices[stock['name']] 26 | change = current_price - stock['price'] 27 | summary = (stock['name'], stock['shares'], current_price, change) 28 | rows.append(summary) 29 | return rows 30 | 31 | def print_report(reportdata): 32 | ''' 33 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 34 | ''' 35 | headers = ('Name','Shares','Price','Change') 36 | print('%10s %10s %10s %10s' % headers) 37 | print(('-'*10 + ' ')*len(headers)) 38 | for row in reportdata: 39 | print('%10s %10d %10.2f %10.2f' % row) 40 | 41 | def portfolio_report(portfoliofile,pricefile): 42 | ''' 43 | Make a stock report given portfolio and price data files. 44 | ''' 45 | # Read data files 46 | portfolio = read_portfolio(portfoliofile) 47 | prices = read_prices(pricefile) 48 | 49 | # Create the report data 50 | report = make_report_data(portfolio, prices) 51 | 52 | # Print it out 53 | print_report(report) 54 | 55 | portfolio_report('../../Work/Data/portfolio.csv', 56 | '../../Work/Data/prices.csv') 57 | -------------------------------------------------------------------------------- /Solutions/3_16/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(filename, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | if select and not has_headers: 9 | raise RuntimeError('select requires column headers') 10 | 11 | with open(filename) as f: 12 | rows = csv.reader(f, delimiter=delimiter) 13 | 14 | # Read the file headers (if any) 15 | headers = next(rows) if has_headers else [] 16 | 17 | # If specific columns have been selected, make indices for filtering and set output columns 18 | if select: 19 | indices = [ headers.index(colname) for colname in select ] 20 | headers = select 21 | 22 | records = [] 23 | for rowno, row in enumerate(rows, 1): 24 | if not row: # Skip rows with no data 25 | continue 26 | 27 | # If specific column indices are selected, pick them out 28 | if select: 29 | row = [ row[index] for index in indices] 30 | 31 | # Apply type conversion to the row 32 | if types: 33 | try: 34 | row = [func(val) for func, val in zip(types, row)] 35 | except ValueError as e: 36 | if not silence_errors: 37 | print(f"Row {rowno}: Couldn't convert {row}") 38 | print(f"Row {rowno}: Reason {e}") 39 | continue 40 | 41 | # Make a dictionary or a tuple 42 | if headers: 43 | record = dict(zip(headers, row)) 44 | else: 45 | record = tuple(row) 46 | records.append(record) 47 | 48 | return records 49 | -------------------------------------------------------------------------------- /Solutions/3_16/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return sum([s['shares'] * s['price'] for s in portfolio]) 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/3_16/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | import fileparse 4 | 5 | def read_portfolio(filename): 6 | ''' 7 | Read a stock portfolio file into a list of dictionaries with keys 8 | name, shares, and price. 9 | ''' 10 | return fileparse.parse_csv(filename, select=['name','shares','price'], types=[str,int,float]) 11 | 12 | def read_prices(filename): 13 | ''' 14 | Read a CSV file of price data into a dict mapping names to prices. 15 | ''' 16 | return dict(fileparse.parse_csv(filename,types=[str,float], has_headers=False)) 17 | 18 | def make_report_data(portfolio,prices): 19 | ''' 20 | Make a list of (name, shares, price, change) tuples given a portfolio list 21 | and prices dictionary. 22 | ''' 23 | rows = [] 24 | for stock in portfolio: 25 | current_price = prices[stock['name']] 26 | change = current_price - stock['price'] 27 | summary = (stock['name'], stock['shares'], current_price, change) 28 | rows.append(summary) 29 | return rows 30 | 31 | def print_report(reportdata): 32 | ''' 33 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 34 | ''' 35 | headers = ('Name','Shares','Price','Change') 36 | print('%10s %10s %10s %10s' % headers) 37 | print(('-'*10 + ' ')*len(headers)) 38 | for row in reportdata: 39 | print('%10s %10d %10.2f %10.2f' % row) 40 | 41 | def portfolio_report(portfoliofile, pricefile): 42 | ''' 43 | Make a stock report given portfolio and price data files. 44 | ''' 45 | # Read data files 46 | portfolio = read_portfolio(portfoliofile) 47 | prices = read_prices(pricefile) 48 | 49 | # Create the report data 50 | report = make_report_data(portfolio, prices) 51 | 52 | # Print it out 53 | print_report(report) 54 | 55 | def main(args): 56 | if len(args) != 3: 57 | raise SystemExit('Usage: %s portfile pricefile' % args[0]) 58 | portfolio_report(args[1], args[2]) 59 | 60 | if __name__ == '__main__': 61 | import sys 62 | main(sys.argv) 63 | -------------------------------------------------------------------------------- /Solutions/3_18/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | if select and not has_headers: 9 | raise RuntimeError('select requires column headers') 10 | 11 | rows = csv.reader(lines, delimiter=delimiter) 12 | 13 | # Read the file headers (if any) 14 | headers = next(rows) if has_headers else [] 15 | 16 | # If specific columns have been selected, make indices for filtering and set output columns 17 | if select: 18 | indices = [ headers.index(colname) for colname in select ] 19 | headers = select 20 | 21 | records = [] 22 | for rowno, row in enumerate(rows, 1): 23 | if not row: # Skip rows with no data 24 | continue 25 | 26 | # If specific column indices are selected, pick them out 27 | if select: 28 | row = [ row[index] for index in indices] 29 | 30 | # Apply type conversion to the row 31 | if types: 32 | try: 33 | row = [func(val) for func, val in zip(types, row)] 34 | except ValueError as e: 35 | if not silence_errors: 36 | print(f"Row {rowno}: Couldn't convert {row}") 37 | print(f"Row {rowno}: Reason {e}") 38 | continue 39 | 40 | # Make a dictionary or a tuple 41 | if headers: 42 | record = dict(zip(headers, row)) 43 | else: 44 | record = tuple(row) 45 | records.append(record) 46 | 47 | return records 48 | -------------------------------------------------------------------------------- /Solutions/3_18/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return sum([s['shares'] * s['price'] for s in portfolio]) 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/3_18/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | import fileparse 4 | 5 | def read_portfolio(filename): 6 | ''' 7 | Read a stock portfolio file into a list of dictionaries with keys 8 | name, shares, and price. 9 | ''' 10 | with open(filename) as lines: 11 | return fileparse.parse_csv(lines, select=['name','shares','price'], types=[str,int,float]) 12 | 13 | def read_prices(filename): 14 | ''' 15 | Read a CSV file of price data into a dict mapping names to prices. 16 | ''' 17 | with open(filename) as lines: 18 | return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False)) 19 | 20 | def make_report_data(portfolio,prices): 21 | ''' 22 | Make a list of (name, shares, price, change) tuples given a portfolio list 23 | and prices dictionary. 24 | ''' 25 | rows = [] 26 | for stock in portfolio: 27 | current_price = prices[stock['name']] 28 | change = current_price - stock['price'] 29 | summary = (stock['name'], stock['shares'], current_price, change) 30 | rows.append(summary) 31 | return rows 32 | 33 | def print_report(reportdata): 34 | ''' 35 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 36 | ''' 37 | headers = ('Name','Shares','Price','Change') 38 | print('%10s %10s %10s %10s' % headers) 39 | print(('-'*10 + ' ')*len(headers)) 40 | for row in reportdata: 41 | print('%10s %10d %10.2f %10.2f' % row) 42 | 43 | def portfolio_report(portfoliofile, pricefile): 44 | ''' 45 | Make a stock report given portfolio and price data files. 46 | ''' 47 | # Read data files 48 | portfolio = read_portfolio(portfoliofile) 49 | prices = read_prices(pricefile) 50 | 51 | # Create the report data 52 | report = make_report_data(portfolio, prices) 53 | 54 | # Print it out 55 | print_report(report) 56 | 57 | def main(args): 58 | if len(args) != 3: 59 | raise SystemExit('Usage: %s portfile pricefile' % args[0]) 60 | portfolio_report(args[1], args[2]) 61 | 62 | if __name__ == '__main__': 63 | import sys 64 | main(sys.argv) 65 | -------------------------------------------------------------------------------- /Solutions/3_2/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | import csv 3 | 4 | def read_portfolio(filename): 5 | ''' 6 | Read a stock portfolio file into a list of dictionaries with keys 7 | name, shares, and price. 8 | ''' 9 | portfolio = [] 10 | with open(filename) as f: 11 | rows = csv.reader(f) 12 | headers = next(rows) 13 | 14 | for row in rows: 15 | record = dict(zip(headers, row)) 16 | stock = { 17 | 'name' : record['name'], 18 | 'shares' : int(record['shares']), 19 | 'price' : float(record['price']) 20 | } 21 | portfolio.append(stock) 22 | 23 | return portfolio 24 | 25 | def read_prices(filename): 26 | ''' 27 | Read a CSV file of price data into a dict mapping names to prices. 28 | ''' 29 | prices = {} 30 | with open(filename) as f: 31 | rows = csv.reader(f) 32 | for row in rows: 33 | try: 34 | prices[row[0]] = float(row[1]) 35 | except IndexError: 36 | pass 37 | 38 | return prices 39 | 40 | def make_report_data(portfolio,prices): 41 | ''' 42 | Make a list of (name, shares, price, change) tuples given a portfolio list 43 | and prices dictionary. 44 | ''' 45 | rows = [] 46 | for stock in portfolio: 47 | current_price = prices[stock['name']] 48 | change = current_price - stock['price'] 49 | summary = (stock['name'], stock['shares'], current_price, change) 50 | rows.append(summary) 51 | return rows 52 | 53 | def print_report(reportdata): 54 | ''' 55 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 56 | ''' 57 | headers = ('Name','Shares','Price','Change') 58 | print('%10s %10s %10s %10s' % headers) 59 | print(('-'*10 + ' ')*len(headers)) 60 | for row in reportdata: 61 | print('%10s %10d %10.2f %10.2f' % row) 62 | 63 | def portfolio_report(portfoliofile,pricefile): 64 | ''' 65 | Make a stock report given portfolio and price data files. 66 | ''' 67 | # Read data files 68 | portfolio = read_portfolio(portfoliofile) 69 | prices = read_prices(pricefile) 70 | 71 | # Create the report data 72 | report = make_report_data(portfolio,prices) 73 | 74 | # Print it out 75 | print_report(report) 76 | 77 | portfolio_report('../../Work/Data/portfolio.csv', 78 | '../../Work/Data/prices.csv') 79 | -------------------------------------------------------------------------------- /Solutions/3_7/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(filename, select=None, types=None, has_headers=True, delimiter=','): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | with open(filename) as f: 9 | rows = csv.reader(f, delimiter=delimiter) 10 | 11 | # Read the file headers (if any) 12 | headers = next(rows) if has_headers else [] 13 | 14 | # If specific columns have been selected, make indices for filtering 15 | if select: 16 | indices = [ headers.index(colname) for colname in select ] 17 | headers = select 18 | 19 | records = [] 20 | for row in rows: 21 | if not row: # Skip rows with no data 22 | continue 23 | 24 | # If specific column indices are selected, pick them out 25 | if select: 26 | row = [ row[index] for index in indices] 27 | 28 | # Apply type conversion to the row 29 | if types: 30 | row = [func(val) for func, val in zip(types, row)] 31 | 32 | # Make a dictionary or a tuple 33 | if headers: 34 | record = dict(zip(headers, row)) 35 | else: 36 | record = tuple(row) 37 | records.append(record) 38 | 39 | return records 40 | -------------------------------------------------------------------------------- /Solutions/4_10/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | if select and not has_headers: 9 | raise RuntimeError('select requires column headers') 10 | 11 | rows = csv.reader(lines, delimiter=delimiter) 12 | 13 | # Read the file headers (if any) 14 | headers = next(rows) if has_headers else [] 15 | 16 | # If specific columns have been selected, make indices for filtering and set output columns 17 | if select: 18 | indices = [ headers.index(colname) for colname in select ] 19 | headers = select 20 | 21 | records = [] 22 | for rowno, row in enumerate(rows, 1): 23 | if not row: # Skip rows with no data 24 | continue 25 | 26 | # If specific column indices are selected, pick them out 27 | if select: 28 | row = [ row[index] for index in indices] 29 | 30 | # Apply type conversion to the row 31 | if types: 32 | try: 33 | row = [func(val) for func, val in zip(types, row)] 34 | except ValueError as e: 35 | if not silence_errors: 36 | print(f"Row {rowno}: Couldn't convert {row}") 37 | print(f"Row {rowno}: Reason {e}") 38 | continue 39 | 40 | # Make a dictionary or a tuple 41 | if headers: 42 | record = dict(zip(headers, row)) 43 | else: 44 | record = tuple(row) 45 | records.append(record) 46 | 47 | return records 48 | -------------------------------------------------------------------------------- /Solutions/4_10/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return sum([s.cost() for s in portfolio]) 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/4_10/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | import fileparse 4 | from stock import Stock 5 | import tableformat 6 | 7 | def read_portfolio(filename): 8 | ''' 9 | Read a stock portfolio file into a list of dictionaries with keys 10 | name, shares, and price. 11 | ''' 12 | with open(filename) as lines: 13 | portdicts = fileparse.parse_csv(lines, 14 | select=['name','shares','price'], 15 | types=[str,int,float]) 16 | 17 | portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ] 18 | return portfolio 19 | 20 | def read_prices(filename): 21 | ''' 22 | Read a CSV file of price data into a dict mapping names to prices. 23 | ''' 24 | with open(filename) as lines: 25 | return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False)) 26 | 27 | def make_report_data(portfolio, prices): 28 | ''' 29 | Make a list of (name, shares, price, change) tuples given a portfolio list 30 | and prices dictionary. 31 | ''' 32 | rows = [] 33 | for s in portfolio: 34 | current_price = prices[s.name] 35 | change = current_price - s.price 36 | summary = (s.name, s.shares, current_price, change) 37 | rows.append(summary) 38 | return rows 39 | 40 | def print_report(reportdata, formatter): 41 | ''' 42 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 43 | ''' 44 | formatter.headings(['Name','Shares','Price','Change']) 45 | for name, shares, price, change in reportdata: 46 | rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ] 47 | formatter.row(rowdata) 48 | 49 | def portfolio_report(portfoliofile, pricefile, fmt='txt'): 50 | ''' 51 | Make a stock report given portfolio and price data files. 52 | ''' 53 | # Read data files 54 | portfolio = read_portfolio(portfoliofile) 55 | prices = read_prices(pricefile) 56 | 57 | # Create the report data 58 | report = make_report_data(portfolio, prices) 59 | 60 | # Print it out 61 | formatter = tableformat.create_formatter(fmt) 62 | print_report(report, formatter) 63 | 64 | def main(args): 65 | if len(args) != 4: 66 | raise SystemExit('Usage: %s portfile pricefile format' % args[0]) 67 | portfolio_report(args[1], args[2], args[3]) 68 | 69 | if __name__ == '__main__': 70 | import sys 71 | main(sys.argv) 72 | -------------------------------------------------------------------------------- /Solutions/4_10/stock.py: -------------------------------------------------------------------------------- 1 | # stock.py 2 | 3 | class Stock: 4 | ''' 5 | An instance of a stock holding consisting of name, shares, and price. 6 | ''' 7 | def __init__(self, name, shares, price): 8 | self.name = name 9 | self.shares = shares 10 | self.price = price 11 | 12 | def __repr__(self): 13 | return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' 14 | 15 | def cost(self): 16 | ''' 17 | Return the cost as shares*price 18 | ''' 19 | return self.shares * self.price 20 | 21 | def sell(self, nshares): 22 | ''' 23 | Sell a number of shares 24 | ''' 25 | self.shares -= nshares 26 | 27 | -------------------------------------------------------------------------------- /Solutions/4_10/tableformat.py: -------------------------------------------------------------------------------- 1 | # tableformat.py 2 | 3 | class TableFormatter: 4 | def headings(self, headers): 5 | ''' 6 | Emit the table headers 7 | ''' 8 | raise NotImplementedError() 9 | 10 | def row(self, rowdata): 11 | ''' 12 | Emit a single row of table data 13 | ''' 14 | raise NotImplementedError() 15 | 16 | class TextTableFormatter(TableFormatter): 17 | ''' 18 | Output data in plain-text format. 19 | ''' 20 | def headings(self, headers): 21 | for h in headers: 22 | print(f'{h:>10s}', end=' ') 23 | print() 24 | print(('-'*10 + ' ')*len(headers)) 25 | 26 | def row(self, rowdata): 27 | for d in rowdata: 28 | print(f'{d:>10s}', end=' ') 29 | print() 30 | 31 | class CSVTableFormatter(TableFormatter): 32 | ''' 33 | Output data in CSV format. 34 | ''' 35 | def headings(self, headers): 36 | print(','.join(headers)) 37 | 38 | def row(self, rowdata): 39 | print(','.join(rowdata)) 40 | 41 | class HTMLTableFormatter(TableFormatter): 42 | ''' 43 | Output data in HTML format. 44 | ''' 45 | def headings(self, headers): 46 | print('', end='') 47 | for h in headers: 48 | print(f'{h}', end='') 49 | print('') 50 | 51 | def row(self, rowdata): 52 | print('', end='') 53 | for d in rowdata: 54 | print(f'{d}', end='') 55 | print('') 56 | 57 | class FormatError(Exception): 58 | pass 59 | 60 | def create_formatter(name): 61 | ''' 62 | Create an appropriate formatter given an output format name 63 | ''' 64 | if name == 'txt': 65 | return TextTableFormatter() 66 | elif name == 'csv': 67 | return CSVTableFormatter() 68 | elif name == 'html': 69 | return HTMLTableFormatter() 70 | else: 71 | raise FormatError(f'Unknown table format {name}') 72 | 73 | def print_table(objects, columns, formatter): 74 | ''' 75 | Make a nicely formatted table from a list of objects and attribute names. 76 | ''' 77 | formatter.headings(columns) 78 | for obj in objects: 79 | rowdata = [ str(getattr(obj, name)) for name in columns ] 80 | formatter.row(rowdata) 81 | 82 | -------------------------------------------------------------------------------- /Solutions/4_4/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | if select and not has_headers: 9 | raise RuntimeError('select requires column headers') 10 | 11 | rows = csv.reader(lines, delimiter=delimiter) 12 | 13 | # Read the file headers (if any) 14 | headers = next(rows) if has_headers else [] 15 | 16 | # If specific columns have been selected, make indices for filtering and set output columns 17 | if select: 18 | indices = [ headers.index(colname) for colname in select ] 19 | headers = select 20 | 21 | records = [] 22 | for rowno, row in enumerate(rows, 1): 23 | if not row: # Skip rows with no data 24 | continue 25 | 26 | # If specific column indices are selected, pick them out 27 | if select: 28 | row = [ row[index] for index in indices] 29 | 30 | # Apply type conversion to the row 31 | if types: 32 | try: 33 | row = [func(val) for func, val in zip(types, row)] 34 | except ValueError as e: 35 | if not silence_errors: 36 | print(f"Row {rowno}: Couldn't convert {row}") 37 | print(f"Row {rowno}: Reason {e}") 38 | continue 39 | 40 | # Make a dictionary or a tuple 41 | if headers: 42 | record = dict(zip(headers, row)) 43 | else: 44 | record = tuple(row) 45 | records.append(record) 46 | 47 | return records 48 | -------------------------------------------------------------------------------- /Solutions/4_4/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return sum([s.cost() for s in portfolio]) 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/4_4/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | import fileparse 4 | from stock import Stock 5 | 6 | def read_portfolio(filename): 7 | ''' 8 | Read a stock portfolio file into a list of dictionaries with keys 9 | name, shares, and price. 10 | ''' 11 | with open(filename) as lines: 12 | portdicts = fileparse.parse_csv(lines, 13 | select=['name','shares','price'], 14 | types=[str,int,float]) 15 | 16 | portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ] 17 | return portfolio 18 | 19 | def read_prices(filename): 20 | ''' 21 | Read a CSV file of price data into a dict mapping names to prices. 22 | ''' 23 | with open(filename) as lines: 24 | return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False)) 25 | 26 | def make_report_data(portfolio, prices): 27 | ''' 28 | Make a list of (name, shares, price, change) tuples given a portfolio list 29 | and prices dictionary. 30 | ''' 31 | rows = [] 32 | for s in portfolio: 33 | current_price = prices[s.name] 34 | change = current_price - s.price 35 | summary = (s.name, s.shares, current_price, change) 36 | rows.append(summary) 37 | return rows 38 | 39 | def print_report(reportdata): 40 | ''' 41 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 42 | ''' 43 | headers = ('Name','Shares','Price','Change') 44 | print('%10s %10s %10s %10s' % headers) 45 | print(('-'*10 + ' ')*len(headers)) 46 | for row in reportdata: 47 | print('%10s %10d %10.2f %10.2f' % row) 48 | 49 | def portfolio_report(portfoliofile, pricefile): 50 | ''' 51 | Make a stock report given portfolio and price data files. 52 | ''' 53 | # Read data files 54 | portfolio = read_portfolio(portfoliofile) 55 | prices = read_prices(pricefile) 56 | 57 | # Create the report data 58 | report = make_report_data(portfolio, prices) 59 | 60 | # Print it out 61 | print_report(report) 62 | 63 | def main(args): 64 | if len(args) != 3: 65 | raise SystemExit('Usage: %s portfile pricefile' % args[0]) 66 | portfolio_report(args[1], args[2]) 67 | 68 | if __name__ == '__main__': 69 | import sys 70 | main(sys.argv) 71 | -------------------------------------------------------------------------------- /Solutions/4_4/stock.py: -------------------------------------------------------------------------------- 1 | # stock.py 2 | 3 | class Stock: 4 | ''' 5 | An instance of a stock holding consisting of name, shares, and price. 6 | ''' 7 | def __init__(self, name, shares, price): 8 | self.name = name 9 | self.shares = shares 10 | self.price = price 11 | 12 | def cost(self): 13 | ''' 14 | Return the cost as shares*price 15 | ''' 16 | return self.shares * self.price 17 | 18 | def sell(self, nshares): 19 | ''' 20 | Sell a number of shares 21 | ''' 22 | self.shares -= nshares 23 | 24 | -------------------------------------------------------------------------------- /Solutions/5_8/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | if select and not has_headers: 9 | raise RuntimeError('select requires column headers') 10 | 11 | rows = csv.reader(lines, delimiter=delimiter) 12 | 13 | # Read the file headers (if any) 14 | headers = next(rows) if has_headers else [] 15 | 16 | # If specific columns have been selected, make indices for filtering and set output columns 17 | if select: 18 | indices = [ headers.index(colname) for colname in select ] 19 | headers = select 20 | 21 | records = [] 22 | for rowno, row in enumerate(rows, 1): 23 | if not row: # Skip rows with no data 24 | continue 25 | 26 | # If specific column indices are selected, pick them out 27 | if select: 28 | row = [ row[index] for index in indices] 29 | 30 | # Apply type conversion to the row 31 | if types: 32 | try: 33 | row = [func(val) for func, val in zip(types, row)] 34 | except ValueError as e: 35 | if not silence_errors: 36 | print(f"Row {rowno}: Couldn't convert {row}") 37 | print(f"Row {rowno}: Reason {e}") 38 | continue 39 | 40 | # Make a dictionary or a tuple 41 | if headers: 42 | record = dict(zip(headers, row)) 43 | else: 44 | record = tuple(row) 45 | records.append(record) 46 | 47 | return records 48 | -------------------------------------------------------------------------------- /Solutions/5_8/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return sum([s.cost() for s in portfolio]) 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/5_8/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | import fileparse 4 | from stock import Stock 5 | import tableformat 6 | 7 | def read_portfolio(filename): 8 | ''' 9 | Read a stock portfolio file into a list of dictionaries with keys 10 | name, shares, and price. 11 | ''' 12 | with open(filename) as lines: 13 | portdicts = fileparse.parse_csv(lines, 14 | select=['name','shares','price'], 15 | types=[str,int,float]) 16 | 17 | portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ] 18 | return portfolio 19 | 20 | def read_prices(filename): 21 | ''' 22 | Read a CSV file of price data into a dict mapping names to prices. 23 | ''' 24 | with open(filename) as lines: 25 | return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False)) 26 | 27 | def make_report_data(portfolio, prices): 28 | ''' 29 | Make a list of (name, shares, price, change) tuples given a portfolio list 30 | and prices dictionary. 31 | ''' 32 | rows = [] 33 | for s in portfolio: 34 | current_price = prices[s.name] 35 | change = current_price - s.price 36 | summary = (s.name, s.shares, current_price, change) 37 | rows.append(summary) 38 | return rows 39 | 40 | def print_report(reportdata, formatter): 41 | ''' 42 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 43 | ''' 44 | formatter.headings(['Name','Shares','Price','Change']) 45 | for name, shares, price, change in reportdata: 46 | rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ] 47 | formatter.row(rowdata) 48 | 49 | def portfolio_report(portfoliofile, pricefile, fmt='txt'): 50 | ''' 51 | Make a stock report given portfolio and price data files. 52 | ''' 53 | # Read data files 54 | portfolio = read_portfolio(portfoliofile) 55 | prices = read_prices(pricefile) 56 | 57 | # Create the report data 58 | report = make_report_data(portfolio, prices) 59 | 60 | # Print it out 61 | formatter = tableformat.create_formatter(fmt) 62 | print_report(report, formatter) 63 | 64 | def main(args): 65 | if len(args) != 4: 66 | raise SystemExit('Usage: %s portfile pricefile format' % args[0]) 67 | portfolio_report(args[1], args[2], args[3]) 68 | 69 | if __name__ == '__main__': 70 | import sys 71 | main(sys.argv) 72 | -------------------------------------------------------------------------------- /Solutions/5_8/stock.py: -------------------------------------------------------------------------------- 1 | # stock.py 2 | 3 | class Stock: 4 | ''' 5 | An instance of a stock holding consisting of name, shares, and price. 6 | ''' 7 | __slots__ = ('name','_shares','price') 8 | def __init__(self,name, shares, price): 9 | self.name = name 10 | self.shares = shares 11 | self.price = price 12 | 13 | def __repr__(self): 14 | return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' 15 | 16 | @property 17 | def shares(self): 18 | return self._shares 19 | 20 | @shares.setter 21 | def shares(self, value): 22 | if not isinstance(value,int): 23 | raise TypeError("Must be integer") 24 | self._shares = value 25 | 26 | @property 27 | def cost(self): 28 | ''' 29 | Return the cost as shares*price 30 | ''' 31 | return self.shares * self.price 32 | 33 | def sell(self, nshares): 34 | ''' 35 | Sell a number of shares and return the remaining number. 36 | ''' 37 | self.shares -= nshares 38 | -------------------------------------------------------------------------------- /Solutions/5_8/tableformat.py: -------------------------------------------------------------------------------- 1 | # tableformat.py 2 | 3 | class TableFormatter: 4 | def headings(self, headers): 5 | ''' 6 | Emit the table headers 7 | ''' 8 | raise NotImplementedError() 9 | 10 | def row(self, rowdata): 11 | ''' 12 | Emit a single row of table data 13 | ''' 14 | raise NotImplementedError() 15 | 16 | class TextTableFormatter(TableFormatter): 17 | ''' 18 | Output data in plain-text format. 19 | ''' 20 | def headings(self, headers): 21 | for h in headers: 22 | print(f'{h:>10s}', end=' ') 23 | print() 24 | print(('-'*10 + ' ')*len(headers)) 25 | 26 | def row(self, rowdata): 27 | for d in rowdata: 28 | print(f'{d:>10s}', end=' ') 29 | print() 30 | 31 | class CSVTableFormatter(TableFormatter): 32 | ''' 33 | Output data in CSV format. 34 | ''' 35 | def headings(self, headers): 36 | print(','.join(headers)) 37 | 38 | def row(self, rowdata): 39 | print(','.join(rowdata)) 40 | 41 | class HTMLTableFormatter(TableFormatter): 42 | ''' 43 | Output data in HTML format. 44 | ''' 45 | def headings(self, headers): 46 | print('', end='') 47 | for h in headers: 48 | print(f'{h}', end='') 49 | print('') 50 | 51 | def row(self, rowdata): 52 | print('', end='') 53 | for d in rowdata: 54 | print(f'{d}', end='') 55 | print('') 56 | 57 | class FormatError(Exception): 58 | pass 59 | 60 | def create_formatter(name): 61 | ''' 62 | Create an appropriate formatter given an output format name 63 | ''' 64 | if name == 'txt': 65 | return TextTableFormatter() 66 | elif name == 'csv': 67 | return CSVTableFormatter() 68 | elif name == 'html': 69 | return HTMLTableFormatter() 70 | else: 71 | raise FormatError(f'Unknown table format {name}') 72 | 73 | def print_table(objects, columns, formatter): 74 | ''' 75 | Make a nicely formatted table from a list of objects and attribute names. 76 | ''' 77 | formatter.headings(columns) 78 | for obj in objects: 79 | rowdata = [ str(getattr(obj, name)) for name in columns ] 80 | formatter.row(rowdata) 81 | 82 | -------------------------------------------------------------------------------- /Solutions/6_12/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | if select and not has_headers: 9 | raise RuntimeError('select requires column headers') 10 | 11 | rows = csv.reader(lines, delimiter=delimiter) 12 | 13 | # Read the file headers (if any) 14 | headers = next(rows) if has_headers else [] 15 | 16 | # If specific columns have been selected, make indices for filtering and set output columns 17 | if select: 18 | indices = [ headers.index(colname) for colname in select ] 19 | headers = select 20 | 21 | records = [] 22 | for rowno, row in enumerate(rows, 1): 23 | if not row: # Skip rows with no data 24 | continue 25 | 26 | # If specific column indices are selected, pick them out 27 | if select: 28 | row = [ row[index] for index in indices] 29 | 30 | # Apply type conversion to the row 31 | if types: 32 | try: 33 | row = [func(val) for func, val in zip(types, row)] 34 | except ValueError as e: 35 | if not silence_errors: 36 | print(f"Row {rowno}: Couldn't convert {row}") 37 | print(f"Row {rowno}: Reason {e}") 38 | continue 39 | 40 | # Make a dictionary or a tuple 41 | if headers: 42 | record = dict(zip(headers, row)) 43 | else: 44 | record = tuple(row) 45 | records.append(record) 46 | 47 | return records 48 | -------------------------------------------------------------------------------- /Solutions/6_12/follow.py: -------------------------------------------------------------------------------- 1 | # follow.py 2 | import os 3 | import time 4 | 5 | def follow(filename): 6 | ''' 7 | Generator that produces a sequence of lines being written at the end of a file. 8 | ''' 9 | f = open(filename, 'r') 10 | f.seek(0,os.SEEK_END) 11 | while True: 12 | line = f.readline() 13 | if line == '': 14 | time.sleep(0.1) # Sleep briefly to avoid busy wait 15 | continue 16 | yield line 17 | 18 | # Example use 19 | if __name__ == '__main__': 20 | import report 21 | 22 | portfolio = report.read_portfolio('../../Data/portfolio.csv') 23 | 24 | for line in follow('../../Data/stocklog.csv'): 25 | row = line.split(',') 26 | name = row[0].strip('"') 27 | price = float(row[1]) 28 | change = float(row[4]) 29 | if name in portfolio: 30 | print(f'{name:>10s} {price:>10.2f} {change:>10.2f}') 31 | -------------------------------------------------------------------------------- /Solutions/6_12/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return portfolio.total_cost 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/6_12/portfolio.py: -------------------------------------------------------------------------------- 1 | # portfolio.py 2 | 3 | class Portfolio: 4 | def __init__(self, holdings): 5 | self._holdings = holdings 6 | 7 | def __iter__(self): 8 | return self._holdings.__iter__() 9 | 10 | def __len__(self): 11 | return len(self._holdings) 12 | 13 | def __getitem__(self, index): 14 | return self._holdings[index] 15 | 16 | def __contains__(self, name): 17 | return any([s.name == name for s in self._holdings]) 18 | 19 | @property 20 | def total_cost(self): 21 | return sum([s.shares * s.price for s in self._holdings]) 22 | 23 | def tabulate_shares(self): 24 | from collections import Counter 25 | total_shares = Counter() 26 | for s in self._holdings: 27 | total_shares[s.name] += s.shares 28 | return total_shares 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Solutions/6_12/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | import fileparse 4 | from stock import Stock 5 | import tableformat 6 | from portfolio import Portfolio 7 | 8 | def read_portfolio(filename): 9 | ''' 10 | Read a stock portfolio file into a list of dictionaries with keys 11 | name, shares, and price. 12 | ''' 13 | with open(filename) as lines: 14 | portdicts = fileparse.parse_csv(lines, 15 | select=['name','shares','price'], 16 | types=[str,int,float]) 17 | 18 | portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ] 19 | return Portfolio(portfolio) 20 | 21 | def read_prices(filename): 22 | ''' 23 | Read a CSV file of price data into a dict mapping names to prices. 24 | ''' 25 | with open(filename) as lines: 26 | return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False)) 27 | 28 | def make_report_data(portfolio, prices): 29 | ''' 30 | Make a list of (name, shares, price, change) tuples given a portfolio list 31 | and prices dictionary. 32 | ''' 33 | rows = [] 34 | for s in portfolio: 35 | current_price = prices[s.name] 36 | change = current_price - s.price 37 | summary = (s.name, s.shares, current_price, change) 38 | rows.append(summary) 39 | return rows 40 | 41 | def print_report(reportdata, formatter): 42 | ''' 43 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 44 | ''' 45 | formatter.headings(['Name','Shares','Price','Change']) 46 | for name, shares, price, change in reportdata: 47 | rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ] 48 | formatter.row(rowdata) 49 | 50 | def portfolio_report(portfoliofile, pricefile, fmt='txt'): 51 | ''' 52 | Make a stock report given portfolio and price data files. 53 | ''' 54 | # Read data files 55 | portfolio = read_portfolio(portfoliofile) 56 | prices = read_prices(pricefile) 57 | 58 | # Create the report data 59 | report = make_report_data(portfolio, prices) 60 | 61 | # Print it out 62 | formatter = tableformat.create_formatter(fmt) 63 | print_report(report, formatter) 64 | 65 | def main(args): 66 | if len(args) != 4: 67 | raise SystemExit('Usage: %s portfile pricefile format' % args[0]) 68 | portfolio_report(args[1], args[2], args[3]) 69 | 70 | if __name__ == '__main__': 71 | import sys 72 | main(sys.argv) 73 | -------------------------------------------------------------------------------- /Solutions/6_12/stock.py: -------------------------------------------------------------------------------- 1 | # stock.py 2 | 3 | class Stock: 4 | ''' 5 | An instance of a stock holding consisting of name, shares, and price. 6 | ''' 7 | __slots__ = ('name','_shares','price') 8 | def __init__(self,name, shares, price): 9 | self.name = name 10 | self.shares = shares 11 | self.price = price 12 | 13 | def __repr__(self): 14 | return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' 15 | 16 | @property 17 | def shares(self): 18 | return self._shares 19 | 20 | @shares.setter 21 | def shares(self, value): 22 | if not isinstance(value,int): 23 | raise TypeError("Must be integer") 24 | self._shares = value 25 | 26 | @property 27 | def cost(self): 28 | ''' 29 | Return the cost as shares*price 30 | ''' 31 | return self.shares * self.price 32 | 33 | def sell(self, nshares): 34 | ''' 35 | Sell a number of shares and return the remaining number. 36 | ''' 37 | self.shares -= nshares 38 | -------------------------------------------------------------------------------- /Solutions/6_12/tableformat.py: -------------------------------------------------------------------------------- 1 | # tableformat.py 2 | 3 | class TableFormatter: 4 | def headings(self, headers): 5 | ''' 6 | Emit the table headers 7 | ''' 8 | raise NotImplementedError() 9 | 10 | def row(self, rowdata): 11 | ''' 12 | Emit a single row of table data 13 | ''' 14 | raise NotImplementedError() 15 | 16 | class TextTableFormatter(TableFormatter): 17 | ''' 18 | Output data in plain-text format. 19 | ''' 20 | def headings(self, headers): 21 | for h in headers: 22 | print(f'{h:>10s}', end=' ') 23 | print() 24 | print(('-'*10 + ' ')*len(headers)) 25 | 26 | def row(self, rowdata): 27 | for d in rowdata: 28 | print(f'{d:>10s}', end=' ') 29 | print() 30 | 31 | class CSVTableFormatter(TableFormatter): 32 | ''' 33 | Output data in CSV format. 34 | ''' 35 | def headings(self, headers): 36 | print(','.join(headers)) 37 | 38 | def row(self, rowdata): 39 | print(','.join(rowdata)) 40 | 41 | class HTMLTableFormatter(TableFormatter): 42 | ''' 43 | Output data in HTML format. 44 | ''' 45 | def headings(self, headers): 46 | print('', end='') 47 | for h in headers: 48 | print(f'{h}', end='') 49 | print('') 50 | 51 | def row(self, rowdata): 52 | print('', end='') 53 | for d in rowdata: 54 | print(f'{d}', end='') 55 | print('') 56 | 57 | class FormatError(Exception): 58 | pass 59 | 60 | def create_formatter(name): 61 | ''' 62 | Create an appropriate formatter given an output format name 63 | ''' 64 | if name == 'txt': 65 | return TextTableFormatter() 66 | elif name == 'csv': 67 | return CSVTableFormatter() 68 | elif name == 'html': 69 | return HTMLTableFormatter() 70 | else: 71 | raise FormatError(f'Unknown table format {name}') 72 | 73 | def print_table(objects, columns, formatter): 74 | ''' 75 | Make a nicely formatted table from a list of objects and attribute names. 76 | ''' 77 | formatter.headings(columns) 78 | for obj in objects: 79 | rowdata = [ str(getattr(obj, name)) for name in columns ] 80 | formatter.row(rowdata) 81 | 82 | -------------------------------------------------------------------------------- /Solutions/6_12/ticker.py: -------------------------------------------------------------------------------- 1 | # ticker.py 2 | 3 | import csv 4 | import report 5 | import tableformat 6 | from follow import follow 7 | import time 8 | 9 | def select_columns(rows, indices): 10 | for row in rows: 11 | yield [row[index] for index in indices] 12 | 13 | def convert_types(rows, types): 14 | for row in rows: 15 | yield [func(val) for func, val in zip(types, row)] 16 | 17 | def make_dicts(rows, headers): 18 | for row in rows: 19 | yield dict(zip(headers, row)) 20 | 21 | def parse_stock_data(lines): 22 | rows = csv.reader(lines) 23 | rows = select_columns(rows, [0, 1, 4]) 24 | rows = convert_types(rows, [str,float,float]) 25 | rows = make_dicts(rows, ['name','price','change']) 26 | return rows 27 | 28 | def filter_symbols(rows, names): 29 | for row in rows: 30 | if row['name'] in names: 31 | yield row 32 | 33 | def ticker(portfile, logfile, fmt): 34 | portfolio = report.read_portfolio(portfile) 35 | lines = follow(logfile) 36 | rows = parse_stock_data(lines) 37 | rows = filter_symbols(rows, portfolio) 38 | formatter = tableformat.create_formatter(fmt) 39 | formatter.headings(['Name','Price','Change']) 40 | for row in rows: 41 | formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] ) 42 | 43 | def main(args): 44 | if len(args) != 4: 45 | raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0]) 46 | ticker(args[1], args[2], args[3]) 47 | 48 | if __name__ == '__main__': 49 | import sys 50 | main(sys.argv) 51 | -------------------------------------------------------------------------------- /Solutions/6_15/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | if select and not has_headers: 9 | raise RuntimeError('select requires column headers') 10 | 11 | rows = csv.reader(lines, delimiter=delimiter) 12 | 13 | # Read the file headers (if any) 14 | headers = next(rows) if has_headers else [] 15 | 16 | # If specific columns have been selected, make indices for filtering and set output columns 17 | if select: 18 | indices = [ headers.index(colname) for colname in select ] 19 | headers = select 20 | 21 | records = [] 22 | for rowno, row in enumerate(rows, 1): 23 | if not row: # Skip rows with no data 24 | continue 25 | 26 | # If specific column indices are selected, pick them out 27 | if select: 28 | row = [ row[index] for index in indices] 29 | 30 | # Apply type conversion to the row 31 | if types: 32 | try: 33 | row = [func(val) for func, val in zip(types, row)] 34 | except ValueError as e: 35 | if not silence_errors: 36 | print(f"Row {rowno}: Couldn't convert {row}") 37 | print(f"Row {rowno}: Reason {e}") 38 | continue 39 | 40 | # Make a dictionary or a tuple 41 | if headers: 42 | record = dict(zip(headers, row)) 43 | else: 44 | record = tuple(row) 45 | records.append(record) 46 | 47 | return records 48 | -------------------------------------------------------------------------------- /Solutions/6_15/follow.py: -------------------------------------------------------------------------------- 1 | # follow.py 2 | 3 | import time 4 | import os 5 | 6 | def follow(filename): 7 | ''' 8 | Generator that produces a sequence of lines being written at the end of a file. 9 | ''' 10 | with open(filename,'r') as f: 11 | f.seek(0,os.SEEK_END) 12 | while True: 13 | line = f.readline() 14 | if line != '': 15 | yield line 16 | else: 17 | time.sleep(0.1) # Sleep briefly to avoid busy wait 18 | -------------------------------------------------------------------------------- /Solutions/6_15/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return portfolio.total_cost 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/6_15/portfolio.py: -------------------------------------------------------------------------------- 1 | # portfolio.py 2 | 3 | class Portfolio: 4 | def __init__(self, holdings): 5 | self._holdings = holdings 6 | 7 | def __iter__(self): 8 | return self._holdings.__iter__() 9 | 10 | def __len__(self): 11 | return len(self._holdings) 12 | 13 | def __getitem__(self, index): 14 | return self._holdings[index] 15 | 16 | def __contains__(self, name): 17 | return any(s.name == name for s in self._holdings) 18 | 19 | @property 20 | def total_cost(self): 21 | return sum(s.shares * s.price for s in self._holdings) 22 | 23 | def tabulate_shares(self): 24 | from collections import Counter 25 | total_shares = Counter() 26 | for s in self._holdings: 27 | total_shares[s.name] += s.shares 28 | return total_shares 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Solutions/6_15/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | import fileparse 4 | from stock import Stock 5 | import tableformat 6 | from portfolio import Portfolio 7 | 8 | def read_portfolio(filename): 9 | ''' 10 | Read a stock portfolio file into a list of dictionaries with keys 11 | name, shares, and price. 12 | ''' 13 | with open(filename) as lines: 14 | portdicts = fileparse.parse_csv(lines, 15 | select=['name','shares','price'], 16 | types=[str,int,float]) 17 | 18 | portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ] 19 | return Portfolio(portfolio) 20 | 21 | def read_prices(filename): 22 | ''' 23 | Read a CSV file of price data into a dict mapping names to prices. 24 | ''' 25 | with open(filename) as lines: 26 | return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False)) 27 | 28 | def make_report_data(portfolio, prices): 29 | ''' 30 | Make a list of (name, shares, price, change) tuples given a portfolio list 31 | and prices dictionary. 32 | ''' 33 | rows = [] 34 | for s in portfolio: 35 | current_price = prices[s.name] 36 | change = current_price - s.price 37 | summary = (s.name, s.shares, current_price, change) 38 | rows.append(summary) 39 | return rows 40 | 41 | def print_report(reportdata, formatter): 42 | ''' 43 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 44 | ''' 45 | formatter.headings(['Name','Shares','Price','Change']) 46 | for name, shares, price, change in reportdata: 47 | rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ] 48 | formatter.row(rowdata) 49 | 50 | def portfolio_report(portfoliofile, pricefile, fmt='txt'): 51 | ''' 52 | Make a stock report given portfolio and price data files. 53 | ''' 54 | # Read data files 55 | portfolio = read_portfolio(portfoliofile) 56 | prices = read_prices(pricefile) 57 | 58 | # Create the report data 59 | report = make_report_data(portfolio, prices) 60 | 61 | # Print it out 62 | formatter = tableformat.create_formatter(fmt) 63 | print_report(report, formatter) 64 | 65 | def main(args): 66 | if len(args) != 4: 67 | raise SystemExit('Usage: %s portfile pricefile format' % args[0]) 68 | portfolio_report(args[1], args[2], args[3]) 69 | 70 | if __name__ == '__main__': 71 | import sys 72 | main(sys.argv) 73 | -------------------------------------------------------------------------------- /Solutions/6_15/stock.py: -------------------------------------------------------------------------------- 1 | # stock.py 2 | 3 | class Stock: 4 | ''' 5 | An instance of a stock holding consisting of name, shares, and price. 6 | ''' 7 | __slots__ = ('name','_shares','price') 8 | def __init__(self,name, shares, price): 9 | self.name = name 10 | self.shares = shares 11 | self.price = price 12 | 13 | def __repr__(self): 14 | return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' 15 | 16 | @property 17 | def shares(self): 18 | return self._shares 19 | 20 | @shares.setter 21 | def shares(self, value): 22 | if not isinstance(value,int): 23 | raise TypeError("Must be integer") 24 | self._shares = value 25 | 26 | @property 27 | def cost(self): 28 | ''' 29 | Return the cost as shares*price 30 | ''' 31 | return self.shares * self.price 32 | 33 | def sell(self, nshares): 34 | ''' 35 | Sell a number of shares and return the remaining number. 36 | ''' 37 | self.shares -= nshares 38 | -------------------------------------------------------------------------------- /Solutions/6_15/tableformat.py: -------------------------------------------------------------------------------- 1 | # tableformat.py 2 | 3 | class TableFormatter: 4 | def headings(self, headers): 5 | ''' 6 | Emit the table headers 7 | ''' 8 | raise NotImplementedError() 9 | 10 | def row(self, rowdata): 11 | ''' 12 | Emit a single row of table data 13 | ''' 14 | raise NotImplementedError() 15 | 16 | class TextTableFormatter(TableFormatter): 17 | ''' 18 | Output data in plain-text format. 19 | ''' 20 | def headings(self, headers): 21 | for h in headers: 22 | print(f'{h:>10s}', end=' ') 23 | print() 24 | print(('-'*10 + ' ')*len(headers)) 25 | 26 | def row(self, rowdata): 27 | for d in rowdata: 28 | print(f'{d:>10s}', end=' ') 29 | print() 30 | 31 | class CSVTableFormatter(TableFormatter): 32 | ''' 33 | Output data in CSV format. 34 | ''' 35 | def headings(self, headers): 36 | print(','.join(headers)) 37 | 38 | def row(self, rowdata): 39 | print(','.join(rowdata)) 40 | 41 | class HTMLTableFormatter(TableFormatter): 42 | ''' 43 | Output data in HTML format. 44 | ''' 45 | def headings(self, headers): 46 | print('', end='') 47 | for h in headers: 48 | print(f'{h}', end='') 49 | print('') 50 | 51 | def row(self, rowdata): 52 | print('', end='') 53 | for d in rowdata: 54 | print(f'{d}', end='') 55 | print('') 56 | 57 | class FormatError(Exception): 58 | pass 59 | 60 | def create_formatter(name): 61 | ''' 62 | Create an appropriate formatter given an output format name 63 | ''' 64 | if name == 'txt': 65 | return TextTableFormatter() 66 | elif name == 'csv': 67 | return CSVTableFormatter() 68 | elif name == 'html': 69 | return HTMLTableFormatter() 70 | else: 71 | raise FormatError(f'Unknown table format {name}') 72 | 73 | def print_table(objects, columns, formatter): 74 | ''' 75 | Make a nicely formatted table from a list of objects and attribute names. 76 | ''' 77 | formatter.headings(columns) 78 | for obj in objects: 79 | rowdata = [ str(getattr(obj, name)) for name in columns ] 80 | formatter.row(rowdata) 81 | 82 | -------------------------------------------------------------------------------- /Solutions/6_15/ticker.py: -------------------------------------------------------------------------------- 1 | # ticker.py 2 | 3 | import csv 4 | import report 5 | import tableformat 6 | from follow import follow 7 | import time 8 | 9 | def select_columns(rows, indices): 10 | for row in rows: 11 | yield [row[index] for index in indices] 12 | 13 | def convert_types(rows, types): 14 | for row in rows: 15 | yield [func(val) for func, val in zip(types, row)] 16 | 17 | def make_dicts(rows, headers): 18 | return (dict(zip(headers,row)) for row in rows) 19 | 20 | def parse_stock_data(lines): 21 | rows = csv.reader(lines) 22 | rows = select_columns(rows, [0, 1, 4]) 23 | rows = convert_types(rows, [str,float,float]) 24 | rows = make_dicts(rows, ['name','price','change']) 25 | return rows 26 | 27 | def ticker(portfile, logfile, fmt): 28 | portfolio = report.read_portfolio(portfile) 29 | lines = follow(logfile) 30 | rows = parse_stock_data(lines) 31 | rows = (row for row in rows if row['name'] in portfolio) 32 | formatter = tableformat.create_formatter(fmt) 33 | formatter.headings(['Name','Price','Change']) 34 | for row in rows: 35 | formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] ) 36 | 37 | def main(args): 38 | if len(args) != 4: 39 | raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0]) 40 | ticker(args[1], args[2], args[3]) 41 | 42 | if __name__ == '__main__': 43 | import sys 44 | main(sys.argv) 45 | -------------------------------------------------------------------------------- /Solutions/6_3/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | if select and not has_headers: 9 | raise RuntimeError('select requires column headers') 10 | 11 | rows = csv.reader(lines, delimiter=delimiter) 12 | 13 | # Read the file headers (if any) 14 | headers = next(rows) if has_headers else [] 15 | 16 | # If specific columns have been selected, make indices for filtering and set output columns 17 | if select: 18 | indices = [ headers.index(colname) for colname in select ] 19 | headers = select 20 | 21 | records = [] 22 | for rowno, row in enumerate(rows, 1): 23 | if not row: # Skip rows with no data 24 | continue 25 | 26 | # If specific column indices are selected, pick them out 27 | if select: 28 | row = [ row[index] for index in indices] 29 | 30 | # Apply type conversion to the row 31 | if types: 32 | try: 33 | row = [func(val) for func, val in zip(types, row)] 34 | except ValueError as e: 35 | if not silence_errors: 36 | print(f"Row {rowno}: Couldn't convert {row}") 37 | print(f"Row {rowno}: Reason {e}") 38 | continue 39 | 40 | # Make a dictionary or a tuple 41 | if headers: 42 | record = dict(zip(headers, row)) 43 | else: 44 | record = tuple(row) 45 | records.append(record) 46 | 47 | return records 48 | -------------------------------------------------------------------------------- /Solutions/6_3/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return portfolio.total_cost 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/6_3/portfolio.py: -------------------------------------------------------------------------------- 1 | # portfolio.py 2 | 3 | class Portfolio: 4 | def __init__(self, holdings): 5 | self._holdings = holdings 6 | 7 | def __iter__(self): 8 | return self._holdings.__iter__() 9 | 10 | def __len__(self): 11 | return len(self._holdings) 12 | 13 | def __getitem__(self, index): 14 | return self._holdings[index] 15 | 16 | def __contains__(self, name): 17 | return any([s.name == name for s in self._holdings]) 18 | 19 | @property 20 | def total_cost(self): 21 | return sum([s.shares * s.price for s in self._holdings]) 22 | 23 | def tabulate_shares(self): 24 | from collections import Counter 25 | total_shares = Counter() 26 | for s in self._holdings: 27 | total_shares[s.name] += s.shares 28 | return total_shares 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Solutions/6_3/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | import fileparse 4 | from stock import Stock 5 | import tableformat 6 | from portfolio import Portfolio 7 | 8 | def read_portfolio(filename): 9 | ''' 10 | Read a stock portfolio file into a list of dictionaries with keys 11 | name, shares, and price. 12 | ''' 13 | with open(filename) as lines: 14 | portdicts = fileparse.parse_csv(lines, 15 | select=['name','shares','price'], 16 | types=[str,int,float]) 17 | 18 | portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ] 19 | return Portfolio(portfolio) 20 | 21 | def read_prices(filename): 22 | ''' 23 | Read a CSV file of price data into a dict mapping names to prices. 24 | ''' 25 | with open(filename) as lines: 26 | return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False)) 27 | 28 | def make_report_data(portfolio, prices): 29 | ''' 30 | Make a list of (name, shares, price, change) tuples given a portfolio list 31 | and prices dictionary. 32 | ''' 33 | rows = [] 34 | for s in portfolio: 35 | current_price = prices[s.name] 36 | change = current_price - s.price 37 | summary = (s.name, s.shares, current_price, change) 38 | rows.append(summary) 39 | return rows 40 | 41 | def print_report(reportdata, formatter): 42 | ''' 43 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 44 | ''' 45 | formatter.headings(['Name','Shares','Price','Change']) 46 | for name, shares, price, change in reportdata: 47 | rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ] 48 | formatter.row(rowdata) 49 | 50 | def portfolio_report(portfoliofile, pricefile, fmt='txt'): 51 | ''' 52 | Make a stock report given portfolio and price data files. 53 | ''' 54 | # Read data files 55 | portfolio = read_portfolio(portfoliofile) 56 | prices = read_prices(pricefile) 57 | 58 | # Create the report data 59 | report = make_report_data(portfolio, prices) 60 | 61 | # Print it out 62 | formatter = tableformat.create_formatter(fmt) 63 | print_report(report, formatter) 64 | 65 | def main(args): 66 | if len(args) != 4: 67 | raise SystemExit('Usage: %s portfile pricefile format' % args[0]) 68 | portfolio_report(args[1], args[2], args[3]) 69 | 70 | if __name__ == '__main__': 71 | import sys 72 | main(sys.argv) 73 | -------------------------------------------------------------------------------- /Solutions/6_3/stock.py: -------------------------------------------------------------------------------- 1 | # stock.py 2 | 3 | class Stock: 4 | ''' 5 | An instance of a stock holding consisting of name, shares, and price. 6 | ''' 7 | __slots__ = ('name','_shares','price') 8 | def __init__(self,name, shares, price): 9 | self.name = name 10 | self.shares = shares 11 | self.price = price 12 | 13 | def __repr__(self): 14 | return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' 15 | 16 | @property 17 | def shares(self): 18 | return self._shares 19 | 20 | @shares.setter 21 | def shares(self, value): 22 | if not isinstance(value,int): 23 | raise TypeError("Must be integer") 24 | self._shares = value 25 | 26 | @property 27 | def cost(self): 28 | ''' 29 | Return the cost as shares*price 30 | ''' 31 | return self.shares * self.price 32 | 33 | def sell(self, nshares): 34 | ''' 35 | Sell a number of shares and return the remaining number. 36 | ''' 37 | self.shares -= nshares 38 | -------------------------------------------------------------------------------- /Solutions/6_3/tableformat.py: -------------------------------------------------------------------------------- 1 | # tableformat.py 2 | 3 | class TableFormatter: 4 | def headings(self, headers): 5 | ''' 6 | Emit the table headers 7 | ''' 8 | raise NotImplementedError() 9 | 10 | def row(self, rowdata): 11 | ''' 12 | Emit a single row of table data 13 | ''' 14 | raise NotImplementedError() 15 | 16 | class TextTableFormatter(TableFormatter): 17 | ''' 18 | Output data in plain-text format. 19 | ''' 20 | def headings(self, headers): 21 | for h in headers: 22 | print(f'{h:>10s}', end=' ') 23 | print() 24 | print(('-'*10 + ' ')*len(headers)) 25 | 26 | def row(self, rowdata): 27 | for d in rowdata: 28 | print(f'{d:>10s}', end=' ') 29 | print() 30 | 31 | class CSVTableFormatter(TableFormatter): 32 | ''' 33 | Output data in CSV format. 34 | ''' 35 | def headings(self, headers): 36 | print(','.join(headers)) 37 | 38 | def row(self, rowdata): 39 | print(','.join(rowdata)) 40 | 41 | class HTMLTableFormatter(TableFormatter): 42 | ''' 43 | Output data in HTML format. 44 | ''' 45 | def headings(self, headers): 46 | print('', end='') 47 | for h in headers: 48 | print(f'{h}', end='') 49 | print('') 50 | 51 | def row(self, rowdata): 52 | print('', end='') 53 | for d in rowdata: 54 | print(f'{d}', end='') 55 | print('') 56 | 57 | class FormatError(Exception): 58 | pass 59 | 60 | def create_formatter(name): 61 | ''' 62 | Create an appropriate formatter given an output format name 63 | ''' 64 | if name == 'txt': 65 | return TextTableFormatter() 66 | elif name == 'csv': 67 | return CSVTableFormatter() 68 | elif name == 'html': 69 | return HTMLTableFormatter() 70 | else: 71 | raise FormatError(f'Unknown table format {name}') 72 | 73 | def print_table(objects, columns, formatter): 74 | ''' 75 | Make a nicely formatted table from a list of objects and attribute names. 76 | ''' 77 | formatter.headings(columns) 78 | for obj in objects: 79 | rowdata = [ str(getattr(obj, name)) for name in columns ] 80 | formatter.row(rowdata) 81 | 82 | -------------------------------------------------------------------------------- /Solutions/6_7/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | if select and not has_headers: 9 | raise RuntimeError('select requires column headers') 10 | 11 | rows = csv.reader(lines, delimiter=delimiter) 12 | 13 | # Read the file headers (if any) 14 | headers = next(rows) if has_headers else [] 15 | 16 | # If specific columns have been selected, make indices for filtering and set output columns 17 | if select: 18 | indices = [ headers.index(colname) for colname in select ] 19 | headers = select 20 | 21 | records = [] 22 | for rowno, row in enumerate(rows, 1): 23 | if not row: # Skip rows with no data 24 | continue 25 | 26 | # If specific column indices are selected, pick them out 27 | if select: 28 | row = [ row[index] for index in indices] 29 | 30 | # Apply type conversion to the row 31 | if types: 32 | try: 33 | row = [func(val) for func, val in zip(types, row)] 34 | except ValueError as e: 35 | if not silence_errors: 36 | print(f"Row {rowno}: Couldn't convert {row}") 37 | print(f"Row {rowno}: Reason {e}") 38 | continue 39 | 40 | # Make a dictionary or a tuple 41 | if headers: 42 | record = dict(zip(headers, row)) 43 | else: 44 | record = tuple(row) 45 | records.append(record) 46 | 47 | return records 48 | -------------------------------------------------------------------------------- /Solutions/6_7/follow.py: -------------------------------------------------------------------------------- 1 | # follow.py 2 | import os 3 | import time 4 | 5 | def follow(filename): 6 | ''' 7 | Generator that produces a sequence of lines being written at the end of a file. 8 | ''' 9 | f = open(filename, 'r') 10 | f.seek(0,os.SEEK_END) 11 | while True: 12 | line = f.readline() 13 | if line == '': 14 | time.sleep(0.1) # Sleep briefly to avoid busy wait 15 | continue 16 | yield line 17 | 18 | # Example use 19 | if __name__ == '__main__': 20 | import report 21 | 22 | portfolio = report.read_portfolio('../../Data/portfolio.csv') 23 | 24 | for line in follow('../../Data/stocklog.csv'): 25 | row = line.split(',') 26 | name = row[0].strip('"') 27 | price = float(row[1]) 28 | change = float(row[4]) 29 | if name in portfolio: 30 | print(f'{name:>10s} {price:>10.2f} {change:>10.2f}') 31 | -------------------------------------------------------------------------------- /Solutions/6_7/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return portfolio.total_cost 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/6_7/portfolio.py: -------------------------------------------------------------------------------- 1 | # portfolio.py 2 | 3 | class Portfolio: 4 | def __init__(self, holdings): 5 | self._holdings = holdings 6 | 7 | def __iter__(self): 8 | return self._holdings.__iter__() 9 | 10 | def __len__(self): 11 | return len(self._holdings) 12 | 13 | def __getitem__(self, index): 14 | return self._holdings[index] 15 | 16 | def __contains__(self, name): 17 | return any([s.name == name for s in self._holdings]) 18 | 19 | @property 20 | def total_cost(self): 21 | return sum([s.shares * s.price for s in self._holdings]) 22 | 23 | def tabulate_shares(self): 24 | from collections import Counter 25 | total_shares = Counter() 26 | for s in self._holdings: 27 | total_shares[s.name] += s.shares 28 | return total_shares 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Solutions/6_7/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | import fileparse 4 | from stock import Stock 5 | import tableformat 6 | from portfolio import Portfolio 7 | 8 | def read_portfolio(filename): 9 | ''' 10 | Read a stock portfolio file into a list of dictionaries with keys 11 | name, shares, and price. 12 | ''' 13 | with open(filename) as lines: 14 | portdicts = fileparse.parse_csv(lines, 15 | select=['name','shares','price'], 16 | types=[str,int,float]) 17 | 18 | portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ] 19 | return Portfolio(portfolio) 20 | 21 | def read_prices(filename): 22 | ''' 23 | Read a CSV file of price data into a dict mapping names to prices. 24 | ''' 25 | with open(filename) as lines: 26 | return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False)) 27 | 28 | def make_report_data(portfolio, prices): 29 | ''' 30 | Make a list of (name, shares, price, change) tuples given a portfolio list 31 | and prices dictionary. 32 | ''' 33 | rows = [] 34 | for s in portfolio: 35 | current_price = prices[s.name] 36 | change = current_price - s.price 37 | summary = (s.name, s.shares, current_price, change) 38 | rows.append(summary) 39 | return rows 40 | 41 | def print_report(reportdata, formatter): 42 | ''' 43 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 44 | ''' 45 | formatter.headings(['Name','Shares','Price','Change']) 46 | for name, shares, price, change in reportdata: 47 | rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ] 48 | formatter.row(rowdata) 49 | 50 | def portfolio_report(portfoliofile, pricefile, fmt='txt'): 51 | ''' 52 | Make a stock report given portfolio and price data files. 53 | ''' 54 | # Read data files 55 | portfolio = read_portfolio(portfoliofile) 56 | prices = read_prices(pricefile) 57 | 58 | # Create the report data 59 | report = make_report_data(portfolio, prices) 60 | 61 | # Print it out 62 | formatter = tableformat.create_formatter(fmt) 63 | print_report(report, formatter) 64 | 65 | def main(args): 66 | if len(args) != 4: 67 | raise SystemExit('Usage: %s portfile pricefile format' % args[0]) 68 | portfolio_report(args[1], args[2], args[3]) 69 | 70 | if __name__ == '__main__': 71 | import sys 72 | main(sys.argv) 73 | -------------------------------------------------------------------------------- /Solutions/6_7/stock.py: -------------------------------------------------------------------------------- 1 | # stock.py 2 | 3 | class Stock: 4 | ''' 5 | An instance of a stock holding consisting of name, shares, and price. 6 | ''' 7 | __slots__ = ('name','_shares','price') 8 | def __init__(self,name, shares, price): 9 | self.name = name 10 | self.shares = shares 11 | self.price = price 12 | 13 | def __repr__(self): 14 | return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' 15 | 16 | @property 17 | def shares(self): 18 | return self._shares 19 | 20 | @shares.setter 21 | def shares(self, value): 22 | if not isinstance(value,int): 23 | raise TypeError("Must be integer") 24 | self._shares = value 25 | 26 | @property 27 | def cost(self): 28 | ''' 29 | Return the cost as shares*price 30 | ''' 31 | return self.shares * self.price 32 | 33 | def sell(self, nshares): 34 | ''' 35 | Sell a number of shares and return the remaining number. 36 | ''' 37 | self.shares -= nshares 38 | -------------------------------------------------------------------------------- /Solutions/6_7/tableformat.py: -------------------------------------------------------------------------------- 1 | # tableformat.py 2 | 3 | class TableFormatter: 4 | def headings(self, headers): 5 | ''' 6 | Emit the table headers 7 | ''' 8 | raise NotImplementedError() 9 | 10 | def row(self, rowdata): 11 | ''' 12 | Emit a single row of table data 13 | ''' 14 | raise NotImplementedError() 15 | 16 | class TextTableFormatter(TableFormatter): 17 | ''' 18 | Output data in plain-text format. 19 | ''' 20 | def headings(self, headers): 21 | for h in headers: 22 | print(f'{h:>10s}', end=' ') 23 | print() 24 | print(('-'*10 + ' ')*len(headers)) 25 | 26 | def row(self, rowdata): 27 | for d in rowdata: 28 | print(f'{d:>10s}', end=' ') 29 | print() 30 | 31 | class CSVTableFormatter(TableFormatter): 32 | ''' 33 | Output data in CSV format. 34 | ''' 35 | def headings(self, headers): 36 | print(','.join(headers)) 37 | 38 | def row(self, rowdata): 39 | print(','.join(rowdata)) 40 | 41 | class HTMLTableFormatter(TableFormatter): 42 | ''' 43 | Output data in HTML format. 44 | ''' 45 | def headings(self, headers): 46 | print('', end='') 47 | for h in headers: 48 | print(f'{h}', end='') 49 | print('') 50 | 51 | def row(self, rowdata): 52 | print('', end='') 53 | for d in rowdata: 54 | print(f'{d}', end='') 55 | print('') 56 | 57 | class FormatError(Exception): 58 | pass 59 | 60 | def create_formatter(name): 61 | ''' 62 | Create an appropriate formatter given an output format name 63 | ''' 64 | if name == 'txt': 65 | return TextTableFormatter() 66 | elif name == 'csv': 67 | return CSVTableFormatter() 68 | elif name == 'html': 69 | return HTMLTableFormatter() 70 | else: 71 | raise FormatError(f'Unknown table format {name}') 72 | 73 | def print_table(objects, columns, formatter): 74 | ''' 75 | Make a nicely formatted table from a list of objects and attribute names. 76 | ''' 77 | formatter.headings(columns) 78 | for obj in objects: 79 | rowdata = [ str(getattr(obj, name)) for name in columns ] 80 | formatter.row(rowdata) 81 | 82 | -------------------------------------------------------------------------------- /Solutions/7_10/timethis.py: -------------------------------------------------------------------------------- 1 | # timethis.py 2 | 3 | import time 4 | 5 | def timethis(func): 6 | def wrapper(*args, **kwargs): 7 | start = time.time() 8 | try: 9 | return func(*args,**kwargs) 10 | finally: 11 | end = time.time() 12 | print("%s.%s : %f" % (func.__module__,func.__name__,end-start)) 13 | return wrapper 14 | 15 | if __name__ == '__main__': 16 | @timethis 17 | def countdown(n): 18 | while n > 0: 19 | n-= 1 20 | 21 | countdown(1000000) 22 | -------------------------------------------------------------------------------- /Solutions/7_11/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | if select and not has_headers: 9 | raise RuntimeError('select requires column headers') 10 | 11 | rows = csv.reader(lines, delimiter=delimiter) 12 | 13 | # Read the file headers (if any) 14 | headers = next(rows) if has_headers else [] 15 | 16 | # If specific columns have been selected, make indices for filtering and set output columns 17 | if select: 18 | indices = [ headers.index(colname) for colname in select ] 19 | headers = select 20 | 21 | records = [] 22 | for rowno, row in enumerate(rows, 1): 23 | if not row: # Skip rows with no data 24 | continue 25 | 26 | # If specific column indices are selected, pick them out 27 | if select: 28 | row = [ row[index] for index in indices] 29 | 30 | # Apply type conversion to the row 31 | if types: 32 | try: 33 | row = [func(val) for func, val in zip(types, row)] 34 | except ValueError as e: 35 | if not silence_errors: 36 | print(f"Row {rowno}: Couldn't convert {row}") 37 | print(f"Row {rowno}: Reason {e}") 38 | continue 39 | 40 | # Make a dictionary or a tuple 41 | if headers: 42 | record = dict(zip(headers, row)) 43 | else: 44 | record = tuple(row) 45 | records.append(record) 46 | 47 | return records 48 | -------------------------------------------------------------------------------- /Solutions/7_11/follow.py: -------------------------------------------------------------------------------- 1 | # follow.py 2 | 3 | import time 4 | import os 5 | 6 | def follow(filename): 7 | ''' 8 | Generator that produces a sequence of lines being written at the end of a file. 9 | ''' 10 | with open(filename,'r') as f: 11 | f.seek(0,os.SEEK_END) 12 | while True: 13 | line = f.readline() 14 | if line != '': 15 | yield line 16 | else: 17 | time.sleep(0.1) # Sleep briefly to avoid busy wait 18 | -------------------------------------------------------------------------------- /Solutions/7_11/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return portfolio.total_cost 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/7_11/portfolio.py: -------------------------------------------------------------------------------- 1 | # portfolio.py 2 | 3 | import fileparse 4 | import stock 5 | 6 | class Portfolio: 7 | def __init__(self): 8 | self._holdings = [] 9 | 10 | @classmethod 11 | def from_csv(cls, lines, **opts): 12 | self = cls() 13 | portdicts = fileparse.parse_csv(lines, 14 | select=['name','shares','price'], 15 | types=[str,int,float], 16 | **opts) 17 | 18 | for d in portdicts: 19 | self.append(stock.Stock(**d)) 20 | 21 | return self 22 | 23 | def append(self, holding): 24 | self._holdings.append(holding) 25 | 26 | def __iter__(self): 27 | return self._holdings.__iter__() 28 | 29 | def __len__(self): 30 | return len(self._holdings) 31 | 32 | def __getitem__(self, index): 33 | return self._holdings[index] 34 | 35 | def __contains__(self, name): 36 | return any(s.name == name for s in self._holdings) 37 | 38 | @property 39 | def total_cost(self): 40 | return sum(s.shares * s.price for s in self._holdings) 41 | 42 | def tabulate_shares(self): 43 | from collections import Counter 44 | total_shares = Counter() 45 | for s in self._holdings: 46 | total_shares[s.name] += s.shares 47 | return total_shares 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Solutions/7_11/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | import fileparse 4 | from stock import Stock 5 | from portfolio import Portfolio 6 | import tableformat 7 | 8 | def read_portfolio(filename, **opts): 9 | ''' 10 | Read a stock portfolio file into a list of dictionaries with keys 11 | name, shares, and price. 12 | ''' 13 | with open(filename) as lines: 14 | return Portfolio.from_csv(lines, **opts) 15 | 16 | def read_prices(filename, **opts): 17 | ''' 18 | Read a CSV file of price data into a dict mapping names to prices. 19 | ''' 20 | with open(filename) as lines: 21 | return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False, **opts)) 22 | 23 | def make_report(portfolio, prices): 24 | ''' 25 | Make a list of (name, shares, price, change) tuples given a portfolio list 26 | and prices dictionary. 27 | ''' 28 | rows = [] 29 | for s in portfolio: 30 | current_price = prices[s.name] 31 | change = current_price - s.price 32 | summary = (s.name, s.shares, current_price, change) 33 | rows.append(summary) 34 | return rows 35 | 36 | def print_report(reportdata, formatter): 37 | ''' 38 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 39 | ''' 40 | formatter.headings(['Name','Shares','Price','Change']) 41 | for name, shares, price, change in reportdata: 42 | rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ] 43 | formatter.row(rowdata) 44 | 45 | def portfolio_report(portfoliofile, pricefile, fmt='txt'): 46 | ''' 47 | Make a stock report given portfolio and price data files. 48 | ''' 49 | # Read data files 50 | portfolio = read_portfolio(portfoliofile) 51 | prices = read_prices(pricefile) 52 | 53 | # Create the report data 54 | report = make_report(portfolio, prices) 55 | 56 | # Print it out 57 | formatter = tableformat.create_formatter(fmt) 58 | print_report(report, formatter) 59 | 60 | def main(args): 61 | if len(args) != 4: 62 | raise SystemExit('Usage: %s portfile pricefile format' % args[0]) 63 | portfolio_report(args[1], args[2], args[3]) 64 | 65 | if __name__ == '__main__': 66 | import sys 67 | main(sys.argv) 68 | -------------------------------------------------------------------------------- /Solutions/7_11/stock.py: -------------------------------------------------------------------------------- 1 | # stock.py 2 | 3 | from typedproperty import String, Integer, Float 4 | 5 | class Stock: 6 | ''' 7 | An instance of a stock holding consisting of name, shares, and price. 8 | ''' 9 | name = String('name') 10 | shares = Integer('shares') 11 | price = Float('price') 12 | 13 | def __init__(self,name, shares, price): 14 | self.name = name 15 | self.shares = shares 16 | self.price = price 17 | 18 | def __repr__(self): 19 | return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' 20 | 21 | @property 22 | def cost(self): 23 | ''' 24 | Return the cost as shares*price 25 | ''' 26 | return self.shares * self.price 27 | 28 | def sell(self, nshares): 29 | ''' 30 | Sell a number of shares and return the remaining number. 31 | ''' 32 | self.shares -= nshares 33 | -------------------------------------------------------------------------------- /Solutions/7_11/tableformat.py: -------------------------------------------------------------------------------- 1 | # tableformat.py 2 | 3 | class TableFormatter: 4 | def headings(self, headers): 5 | ''' 6 | Emit the table headers 7 | ''' 8 | raise NotImplementedError() 9 | 10 | def row(self, rowdata): 11 | ''' 12 | Emit a single row of table data 13 | ''' 14 | raise NotImplementedError() 15 | 16 | class TextTableFormatter(TableFormatter): 17 | ''' 18 | Output data in plain-text format. 19 | ''' 20 | def headings(self, headers): 21 | for h in headers: 22 | print(f'{h:>10s}', end=' ') 23 | print() 24 | print(('-'*10 + ' ')*len(headers)) 25 | 26 | def row(self, rowdata): 27 | for d in rowdata: 28 | print(f'{d:>10s}', end=' ') 29 | print() 30 | 31 | class CSVTableFormatter(TableFormatter): 32 | ''' 33 | Output data in CSV format. 34 | ''' 35 | def headings(self, headers): 36 | print(','.join(headers)) 37 | 38 | def row(self, rowdata): 39 | print(','.join(rowdata)) 40 | 41 | class HTMLTableFormatter(TableFormatter): 42 | ''' 43 | Output data in HTML format. 44 | ''' 45 | def headings(self, headers): 46 | print('', end='') 47 | for h in headers: 48 | print(f'{h}', end='') 49 | print('') 50 | 51 | def row(self, rowdata): 52 | print('', end='') 53 | for d in rowdata: 54 | print(f'{d}', end='') 55 | print('') 56 | 57 | class FormatError(Exception): 58 | pass 59 | 60 | def create_formatter(name): 61 | ''' 62 | Create an appropriate formatter given an output format name 63 | ''' 64 | if name == 'txt': 65 | return TextTableFormatter() 66 | elif name == 'csv': 67 | return CSVTableFormatter() 68 | elif name == 'html': 69 | return HTMLTableFormatter() 70 | else: 71 | raise FormatError(f'Unknown table format {name}') 72 | 73 | def print_table(objects, columns, formatter): 74 | ''' 75 | Make a nicely formatted table from a list of objects and attribute names. 76 | ''' 77 | formatter.headings(columns) 78 | for obj in objects: 79 | rowdata = [ str(getattr(obj, name)) for name in columns ] 80 | formatter.row(rowdata) 81 | 82 | -------------------------------------------------------------------------------- /Solutions/7_11/ticker.py: -------------------------------------------------------------------------------- 1 | # ticker.py 2 | 3 | import csv 4 | import report 5 | import tableformat 6 | from follow import follow 7 | 8 | def select_columns(rows, indices): 9 | for row in rows: 10 | yield [row[index] for index in indices] 11 | 12 | def convert_types(rows, types): 13 | for row in rows: 14 | yield [func(val) for func, val in zip(types, row)] 15 | 16 | def make_dicts(rows, headers): 17 | return (dict(zip(headers,row)) for row in rows) 18 | 19 | def parse_stock_data(lines): 20 | rows = csv.reader(lines) 21 | rows = select_columns(rows, [0, 1, 4]) 22 | rows = convert_types(rows, [str,float,float]) 23 | rows = make_dicts(rows, ['name','price','change']) 24 | return rows 25 | 26 | def ticker(portfile, logfile, fmt): 27 | portfolio = report.read_portfolio(portfile) 28 | lines = follow(logfile) 29 | rows = parse_stock_data(lines) 30 | rows = (row for row in rows if row['name'] in portfolio) 31 | formatter = tableformat.create_formatter(fmt) 32 | formatter.headings(['Name','Price','Change']) 33 | for row in rows: 34 | formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] ) 35 | 36 | def main(args): 37 | if len(args) != 4: 38 | raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0]) 39 | ticker(args[1], args[2], args[3]) 40 | 41 | if __name__ == '__main__': 42 | import sys 43 | main(sys.argv) 44 | -------------------------------------------------------------------------------- /Solutions/7_11/timethis.py: -------------------------------------------------------------------------------- 1 | # timethis.py 2 | 3 | import time 4 | 5 | def timethis(func): 6 | def wrapper(*args, **kwargs): 7 | start = time.time() 8 | try: 9 | return func(*args,**kwargs) 10 | finally: 11 | end = time.time() 12 | print("%s.%s : %f" % (func.__module__,func.__name__,end-start)) 13 | return wrapper 14 | 15 | if __name__ == '__main__': 16 | @timethis 17 | def countdown(n): 18 | while n > 0: 19 | n-= 1 20 | 21 | countdown(1000000) 22 | -------------------------------------------------------------------------------- /Solutions/7_11/typedproperty.py: -------------------------------------------------------------------------------- 1 | # typedproperty.py 2 | 3 | def typedproperty(name, expected_type): 4 | private_name = '_' + name 5 | @property 6 | def prop(self): 7 | return getattr(self, private_name) 8 | 9 | @prop.setter 10 | def prop(self, value): 11 | if not isinstance(value, expected_type): 12 | raise TypeError(f'Expected {expected_type}') 13 | setattr(self, private_name, value) 14 | 15 | return prop 16 | 17 | String = lambda name: typedproperty(name, str) 18 | Integer = lambda name: typedproperty(name, int) 19 | Float = lambda name: typedproperty(name, float) 20 | 21 | # Example 22 | if __name__ == '__main__': 23 | class Stock: 24 | name = typedproperty('name', str) 25 | shares = typedproperty('shares', int) 26 | price = typedproperty('price', float) 27 | 28 | def __init__(self, name, shares, price): 29 | self.name = name 30 | self.shares = shares 31 | self.price = price 32 | 33 | 34 | class Stock2: 35 | name = String('name') 36 | shares = Integer('shares') 37 | price = Float('price') 38 | 39 | def __init__(self, name, shares, price): 40 | self.name = name 41 | self.shares = shares 42 | self.price = price 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Solutions/7_4/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | if select and not has_headers: 9 | raise RuntimeError('select requires column headers') 10 | 11 | rows = csv.reader(lines, delimiter=delimiter) 12 | 13 | # Read the file headers (if any) 14 | headers = next(rows) if has_headers else [] 15 | 16 | # If specific columns have been selected, make indices for filtering and set output columns 17 | if select: 18 | indices = [ headers.index(colname) for colname in select ] 19 | headers = select 20 | 21 | records = [] 22 | for rowno, row in enumerate(rows, 1): 23 | if not row: # Skip rows with no data 24 | continue 25 | 26 | # If specific column indices are selected, pick them out 27 | if select: 28 | row = [ row[index] for index in indices] 29 | 30 | # Apply type conversion to the row 31 | if types: 32 | try: 33 | row = [func(val) for func, val in zip(types, row)] 34 | except ValueError as e: 35 | if not silence_errors: 36 | print(f"Row {rowno}: Couldn't convert {row}") 37 | print(f"Row {rowno}: Reason {e}") 38 | continue 39 | 40 | # Make a dictionary or a tuple 41 | if headers: 42 | record = dict(zip(headers, row)) 43 | else: 44 | record = tuple(row) 45 | records.append(record) 46 | 47 | return records 48 | -------------------------------------------------------------------------------- /Solutions/7_4/follow.py: -------------------------------------------------------------------------------- 1 | # follow.py 2 | 3 | import time 4 | import os 5 | 6 | def follow(filename): 7 | ''' 8 | Generator that produces a sequence of lines being written at the end of a file. 9 | ''' 10 | with open(filename,'r') as f: 11 | f.seek(0,os.SEEK_END) 12 | while True: 13 | line = f.readline() 14 | if line != '': 15 | yield line 16 | else: 17 | time.sleep(0.1) # Sleep briefly to avoid busy wait 18 | -------------------------------------------------------------------------------- /Solutions/7_4/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return portfolio.total_cost 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/7_4/portfolio.py: -------------------------------------------------------------------------------- 1 | # portfolio.py 2 | 3 | class Portfolio: 4 | def __init__(self, holdings): 5 | self._holdings = holdings 6 | 7 | def __iter__(self): 8 | return self._holdings.__iter__() 9 | 10 | def __len__(self): 11 | return len(self._holdings) 12 | 13 | def __getitem__(self, index): 14 | return self._holdings[index] 15 | 16 | def __contains__(self, name): 17 | return any(s.name == name for s in self._holdings) 18 | 19 | @property 20 | def total_cost(self): 21 | return sum(s.shares * s.price for s in self._holdings) 22 | 23 | def tabulate_shares(self): 24 | from collections import Counter 25 | total_shares = Counter() 26 | for s in self._holdings: 27 | total_shares[s.name] += s.shares 28 | return total_shares 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Solutions/7_4/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | import fileparse 4 | from stock import Stock 5 | import tableformat 6 | from portfolio import Portfolio 7 | 8 | def read_portfolio(filename, **opts): 9 | ''' 10 | Read a stock portfolio file into a list of dictionaries with keys 11 | name, shares, and price. 12 | ''' 13 | with open(filename) as lines: 14 | portdicts = fileparse.parse_csv(lines, 15 | select=['name','shares','price'], 16 | types=[str,int,float], 17 | **opts) 18 | 19 | portfolio = [ Stock(**d) for d in portdicts ] 20 | return Portfolio(portfolio) 21 | 22 | def read_prices(filename, **opts): 23 | ''' 24 | Read a CSV file of price data into a dict mapping names to prices. 25 | ''' 26 | with open(filename) as lines: 27 | return dict(fileparse.parse_csv(lines,types=[str,float], has_headers=False, **opts)) 28 | 29 | def make_report_data(portfolio, prices): 30 | ''' 31 | Make a list of (name, shares, price, change) tuples given a portfolio list 32 | and prices dictionary. 33 | ''' 34 | rows = [] 35 | for s in portfolio: 36 | current_price = prices[s.name] 37 | change = current_price - s.price 38 | summary = (s.name, s.shares, current_price, change) 39 | rows.append(summary) 40 | return rows 41 | 42 | def print_report(reportdata, formatter): 43 | ''' 44 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 45 | ''' 46 | formatter.headings(['Name','Shares','Price','Change']) 47 | for name, shares, price, change in reportdata: 48 | rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ] 49 | formatter.row(rowdata) 50 | 51 | def portfolio_report(portfoliofile, pricefile, fmt='txt'): 52 | ''' 53 | Make a stock report given portfolio and price data files. 54 | ''' 55 | # Read data files 56 | portfolio = read_portfolio(portfoliofile) 57 | prices = read_prices(pricefile) 58 | 59 | # Create the report data 60 | report = make_report_data(portfolio, prices) 61 | 62 | # Print it out 63 | formatter = tableformat.create_formatter(fmt) 64 | print_report(report, formatter) 65 | 66 | def main(args): 67 | if len(args) != 4: 68 | raise SystemExit('Usage: %s portfile pricefile format' % args[0]) 69 | portfolio_report(args[1], args[2], args[3]) 70 | 71 | if __name__ == '__main__': 72 | import sys 73 | main(sys.argv) 74 | -------------------------------------------------------------------------------- /Solutions/7_4/stock.py: -------------------------------------------------------------------------------- 1 | # stock.py 2 | 3 | class Stock: 4 | ''' 5 | An instance of a stock holding consisting of name, shares, and price. 6 | ''' 7 | __slots__ = ('name','_shares','price') 8 | def __init__(self,name, shares, price): 9 | self.name = name 10 | self.shares = shares 11 | self.price = price 12 | 13 | def __repr__(self): 14 | return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' 15 | 16 | @property 17 | def shares(self): 18 | return self._shares 19 | 20 | @shares.setter 21 | def shares(self, value): 22 | if not isinstance(value,int): 23 | raise TypeError("Must be integer") 24 | self._shares = value 25 | 26 | @property 27 | def cost(self): 28 | ''' 29 | Return the cost as shares*price 30 | ''' 31 | return self.shares * self.price 32 | 33 | def sell(self, nshares): 34 | ''' 35 | Sell a number of shares and return the remaining number. 36 | ''' 37 | self.shares -= nshares 38 | -------------------------------------------------------------------------------- /Solutions/7_4/tableformat.py: -------------------------------------------------------------------------------- 1 | # tableformat.py 2 | 3 | class TableFormatter: 4 | def headings(self, headers): 5 | ''' 6 | Emit the table headers 7 | ''' 8 | raise NotImplementedError() 9 | 10 | def row(self, rowdata): 11 | ''' 12 | Emit a single row of table data 13 | ''' 14 | raise NotImplementedError() 15 | 16 | class TextTableFormatter(TableFormatter): 17 | ''' 18 | Output data in plain-text format. 19 | ''' 20 | def headings(self, headers): 21 | for h in headers: 22 | print(f'{h:>10s}', end=' ') 23 | print() 24 | print(('-'*10 + ' ')*len(headers)) 25 | 26 | def row(self, rowdata): 27 | for d in rowdata: 28 | print(f'{d:>10s}', end=' ') 29 | print() 30 | 31 | class CSVTableFormatter(TableFormatter): 32 | ''' 33 | Output data in CSV format. 34 | ''' 35 | def headings(self, headers): 36 | print(','.join(headers)) 37 | 38 | def row(self, rowdata): 39 | print(','.join(rowdata)) 40 | 41 | class HTMLTableFormatter(TableFormatter): 42 | ''' 43 | Output data in HTML format. 44 | ''' 45 | def headings(self, headers): 46 | print('', end='') 47 | for h in headers: 48 | print(f'{h}', end='') 49 | print('') 50 | 51 | def row(self, rowdata): 52 | print('', end='') 53 | for d in rowdata: 54 | print(f'{d}', end='') 55 | print('') 56 | 57 | class FormatError(Exception): 58 | pass 59 | 60 | def create_formatter(name): 61 | ''' 62 | Create an appropriate formatter given an output format name 63 | ''' 64 | if name == 'txt': 65 | return TextTableFormatter() 66 | elif name == 'csv': 67 | return CSVTableFormatter() 68 | elif name == 'html': 69 | return HTMLTableFormatter() 70 | else: 71 | raise FormatError(f'Unknown table format {name}') 72 | 73 | def print_table(objects, columns, formatter): 74 | ''' 75 | Make a nicely formatted table from a list of objects and attribute names. 76 | ''' 77 | formatter.headings(columns) 78 | for obj in objects: 79 | rowdata = [ str(getattr(obj, name)) for name in columns ] 80 | formatter.row(rowdata) 81 | 82 | -------------------------------------------------------------------------------- /Solutions/7_4/ticker.py: -------------------------------------------------------------------------------- 1 | # ticker.py 2 | 3 | import csv 4 | import report 5 | import tableformat 6 | from follow import follow 7 | import time 8 | 9 | def select_columns(rows, indices): 10 | for row in rows: 11 | yield [row[index] for index in indices] 12 | 13 | def convert_types(rows, types): 14 | for row in rows: 15 | yield [func(val) for func, val in zip(types, row)] 16 | 17 | def make_dicts(rows, headers): 18 | return (dict(zip(headers,row)) for row in rows) 19 | 20 | def parse_stock_data(lines): 21 | rows = csv.reader(lines) 22 | rows = select_columns(rows, [0, 1, 4]) 23 | rows = convert_types(rows, [str,float,float]) 24 | rows = make_dicts(rows, ['name','price','change']) 25 | return rows 26 | 27 | def ticker(portfile, logfile, fmt): 28 | portfolio = report.read_portfolio(portfile) 29 | lines = follow(logfile) 30 | rows = parse_stock_data(lines) 31 | rows = (row for row in rows if row['name'] in portfolio) 32 | formatter = tableformat.create_formatter(fmt) 33 | formatter.headings(['Name','Price','Change']) 34 | for row in rows: 35 | formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] ) 36 | 37 | def main(args): 38 | if len(args) != 4: 39 | raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0]) 40 | ticker(args[1], args[2], args[3]) 41 | 42 | if __name__ == '__main__': 43 | import sys 44 | main(sys.argv) 45 | -------------------------------------------------------------------------------- /Solutions/7_9/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | if select and not has_headers: 9 | raise RuntimeError('select requires column headers') 10 | 11 | rows = csv.reader(lines, delimiter=delimiter) 12 | 13 | # Read the file headers (if any) 14 | headers = next(rows) if has_headers else [] 15 | 16 | # If specific columns have been selected, make indices for filtering and set output columns 17 | if select: 18 | indices = [ headers.index(colname) for colname in select ] 19 | headers = select 20 | 21 | records = [] 22 | for rowno, row in enumerate(rows, 1): 23 | if not row: # Skip rows with no data 24 | continue 25 | 26 | # If specific column indices are selected, pick them out 27 | if select: 28 | row = [ row[index] for index in indices] 29 | 30 | # Apply type conversion to the row 31 | if types: 32 | try: 33 | row = [func(val) for func, val in zip(types, row)] 34 | except ValueError as e: 35 | if not silence_errors: 36 | print(f"Row {rowno}: Couldn't convert {row}") 37 | print(f"Row {rowno}: Reason {e}") 38 | continue 39 | 40 | # Make a dictionary or a tuple 41 | if headers: 42 | record = dict(zip(headers, row)) 43 | else: 44 | record = tuple(row) 45 | records.append(record) 46 | 47 | return records 48 | -------------------------------------------------------------------------------- /Solutions/7_9/follow.py: -------------------------------------------------------------------------------- 1 | # follow.py 2 | 3 | import time 4 | import os 5 | 6 | def follow(filename): 7 | ''' 8 | Generator that produces a sequence of lines being written at the end of a file. 9 | ''' 10 | with open(filename,'r') as f: 11 | f.seek(0,os.SEEK_END) 12 | while True: 13 | line = f.readline() 14 | if line != '': 15 | yield line 16 | else: 17 | time.sleep(0.1) # Sleep briefly to avoid busy wait 18 | -------------------------------------------------------------------------------- /Solutions/7_9/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return portfolio.total_cost 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/7_9/portfolio.py: -------------------------------------------------------------------------------- 1 | # portfolio.py 2 | 3 | class Portfolio: 4 | def __init__(self, holdings): 5 | self._holdings = holdings 6 | 7 | def __iter__(self): 8 | return self._holdings.__iter__() 9 | 10 | def __len__(self): 11 | return len(self._holdings) 12 | 13 | def __getitem__(self, index): 14 | return self._holdings[index] 15 | 16 | def __contains__(self, name): 17 | return any(s.name == name for s in self._holdings) 18 | 19 | @property 20 | def total_cost(self): 21 | return sum(s.shares * s.price for s in self._holdings) 22 | 23 | def tabulate_shares(self): 24 | from collections import Counter 25 | total_shares = Counter() 26 | for s in self._holdings: 27 | total_shares[s.name] += s.shares 28 | return total_shares 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Solutions/7_9/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | import fileparse 4 | from stock import Stock 5 | import tableformat 6 | from portfolio import Portfolio 7 | 8 | def read_portfolio(filename, **opts): 9 | ''' 10 | Read a stock portfolio file into a list of dictionaries with keys 11 | name, shares, and price. 12 | ''' 13 | with open(filename) as lines: 14 | portdicts = fileparse.parse_csv(lines, 15 | select=['name','shares','price'], 16 | types=[str,int,float], 17 | **opts) 18 | 19 | portfolio = [ Stock(**d) for d in portdicts ] 20 | return Portfolio(portfolio) 21 | 22 | def read_prices(filename, **opts): 23 | ''' 24 | Read a CSV file of price data into a dict mapping names to prices. 25 | ''' 26 | with open(filename) as lines: 27 | return dict(fileparse.parse_csv(lines,types=[str,float], has_headers=False, **opts)) 28 | 29 | def make_report_data(portfolio, prices): 30 | ''' 31 | Make a list of (name, shares, price, change) tuples given a portfolio list 32 | and prices dictionary. 33 | ''' 34 | rows = [] 35 | for s in portfolio: 36 | current_price = prices[s.name] 37 | change = current_price - s.price 38 | summary = (s.name, s.shares, current_price, change) 39 | rows.append(summary) 40 | return rows 41 | 42 | def print_report(reportdata, formatter): 43 | ''' 44 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 45 | ''' 46 | formatter.headings(['Name','Shares','Price','Change']) 47 | for name, shares, price, change in reportdata: 48 | rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ] 49 | formatter.row(rowdata) 50 | 51 | def portfolio_report(portfoliofile, pricefile, fmt='txt'): 52 | ''' 53 | Make a stock report given portfolio and price data files. 54 | ''' 55 | # Read data files 56 | portfolio = read_portfolio(portfoliofile) 57 | prices = read_prices(pricefile) 58 | 59 | # Create the report data 60 | report = make_report_data(portfolio, prices) 61 | 62 | # Print it out 63 | formatter = tableformat.create_formatter(fmt) 64 | print_report(report, formatter) 65 | 66 | def main(args): 67 | if len(args) != 4: 68 | raise SystemExit('Usage: %s portfile pricefile format' % args[0]) 69 | portfolio_report(args[1], args[2], args[3]) 70 | 71 | if __name__ == '__main__': 72 | import sys 73 | main(sys.argv) 74 | -------------------------------------------------------------------------------- /Solutions/7_9/stock.py: -------------------------------------------------------------------------------- 1 | # stock.py 2 | 3 | from typedproperty import String, Integer, Float 4 | 5 | class Stock: 6 | ''' 7 | An instance of a stock holding consisting of name, shares, and price. 8 | ''' 9 | name = String('name') 10 | shares = Integer('shares') 11 | price = Float('price') 12 | 13 | def __init__(self,name, shares, price): 14 | self.name = name 15 | self.shares = shares 16 | self.price = price 17 | 18 | def __repr__(self): 19 | return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' 20 | 21 | @property 22 | def cost(self): 23 | ''' 24 | Return the cost as shares*price 25 | ''' 26 | return self.shares * self.price 27 | 28 | def sell(self, nshares): 29 | ''' 30 | Sell a number of shares and return the remaining number. 31 | ''' 32 | self.shares -= nshares 33 | -------------------------------------------------------------------------------- /Solutions/7_9/tableformat.py: -------------------------------------------------------------------------------- 1 | # tableformat.py 2 | 3 | class TableFormatter: 4 | def headings(self, headers): 5 | ''' 6 | Emit the table headers 7 | ''' 8 | raise NotImplementedError() 9 | 10 | def row(self, rowdata): 11 | ''' 12 | Emit a single row of table data 13 | ''' 14 | raise NotImplementedError() 15 | 16 | class TextTableFormatter(TableFormatter): 17 | ''' 18 | Output data in plain-text format. 19 | ''' 20 | def headings(self, headers): 21 | for h in headers: 22 | print(f'{h:>10s}', end=' ') 23 | print() 24 | print(('-'*10 + ' ')*len(headers)) 25 | 26 | def row(self, rowdata): 27 | for d in rowdata: 28 | print(f'{d:>10s}', end=' ') 29 | print() 30 | 31 | class CSVTableFormatter(TableFormatter): 32 | ''' 33 | Output data in CSV format. 34 | ''' 35 | def headings(self, headers): 36 | print(','.join(headers)) 37 | 38 | def row(self, rowdata): 39 | print(','.join(rowdata)) 40 | 41 | class HTMLTableFormatter(TableFormatter): 42 | ''' 43 | Output data in HTML format. 44 | ''' 45 | def headings(self, headers): 46 | print('', end='') 47 | for h in headers: 48 | print(f'{h}', end='') 49 | print('') 50 | 51 | def row(self, rowdata): 52 | print('', end='') 53 | for d in rowdata: 54 | print(f'{d}', end='') 55 | print('') 56 | 57 | class FormatError(Exception): 58 | pass 59 | 60 | def create_formatter(name): 61 | ''' 62 | Create an appropriate formatter given an output format name 63 | ''' 64 | if name == 'txt': 65 | return TextTableFormatter() 66 | elif name == 'csv': 67 | return CSVTableFormatter() 68 | elif name == 'html': 69 | return HTMLTableFormatter() 70 | else: 71 | raise FormatError(f'Unknown table format {name}') 72 | 73 | def print_table(objects, columns, formatter): 74 | ''' 75 | Make a nicely formatted table from a list of objects and attribute names. 76 | ''' 77 | formatter.headings(columns) 78 | for obj in objects: 79 | rowdata = [ str(getattr(obj, name)) for name in columns ] 80 | formatter.row(rowdata) 81 | 82 | -------------------------------------------------------------------------------- /Solutions/7_9/ticker.py: -------------------------------------------------------------------------------- 1 | # ticker.py 2 | 3 | import csv 4 | import report 5 | import tableformat 6 | from follow import follow 7 | import time 8 | 9 | def select_columns(rows, indices): 10 | for row in rows: 11 | yield [row[index] for index in indices] 12 | 13 | def convert_types(rows, types): 14 | for row in rows: 15 | yield [func(val) for func, val in zip(types, row)] 16 | 17 | def make_dicts(rows, headers): 18 | return (dict(zip(headers,row)) for row in rows) 19 | 20 | def parse_stock_data(lines): 21 | rows = csv.reader(lines) 22 | rows = select_columns(rows, [0, 1, 4]) 23 | rows = convert_types(rows, [str,float,float]) 24 | rows = make_dicts(rows, ['name','price','change']) 25 | return rows 26 | 27 | def ticker(portfile, logfile, fmt): 28 | portfolio = report.read_portfolio(portfile) 29 | lines = follow(logfile) 30 | rows = parse_stock_data(lines) 31 | rows = (row for row in rows if row['name'] in portfolio) 32 | formatter = tableformat.create_formatter(fmt) 33 | formatter.headings(['Name','Price','Change']) 34 | for row in rows: 35 | formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] ) 36 | 37 | def main(args): 38 | if len(args) != 4: 39 | raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0]) 40 | ticker(args[1], args[2], args[3]) 41 | 42 | if __name__ == '__main__': 43 | import sys 44 | main(sys.argv) 45 | -------------------------------------------------------------------------------- /Solutions/7_9/typedproperty.py: -------------------------------------------------------------------------------- 1 | # typedproperty.py 2 | 3 | def typedproperty(name, expected_type): 4 | private_name = '_' + name 5 | @property 6 | def prop(self): 7 | return getattr(self, private_name) 8 | 9 | @prop.setter 10 | def prop(self, value): 11 | if not isinstance(value, expected_type): 12 | raise TypeError(f'Expected {expected_type}') 13 | setattr(self, private_name, value) 14 | 15 | return prop 16 | 17 | String = lambda name: typedproperty(name, str) 18 | Integer = lambda name: typedproperty(name, int) 19 | Float = lambda name: typedproperty(name, float) 20 | 21 | # Example 22 | if __name__ == '__main__': 23 | class Stock: 24 | name = typedproperty('name', str) 25 | shares = typedproperty('shares', int) 26 | price = typedproperty('price', float) 27 | 28 | def __init__(self, name, shares, price): 29 | self.name = name 30 | self.shares = shares 31 | self.price = price 32 | 33 | class Stock2: 34 | name = String('name') 35 | shares = Integer('shares') 36 | price = Float('price') 37 | 38 | def __init__(self, name, shares, price): 39 | self.name = name 40 | self.shares = shares 41 | self.price = price 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Solutions/8_1/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | 4 | def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 5 | ''' 6 | Parse a CSV file into a list of records with type conversion. 7 | ''' 8 | if select and not has_headers: 9 | raise RuntimeError('select requires column headers') 10 | 11 | rows = csv.reader(lines, delimiter=delimiter) 12 | 13 | # Read the file headers (if any) 14 | headers = next(rows) if has_headers else [] 15 | 16 | # If specific columns have been selected, make indices for filtering and set output columns 17 | if select: 18 | indices = [ headers.index(colname) for colname in select ] 19 | headers = select 20 | 21 | records = [] 22 | for rowno, row in enumerate(rows, 1): 23 | if not row: # Skip rows with no data 24 | continue 25 | 26 | # If specific column indices are selected, pick them out 27 | if select: 28 | row = [ row[index] for index in indices] 29 | 30 | # Apply type conversion to the row 31 | if types: 32 | try: 33 | row = [func(val) for func, val in zip(types, row)] 34 | except ValueError as e: 35 | if not silence_errors: 36 | print(f"Row {rowno}: Couldn't convert {row}") 37 | print(f"Row {rowno}: Reason {e}") 38 | continue 39 | 40 | # Make a dictionary or a tuple 41 | if headers: 42 | record = dict(zip(headers, row)) 43 | else: 44 | record = tuple(row) 45 | records.append(record) 46 | 47 | return records 48 | -------------------------------------------------------------------------------- /Solutions/8_1/follow.py: -------------------------------------------------------------------------------- 1 | # follow.py 2 | 3 | import time 4 | import os 5 | 6 | def follow(filename): 7 | ''' 8 | Generator that produces a sequence of lines being written at the end of a file. 9 | ''' 10 | with open(filename,'r') as f: 11 | f.seek(0,os.SEEK_END) 12 | while True: 13 | line = f.readline() 14 | if line != '': 15 | yield line 16 | else: 17 | time.sleep(0.1) # Sleep briefly to avoid busy wait 18 | -------------------------------------------------------------------------------- /Solutions/8_1/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return portfolio.total_cost 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/8_1/portfolio.py: -------------------------------------------------------------------------------- 1 | # portfolio.py 2 | 3 | import fileparse 4 | import stock 5 | 6 | class Portfolio: 7 | def __init__(self): 8 | self._holdings = [] 9 | 10 | @classmethod 11 | def from_csv(cls, lines, **opts): 12 | self = cls() 13 | portdicts = fileparse.parse_csv(lines, 14 | select=['name','shares','price'], 15 | types=[str,int,float], 16 | **opts) 17 | 18 | for d in portdicts: 19 | self.append(stock.Stock(**d)) 20 | 21 | return self 22 | 23 | def append(self, holding): 24 | self._holdings.append(holding) 25 | 26 | def __iter__(self): 27 | return self._holdings.__iter__() 28 | 29 | def __len__(self): 30 | return len(self._holdings) 31 | 32 | def __getitem__(self, index): 33 | return self._holdings[index] 34 | 35 | def __contains__(self, name): 36 | return any(s.name == name for s in self._holdings) 37 | 38 | @property 39 | def total_cost(self): 40 | return sum(s.shares * s.price for s in self._holdings) 41 | 42 | def tabulate_shares(self): 43 | from collections import Counter 44 | total_shares = Counter() 45 | for s in self._holdings: 46 | total_shares[s.name] += s.shares 47 | return total_shares 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Solutions/8_1/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | import fileparse 4 | from stock import Stock 5 | from portfolio import Portfolio 6 | import tableformat 7 | 8 | def read_portfolio(filename, **opts): 9 | ''' 10 | Read a stock portfolio file into a list of dictionaries with keys 11 | name, shares, and price. 12 | ''' 13 | with open(filename) as lines: 14 | return Portfolio.from_csv(lines, **opts) 15 | 16 | def read_prices(filename, **opts): 17 | ''' 18 | Read a CSV file of price data into a dict mapping names to prices. 19 | ''' 20 | with open(filename) as lines: 21 | return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False, **opts)) 22 | 23 | def make_report(portfolio, prices): 24 | ''' 25 | Make a list of (name, shares, price, change) tuples given a portfolio list 26 | and prices dictionary. 27 | ''' 28 | rows = [] 29 | for s in portfolio: 30 | current_price = prices[s.name] 31 | change = current_price - s.price 32 | summary = (s.name, s.shares, current_price, change) 33 | rows.append(summary) 34 | return rows 35 | 36 | def print_report(reportdata, formatter): 37 | ''' 38 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 39 | ''' 40 | formatter.headings(['Name','Shares','Price','Change']) 41 | for name, shares, price, change in reportdata: 42 | rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ] 43 | formatter.row(rowdata) 44 | 45 | def portfolio_report(portfoliofile, pricefile, fmt='txt'): 46 | ''' 47 | Make a stock report given portfolio and price data files. 48 | ''' 49 | # Read data files 50 | portfolio = read_portfolio(portfoliofile) 51 | prices = read_prices(pricefile) 52 | 53 | # Create the report data 54 | report = make_report(portfolio, prices) 55 | 56 | # Print it out 57 | formatter = tableformat.create_formatter(fmt) 58 | print_report(report, formatter) 59 | 60 | def main(args): 61 | if len(args) != 4: 62 | raise SystemExit('Usage: %s portfile pricefile format' % args[0]) 63 | portfolio_report(args[1], args[2], args[3]) 64 | 65 | if __name__ == '__main__': 66 | import sys 67 | main(sys.argv) 68 | -------------------------------------------------------------------------------- /Solutions/8_1/stock.py: -------------------------------------------------------------------------------- 1 | # stock.py 2 | 3 | from typedproperty import String, Integer, Float 4 | 5 | class Stock: 6 | ''' 7 | An instance of a stock holding consisting of name, shares, and price. 8 | ''' 9 | name = String('name') 10 | shares = Integer('shares') 11 | price = Float('price') 12 | 13 | def __init__(self,name, shares, price): 14 | self.name = name 15 | self.shares = shares 16 | self.price = price 17 | 18 | def __repr__(self): 19 | return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' 20 | 21 | @property 22 | def cost(self): 23 | ''' 24 | Return the cost as shares*price 25 | ''' 26 | return self.shares * self.price 27 | 28 | def sell(self, nshares): 29 | ''' 30 | Sell a number of shares and return the remaining number. 31 | ''' 32 | self.shares -= nshares 33 | -------------------------------------------------------------------------------- /Solutions/8_1/tableformat.py: -------------------------------------------------------------------------------- 1 | # tableformat.py 2 | 3 | class TableFormatter: 4 | def headings(self, headers): 5 | ''' 6 | Emit the table headers 7 | ''' 8 | raise NotImplementedError() 9 | 10 | def row(self, rowdata): 11 | ''' 12 | Emit a single row of table data 13 | ''' 14 | raise NotImplementedError() 15 | 16 | class TextTableFormatter(TableFormatter): 17 | ''' 18 | Output data in plain-text format. 19 | ''' 20 | def headings(self, headers): 21 | for h in headers: 22 | print(f'{h:>10s}', end=' ') 23 | print() 24 | print(('-'*10 + ' ')*len(headers)) 25 | 26 | def row(self, rowdata): 27 | for d in rowdata: 28 | print(f'{d:>10s}', end=' ') 29 | print() 30 | 31 | class CSVTableFormatter(TableFormatter): 32 | ''' 33 | Output data in CSV format. 34 | ''' 35 | def headings(self, headers): 36 | print(','.join(headers)) 37 | 38 | def row(self, rowdata): 39 | print(','.join(rowdata)) 40 | 41 | class HTMLTableFormatter(TableFormatter): 42 | ''' 43 | Output data in HTML format. 44 | ''' 45 | def headings(self, headers): 46 | print('', end='') 47 | for h in headers: 48 | print(f'{h}', end='') 49 | print('') 50 | 51 | def row(self, rowdata): 52 | print('', end='') 53 | for d in rowdata: 54 | print(f'{d}', end='') 55 | print('') 56 | 57 | class FormatError(Exception): 58 | pass 59 | 60 | def create_formatter(name): 61 | ''' 62 | Create an appropriate formatter given an output format name 63 | ''' 64 | if name == 'txt': 65 | return TextTableFormatter() 66 | elif name == 'csv': 67 | return CSVTableFormatter() 68 | elif name == 'html': 69 | return HTMLTableFormatter() 70 | else: 71 | raise FormatError(f'Unknown table format {name}') 72 | 73 | def print_table(objects, columns, formatter): 74 | ''' 75 | Make a nicely formatted table from a list of objects and attribute names. 76 | ''' 77 | formatter.headings(columns) 78 | for obj in objects: 79 | rowdata = [ str(getattr(obj, name)) for name in columns ] 80 | formatter.row(rowdata) 81 | 82 | -------------------------------------------------------------------------------- /Solutions/8_1/test_stock.py: -------------------------------------------------------------------------------- 1 | # test_stock.py 2 | 3 | import unittest 4 | import stock 5 | 6 | class TestStock(unittest.TestCase): 7 | def test_create(self): 8 | s = stock.Stock('GOOG', 100, 490.1) 9 | self.assertEqual(s.name, 'GOOG') 10 | self.assertEqual(s.shares, 100) 11 | self.assertEqual(s.price, 490.1) 12 | 13 | def test_cost(self): 14 | s = stock.Stock('GOOG', 100, 490.1) 15 | self.assertEqual(s.cost, 49010.0) 16 | 17 | def test_sell(self): 18 | s = stock.Stock('GOOG', 100, 490.1) 19 | s.sell(25) 20 | self.assertEqual(s.shares, 75) 21 | 22 | def test_shares_check(self): 23 | s = stock.Stock('GOOG', 100, 490.1) 24 | with self.assertRaises(TypeError): 25 | s.shares = '100' 26 | 27 | if __name__ == '__main__': 28 | unittest.main() 29 | -------------------------------------------------------------------------------- /Solutions/8_1/ticker.py: -------------------------------------------------------------------------------- 1 | # ticker.py 2 | 3 | import csv 4 | import report 5 | import tableformat 6 | from follow import follow 7 | 8 | def select_columns(rows, indices): 9 | for row in rows: 10 | yield [row[index] for index in indices] 11 | 12 | def convert_types(rows, types): 13 | for row in rows: 14 | yield [func(val) for func, val in zip(types, row)] 15 | 16 | def make_dicts(rows, headers): 17 | return (dict(zip(headers,row)) for row in rows) 18 | 19 | def parse_stock_data(lines): 20 | rows = csv.reader(lines) 21 | rows = select_columns(rows, [0, 1, 4]) 22 | rows = convert_types(rows, [str,float,float]) 23 | rows = make_dicts(rows, ['name','price','change']) 24 | return rows 25 | 26 | def ticker(portfile, logfile, fmt): 27 | portfolio = report.read_portfolio(portfile) 28 | lines = follow(logfile) 29 | rows = parse_stock_data(lines) 30 | rows = (row for row in rows if row['name'] in portfolio) 31 | formatter = tableformat.create_formatter(fmt) 32 | formatter.headings(['Name','Price','Change']) 33 | for row in rows: 34 | formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] ) 35 | 36 | def main(args): 37 | if len(args) != 4: 38 | raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0]) 39 | ticker(args[1], args[2], args[3]) 40 | 41 | if __name__ == '__main__': 42 | import sys 43 | main(sys.argv) 44 | -------------------------------------------------------------------------------- /Solutions/8_1/timethis.py: -------------------------------------------------------------------------------- 1 | # timethis.py 2 | 3 | import time 4 | 5 | def timethis(func): 6 | def wrapper(*args, **kwargs): 7 | start = time.time() 8 | try: 9 | return func(*args,**kwargs) 10 | finally: 11 | end = time.time() 12 | print("%s.%s : %f" % (func.__module__,func.__name__,end-start)) 13 | return wrapper 14 | 15 | if __name__ == '__main__': 16 | @timethis 17 | def countdown(n): 18 | while n > 0: 19 | n-= 1 20 | 21 | countdown(1000000) 22 | -------------------------------------------------------------------------------- /Solutions/8_1/typedproperty.py: -------------------------------------------------------------------------------- 1 | # typedproperty.py 2 | 3 | def typedproperty(name, expected_type): 4 | private_name = '_' + name 5 | @property 6 | def prop(self): 7 | return getattr(self, private_name) 8 | 9 | @prop.setter 10 | def prop(self, value): 11 | if not isinstance(value, expected_type): 12 | raise TypeError(f'Expected {expected_type}') 13 | setattr(self, private_name, value) 14 | 15 | return prop 16 | 17 | String = lambda name: typedproperty(name, str) 18 | Integer = lambda name: typedproperty(name, int) 19 | Float = lambda name: typedproperty(name, float) 20 | 21 | # Example 22 | if __name__ == '__main__': 23 | class Stock: 24 | name = typedproperty('name', str) 25 | shares = typedproperty('shares', int) 26 | price = typedproperty('price', float) 27 | 28 | def __init__(self, name, shares, price): 29 | self.name = name 30 | self.shares = shares 31 | self.price = price 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Solutions/8_2/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | import logging 4 | log = logging.getLogger(__name__) 5 | 6 | def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 7 | ''' 8 | Parse a CSV file into a list of records with type conversion. 9 | ''' 10 | if select and not has_headers: 11 | raise RuntimeError('select requires column headers') 12 | 13 | rows = csv.reader(lines, delimiter=delimiter) 14 | 15 | # Read the file headers (if any) 16 | headers = next(rows) if has_headers else [] 17 | 18 | # If specific columns have been selected, make indices for filtering and set output columns 19 | if select: 20 | indices = [ headers.index(colname) for colname in select ] 21 | headers = select 22 | 23 | records = [] 24 | for rowno, row in enumerate(rows, 1): 25 | if not row: # Skip rows with no data 26 | continue 27 | 28 | # If specific column indices are selected, pick them out 29 | if select: 30 | row = [ row[index] for index in indices] 31 | 32 | # Apply type conversion to the row 33 | if types: 34 | try: 35 | row = [func(val) for func, val in zip(types, row)] 36 | except ValueError as e: 37 | if not silence_errors: 38 | log.warning("Row %d: Couldn't convert %s", rowno, row) 39 | log.debug("Row %d: Reason %s", rowno, e) 40 | continue 41 | 42 | # Make a dictionary or a tuple 43 | if headers: 44 | record = dict(zip(headers, row)) 45 | else: 46 | record = tuple(row) 47 | records.append(record) 48 | 49 | return records 50 | -------------------------------------------------------------------------------- /Solutions/8_2/follow.py: -------------------------------------------------------------------------------- 1 | # follow.py 2 | 3 | import time 4 | import os 5 | 6 | def follow(filename): 7 | ''' 8 | Generator that produces a sequence of lines being written at the end of a file. 9 | ''' 10 | with open(filename,'r') as f: 11 | f.seek(0,os.SEEK_END) 12 | while True: 13 | line = f.readline() 14 | if line != '': 15 | yield line 16 | else: 17 | time.sleep(0.1) # Sleep briefly to avoid busy wait 18 | -------------------------------------------------------------------------------- /Solutions/8_2/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return portfolio.total_cost 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/8_2/portfolio.py: -------------------------------------------------------------------------------- 1 | # portfolio.py 2 | 3 | import fileparse 4 | import stock 5 | 6 | class Portfolio: 7 | def __init__(self): 8 | self._holdings = [] 9 | 10 | @classmethod 11 | def from_csv(cls, lines, **opts): 12 | self = cls() 13 | portdicts = fileparse.parse_csv(lines, 14 | select=['name','shares','price'], 15 | types=[str,int,float], 16 | **opts) 17 | 18 | for d in portdicts: 19 | self.append(stock.Stock(**d)) 20 | 21 | return self 22 | 23 | def append(self, holding): 24 | self._holdings.append(holding) 25 | 26 | def __iter__(self): 27 | return self._holdings.__iter__() 28 | 29 | def __len__(self): 30 | return len(self._holdings) 31 | 32 | def __getitem__(self, index): 33 | return self._holdings[index] 34 | 35 | def __contains__(self, name): 36 | return any(s.name == name for s in self._holdings) 37 | 38 | @property 39 | def total_cost(self): 40 | return sum(s.shares * s.price for s in self._holdings) 41 | 42 | def tabulate_shares(self): 43 | from collections import Counter 44 | total_shares = Counter() 45 | for s in self._holdings: 46 | total_shares[s.name] += s.shares 47 | return total_shares 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Solutions/8_2/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | import fileparse 4 | from stock import Stock 5 | from portfolio import Portfolio 6 | import tableformat 7 | 8 | def read_portfolio(filename, **opts): 9 | ''' 10 | Read a stock portfolio file into a list of dictionaries with keys 11 | name, shares, and price. 12 | ''' 13 | with open(filename) as lines: 14 | return Portfolio.from_csv(lines, **opts) 15 | 16 | def read_prices(filename, **opts): 17 | ''' 18 | Read a CSV file of price data into a dict mapping names to prices. 19 | ''' 20 | with open(filename) as lines: 21 | return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False, **opts)) 22 | 23 | def make_report(portfolio, prices): 24 | ''' 25 | Make a list of (name, shares, price, change) tuples given a portfolio list 26 | and prices dictionary. 27 | ''' 28 | rows = [] 29 | for s in portfolio: 30 | current_price = prices[s.name] 31 | change = current_price - s.price 32 | summary = (s.name, s.shares, current_price, change) 33 | rows.append(summary) 34 | return rows 35 | 36 | def print_report(reportdata, formatter): 37 | ''' 38 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 39 | ''' 40 | formatter.headings(['Name','Shares','Price','Change']) 41 | for name, shares, price, change in reportdata: 42 | rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ] 43 | formatter.row(rowdata) 44 | 45 | def portfolio_report(portfoliofile, pricefile, fmt='txt'): 46 | ''' 47 | Make a stock report given portfolio and price data files. 48 | ''' 49 | # Read data files 50 | portfolio = read_portfolio(portfoliofile) 51 | prices = read_prices(pricefile) 52 | 53 | # Create the report data 54 | report = make_report(portfolio, prices) 55 | 56 | # Print it out 57 | formatter = tableformat.create_formatter(fmt) 58 | print_report(report, formatter) 59 | 60 | def main(args): 61 | if len(args) != 4: 62 | raise SystemExit('Usage: %s portfile pricefile format' % args[0]) 63 | portfolio_report(args[1], args[2], args[3]) 64 | 65 | if __name__ == '__main__': 66 | import sys 67 | main(sys.argv) 68 | -------------------------------------------------------------------------------- /Solutions/8_2/stock.py: -------------------------------------------------------------------------------- 1 | # stock.py 2 | 3 | from typedproperty import String, Integer, Float 4 | 5 | class Stock: 6 | ''' 7 | An instance of a stock holding consisting of name, shares, and price. 8 | ''' 9 | name = String('name') 10 | shares = Integer('shares') 11 | price = Float('price') 12 | 13 | def __init__(self,name, shares, price): 14 | self.name = name 15 | self.shares = shares 16 | self.price = price 17 | 18 | def __repr__(self): 19 | return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' 20 | 21 | @property 22 | def cost(self): 23 | ''' 24 | Return the cost as shares*price 25 | ''' 26 | return self.shares * self.price 27 | 28 | def sell(self, nshares): 29 | ''' 30 | Sell a number of shares and return the remaining number. 31 | ''' 32 | self.shares -= nshares 33 | -------------------------------------------------------------------------------- /Solutions/8_2/tableformat.py: -------------------------------------------------------------------------------- 1 | # tableformat.py 2 | 3 | class TableFormatter: 4 | def headings(self, headers): 5 | ''' 6 | Emit the table headers 7 | ''' 8 | raise NotImplementedError() 9 | 10 | def row(self, rowdata): 11 | ''' 12 | Emit a single row of table data 13 | ''' 14 | raise NotImplementedError() 15 | 16 | class TextTableFormatter(TableFormatter): 17 | ''' 18 | Output data in plain-text format. 19 | ''' 20 | def headings(self, headers): 21 | for h in headers: 22 | print(f'{h:>10s}', end=' ') 23 | print() 24 | print(('-'*10 + ' ')*len(headers)) 25 | 26 | def row(self, rowdata): 27 | for d in rowdata: 28 | print(f'{d:>10s}', end=' ') 29 | print() 30 | 31 | class CSVTableFormatter(TableFormatter): 32 | ''' 33 | Output data in CSV format. 34 | ''' 35 | def headings(self, headers): 36 | print(','.join(headers)) 37 | 38 | def row(self, rowdata): 39 | print(','.join(rowdata)) 40 | 41 | class HTMLTableFormatter(TableFormatter): 42 | ''' 43 | Output data in HTML format. 44 | ''' 45 | def headings(self, headers): 46 | print('', end='') 47 | for h in headers: 48 | print(f'{h}', end='') 49 | print('') 50 | 51 | def row(self, rowdata): 52 | print('', end='') 53 | for d in rowdata: 54 | print(f'{d}', end='') 55 | print('') 56 | 57 | class FormatError(Exception): 58 | pass 59 | 60 | def create_formatter(name): 61 | ''' 62 | Create an appropriate formatter given an output format name 63 | ''' 64 | if name == 'txt': 65 | return TextTableFormatter() 66 | elif name == 'csv': 67 | return CSVTableFormatter() 68 | elif name == 'html': 69 | return HTMLTableFormatter() 70 | else: 71 | raise FormatError(f'Unknown table format {name}') 72 | 73 | def print_table(objects, columns, formatter): 74 | ''' 75 | Make a nicely formatted table from a list of objects and attribute names. 76 | ''' 77 | formatter.headings(columns) 78 | for obj in objects: 79 | rowdata = [ str(getattr(obj, name)) for name in columns ] 80 | formatter.row(rowdata) 81 | 82 | -------------------------------------------------------------------------------- /Solutions/8_2/test_stock.py: -------------------------------------------------------------------------------- 1 | # test_stock.py 2 | 3 | import unittest 4 | import stock 5 | 6 | class TestStock(unittest.TestCase): 7 | def test_create(self): 8 | s = stock.Stock('GOOG', 100, 490.1) 9 | self.assertEqual(s.name, 'GOOG') 10 | self.assertEqual(s.shares, 100) 11 | self.assertEqual(s.price, 490.1) 12 | 13 | def test_cost(self): 14 | s = stock.Stock('GOOG', 100, 490.1) 15 | self.assertEqual(s.cost, 49010.0) 16 | 17 | def test_sell(self): 18 | s = stock.Stock('GOOG', 100, 490.1) 19 | s.sell(25) 20 | self.assertEqual(s.shares, 75) 21 | 22 | def test_shares_check(self): 23 | s = stock.Stock('GOOG', 100, 490.1) 24 | with self.assertRaises(TypeError): 25 | s.shares = '100' 26 | 27 | if __name__ == '__main__': 28 | unittest.main() 29 | -------------------------------------------------------------------------------- /Solutions/8_2/ticker.py: -------------------------------------------------------------------------------- 1 | # ticker.py 2 | 3 | import csv 4 | import report 5 | import tableformat 6 | from follow import follow 7 | 8 | def select_columns(rows, indices): 9 | for row in rows: 10 | yield [row[index] for index in indices] 11 | 12 | def convert_types(rows, types): 13 | for row in rows: 14 | yield [func(val) for func, val in zip(types, row)] 15 | 16 | def make_dicts(rows, headers): 17 | return (dict(zip(headers,row)) for row in rows) 18 | 19 | def parse_stock_data(lines): 20 | rows = csv.reader(lines) 21 | rows = select_columns(rows, [0, 1, 4]) 22 | rows = convert_types(rows, [str,float,float]) 23 | rows = make_dicts(rows, ['name','price','change']) 24 | return rows 25 | 26 | def ticker(portfile, logfile, fmt): 27 | portfolio = report.read_portfolio(portfile) 28 | lines = follow(logfile) 29 | rows = parse_stock_data(lines) 30 | rows = (row for row in rows if row['name'] in portfolio) 31 | formatter = tableformat.create_formatter(fmt) 32 | formatter.headings(['Name','Price','Change']) 33 | for row in rows: 34 | formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] ) 35 | 36 | def main(args): 37 | if len(args) != 4: 38 | raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0]) 39 | ticker(args[1], args[2], args[3]) 40 | 41 | if __name__ == '__main__': 42 | import sys 43 | main(sys.argv) 44 | -------------------------------------------------------------------------------- /Solutions/8_2/timethis.py: -------------------------------------------------------------------------------- 1 | # timethis.py 2 | 3 | import time 4 | 5 | def timethis(func): 6 | def wrapper(*args, **kwargs): 7 | start = time.time() 8 | try: 9 | return func(*args,**kwargs) 10 | finally: 11 | end = time.time() 12 | print("%s.%s : %f" % (func.__module__,func.__name__,end-start)) 13 | return wrapper 14 | 15 | if __name__ == '__main__': 16 | @timethis 17 | def countdown(n): 18 | while n > 0: 19 | n-= 1 20 | 21 | countdown(1000000) 22 | -------------------------------------------------------------------------------- /Solutions/8_2/typedproperty.py: -------------------------------------------------------------------------------- 1 | # typedproperty.py 2 | 3 | def typedproperty(name, expected_type): 4 | private_name = '_' + name 5 | @property 6 | def prop(self): 7 | return getattr(self, private_name) 8 | 9 | @prop.setter 10 | def prop(self, value): 11 | if not isinstance(value, expected_type): 12 | raise TypeError(f'Expected {expected_type}') 13 | setattr(self, private_name, value) 14 | 15 | return prop 16 | 17 | String = lambda name: typedproperty(name, str) 18 | Integer = lambda name: typedproperty(name, int) 19 | Float = lambda name: typedproperty(name, float) 20 | 21 | # Example 22 | if __name__ == '__main__': 23 | class Stock: 24 | name = typedproperty('name', str) 25 | shares = typedproperty('shares', int) 26 | price = typedproperty('price', float) 27 | 28 | def __init__(self, name, shares, price): 29 | self.name = name 30 | self.shares = shares 31 | self.price = price 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Solutions/9_3/porty-app/README.txt: -------------------------------------------------------------------------------- 1 | Code from Practical Python. 2 | 3 | The "porty" directory is a Python package of code that's loaded via 4 | import. The "print-report.py" program is a top-level script that 5 | produces a report. Try it: 6 | 7 | shell % python3 print-report.py portfolio.csv prices.csv txt 8 | Name Shares Price Change 9 | ---------- ---------- ---------- ---------- 10 | AA 100 9.22 -22.98 11 | IBM 50 106.28 15.18 12 | CAT 150 35.46 -47.98 13 | MSFT 200 20.89 -30.34 14 | GE 95 13.48 -26.89 15 | MSFT 50 20.89 -44.21 16 | IBM 100 106.28 35.84 17 | shell % 18 | -------------------------------------------------------------------------------- /Solutions/9_3/porty-app/portfolio.csv: -------------------------------------------------------------------------------- 1 | name,shares,price 2 | "AA",100,32.20 3 | "IBM",50,91.10 4 | "CAT",150,83.44 5 | "MSFT",200,51.23 6 | "GE",95,40.37 7 | "MSFT",50,65.10 8 | "IBM",100,70.44 9 | -------------------------------------------------------------------------------- /Solutions/9_3/porty-app/porty/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabeaz-course/practical-python/3c00ff81b7b61eae7ef79a760d60687f9fcf0a81/Solutions/9_3/porty-app/porty/__init__.py -------------------------------------------------------------------------------- /Solutions/9_3/porty-app/porty/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | import logging 4 | log = logging.getLogger(__name__) 5 | 6 | def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 7 | ''' 8 | Parse a CSV file into a list of records with type conversion. 9 | ''' 10 | assert not (select and not has_headers), 'select requires column headers' 11 | rows = csv.reader(lines, delimiter=delimiter) 12 | 13 | # Read the file headers (if any) 14 | headers = next(rows) if has_headers else [] 15 | 16 | # If specific columns have been selected, make indices for filtering and set output columns 17 | if select: 18 | indices = [ headers.index(colname) for colname in select ] 19 | headers = select 20 | 21 | records = [] 22 | for rowno, row in enumerate(rows, 1): 23 | if not row: # Skip rows with no data 24 | continue 25 | 26 | # If specific column indices are selected, pick them out 27 | if select: 28 | row = [ row[index] for index in indices] 29 | 30 | # Apply type conversion to the row 31 | if types: 32 | try: 33 | row = [func(val) for func, val in zip(types, row)] 34 | except ValueError as e: 35 | if not silence_errors: 36 | log.warning("Row %d: Couldn't convert %s", rowno, row) 37 | log.debug("Row %d: Reason %s", rowno, e) 38 | continue 39 | 40 | # Make a dictionary or a tuple 41 | if headers: 42 | record = dict(zip(headers, row)) 43 | else: 44 | record = tuple(row) 45 | records.append(record) 46 | 47 | return records 48 | -------------------------------------------------------------------------------- /Solutions/9_3/porty-app/porty/follow.py: -------------------------------------------------------------------------------- 1 | # follow.py 2 | 3 | import time 4 | import os 5 | 6 | def follow(filename): 7 | ''' 8 | Generator that produces a sequence of lines being written at the end of a file. 9 | ''' 10 | with open(filename,'r') as f: 11 | f.seek(0,os.SEEK_END) 12 | while True: 13 | line = f.readline() 14 | if line != '': 15 | yield line 16 | else: 17 | time.sleep(0.1) # Sleep briefly to avoid busy wait 18 | -------------------------------------------------------------------------------- /Solutions/9_3/porty-app/porty/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | from . import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return portfolio.total_cost 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/9_3/porty-app/porty/portfolio.py: -------------------------------------------------------------------------------- 1 | # portfolio.py 2 | 3 | from . import fileparse 4 | from . import stock 5 | 6 | class Portfolio: 7 | def __init__(self): 8 | self._holdings = [] 9 | 10 | @classmethod 11 | def from_csv(cls, lines, **opts): 12 | self = cls() 13 | portdicts = fileparse.parse_csv(lines, 14 | select=['name','shares','price'], 15 | types=[str,int,float], 16 | **opts) 17 | 18 | for d in portdicts: 19 | self.append(stock.Stock(**d)) 20 | 21 | return self 22 | 23 | def append(self, holding): 24 | self._holdings.append(holding) 25 | 26 | def __iter__(self): 27 | return self._holdings.__iter__() 28 | 29 | def __len__(self): 30 | return len(self._holdings) 31 | 32 | def __getitem__(self, index): 33 | return self._holdings[index] 34 | 35 | def __contains__(self, name): 36 | return any(s.name == name for s in self._holdings) 37 | 38 | @property 39 | def total_cost(self): 40 | return sum(s.shares * s.price for s in self._holdings) 41 | 42 | def tabulate_shares(self): 43 | from collections import Counter 44 | total_shares = Counter() 45 | for s in self._holdings: 46 | total_shares[s.name] += s.shares 47 | return total_shares 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Solutions/9_3/porty-app/porty/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | from . import fileparse 4 | from .stock import Stock 5 | from .portfolio import Portfolio 6 | from . import tableformat 7 | 8 | def read_portfolio(filename, **opts): 9 | ''' 10 | Read a stock portfolio file into a list of dictionaries with keys 11 | name, shares, and price. 12 | ''' 13 | with open(filename) as lines: 14 | return Portfolio.from_csv(lines, **opts) 15 | 16 | def read_prices(filename, **opts): 17 | ''' 18 | Read a CSV file of price data into a dict mapping names to prices. 19 | ''' 20 | with open(filename) as lines: 21 | return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False, **opts)) 22 | 23 | def make_report(portfolio, prices): 24 | ''' 25 | Make a list of (name, shares, price, change) tuples given a portfolio list 26 | and prices dictionary. 27 | ''' 28 | rows = [] 29 | for s in portfolio: 30 | current_price = prices[s.name] 31 | change = current_price - s.price 32 | summary = (s.name, s.shares, current_price, change) 33 | rows.append(summary) 34 | return rows 35 | 36 | def print_report(reportdata, formatter): 37 | ''' 38 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 39 | ''' 40 | formatter.headings(['Name','Shares','Price','Change']) 41 | for name, shares, price, change in reportdata: 42 | rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ] 43 | formatter.row(rowdata) 44 | 45 | def portfolio_report(portfoliofile, pricefile, fmt='txt'): 46 | ''' 47 | Make a stock report given portfolio and price data files. 48 | ''' 49 | # Read data files 50 | portfolio = read_portfolio(portfoliofile) 51 | prices = read_prices(pricefile) 52 | 53 | # Create the report data 54 | report = make_report(portfolio, prices) 55 | 56 | # Print it out 57 | formatter = tableformat.create_formatter(fmt) 58 | print_report(report, formatter) 59 | 60 | def main(args): 61 | if len(args) != 4: 62 | raise SystemExit('Usage: %s portfile pricefile format' % args[0]) 63 | portfolio_report(args[1], args[2], args[3]) 64 | 65 | if __name__ == '__main__': 66 | import sys 67 | main(sys.argv) 68 | -------------------------------------------------------------------------------- /Solutions/9_3/porty-app/porty/stock.py: -------------------------------------------------------------------------------- 1 | # stock.py 2 | 3 | from .typedproperty import String, Integer, Float 4 | 5 | class Stock: 6 | ''' 7 | An instance of a stock holding consisting of name, shares, and price. 8 | ''' 9 | if __debug__: 10 | name = String('name') 11 | shares = Integer('shares') 12 | price = Float('price') 13 | 14 | def __init__(self,name, shares, price): 15 | self.name = name 16 | self.shares = shares 17 | self.price = price 18 | 19 | def __repr__(self): 20 | return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' 21 | 22 | @property 23 | def cost(self): 24 | ''' 25 | Return the cost as shares*price 26 | ''' 27 | return self.shares * self.price 28 | 29 | def sell(self, nshares): 30 | ''' 31 | Sell a number of shares and return the remaining number. 32 | ''' 33 | self.shares -= nshares 34 | -------------------------------------------------------------------------------- /Solutions/9_3/porty-app/porty/tableformat.py: -------------------------------------------------------------------------------- 1 | # tableformat.py 2 | 3 | class TableFormatter: 4 | def headings(self, headers): 5 | ''' 6 | Emit the table headers 7 | ''' 8 | raise NotImplementedError() 9 | 10 | def row(self, rowdata): 11 | ''' 12 | Emit a single row of table data 13 | ''' 14 | raise NotImplementedError() 15 | 16 | class TextTableFormatter(TableFormatter): 17 | ''' 18 | Output data in plain-text format. 19 | ''' 20 | def headings(self, headers): 21 | for h in headers: 22 | print(f'{h:>10s}', end=' ') 23 | print() 24 | print(('-'*10 + ' ')*len(headers)) 25 | 26 | def row(self, rowdata): 27 | for d in rowdata: 28 | print(f'{d:>10s}', end=' ') 29 | print() 30 | 31 | class CSVTableFormatter(TableFormatter): 32 | ''' 33 | Output data in CSV format. 34 | ''' 35 | def headings(self, headers): 36 | print(','.join(headers)) 37 | 38 | def row(self, rowdata): 39 | print(','.join(rowdata)) 40 | 41 | class HTMLTableFormatter(TableFormatter): 42 | ''' 43 | Output data in HTML format. 44 | ''' 45 | def headings(self, headers): 46 | print('', end='') 47 | for h in headers: 48 | print(f'{h}', end='') 49 | print('') 50 | 51 | def row(self, rowdata): 52 | print('', end='') 53 | for d in rowdata: 54 | print(f'{d}', end='') 55 | print('') 56 | 57 | class FormatError(Exception): 58 | pass 59 | 60 | def create_formatter(name): 61 | ''' 62 | Create an appropriate formatter given an output format name 63 | ''' 64 | if name == 'txt': 65 | return TextTableFormatter() 66 | elif name == 'csv': 67 | return CSVTableFormatter() 68 | elif name == 'html': 69 | return HTMLTableFormatter() 70 | else: 71 | raise FormatError(f'Unknown table format {name}') 72 | 73 | def print_table(objects, columns, formatter): 74 | ''' 75 | Make a nicely formatted table from a list of objects and attribute names. 76 | ''' 77 | formatter.headings(columns) 78 | for obj in objects: 79 | rowdata = [ str(getattr(obj, name)) for name in columns ] 80 | formatter.row(rowdata) 81 | 82 | -------------------------------------------------------------------------------- /Solutions/9_3/porty-app/porty/test_stock.py: -------------------------------------------------------------------------------- 1 | # test_stock.py 2 | 3 | import unittest 4 | from . import stock 5 | 6 | class TestStock(unittest.TestCase): 7 | def test_create(self): 8 | s = stock.Stock('GOOG', 100, 490.1) 9 | self.assertEqual(s.name, 'GOOG') 10 | self.assertEqual(s.shares, 100) 11 | self.assertEqual(s.price, 490.1) 12 | 13 | def test_cost(self): 14 | s = stock.Stock('GOOG', 100, 490.1) 15 | self.assertEqual(s.cost, 49010.0) 16 | 17 | def test_sell(self): 18 | s = stock.Stock('GOOG', 100, 490.1) 19 | s.sell(25) 20 | self.assertEqual(s.shares, 75) 21 | 22 | def test_shares_check(self): 23 | s = stock.Stock('GOOG', 100, 490.1) 24 | with self.assertRaises(TypeError): 25 | s.shares = '100' 26 | 27 | if __name__ == '__main__': 28 | unittest.main() 29 | -------------------------------------------------------------------------------- /Solutions/9_3/porty-app/porty/ticker.py: -------------------------------------------------------------------------------- 1 | # ticker.py 2 | 3 | import csv 4 | from . import report 5 | from . import tableformat 6 | from .follow import follow 7 | 8 | def select_columns(rows, indices): 9 | for row in rows: 10 | yield [row[index] for index in indices] 11 | 12 | def convert_types(rows, types): 13 | for row in rows: 14 | yield [func(val) for func, val in zip(types, row)] 15 | 16 | def make_dicts(rows, headers): 17 | return (dict(zip(headers,row)) for row in rows) 18 | 19 | def parse_stock_data(lines): 20 | rows = csv.reader(lines) 21 | rows = select_columns(rows, [0, 1, 4]) 22 | rows = convert_types(rows, [str,float,float]) 23 | rows = make_dicts(rows, ['name','price','change']) 24 | return rows 25 | 26 | def ticker(portfile, logfile, fmt): 27 | portfolio = report.read_portfolio(portfile) 28 | lines = follow(logfile) 29 | rows = parse_stock_data(lines) 30 | rows = (row for row in rows if row['name'] in portfolio) 31 | formatter = tableformat.create_formatter(fmt) 32 | formatter.headings(['Name','Price','Change']) 33 | for row in rows: 34 | formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] ) 35 | 36 | def main(args): 37 | if len(args) != 4: 38 | raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0]) 39 | ticker(args[1], args[2], args[3]) 40 | 41 | if __name__ == '__main__': 42 | import sys 43 | main(sys.argv) 44 | -------------------------------------------------------------------------------- /Solutions/9_3/porty-app/porty/typedproperty.py: -------------------------------------------------------------------------------- 1 | # typedproperty.py 2 | 3 | def typedproperty(name, expected_type): 4 | private_name = '_' + name 5 | @property 6 | def prop(self): 7 | return getattr(self, private_name) 8 | 9 | @prop.setter 10 | def prop(self, value): 11 | if not isinstance(value, expected_type): 12 | raise TypeError(f'Expected {expected_type}') 13 | setattr(self, private_name, value) 14 | 15 | return prop 16 | 17 | String = lambda name: typedproperty(name, str) 18 | Integer = lambda name: typedproperty(name, int) 19 | Float = lambda name: typedproperty(name, float) 20 | 21 | # Example 22 | if __name__ == '__main__': 23 | class Stock: 24 | name = typedproperty('name', str) 25 | shares = typedproperty('shares', int) 26 | price = typedproperty('price', float) 27 | 28 | def __init__(self, name, shares, price): 29 | self.name = name 30 | self.shares = shares 31 | self.price = price 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Solutions/9_3/porty-app/prices.csv: -------------------------------------------------------------------------------- 1 | "AA",9.22 2 | "AXP",24.85 3 | "BA",44.85 4 | "BAC",11.27 5 | "C",3.72 6 | "CAT",35.46 7 | "CVX",66.67 8 | "DD",28.47 9 | "DIS",24.22 10 | "GE",13.48 11 | "GM",0.75 12 | "HD",23.16 13 | "HPQ",34.35 14 | "IBM",106.28 15 | "INTC",15.72 16 | "JNJ",55.16 17 | "JPM",36.90 18 | "KFT",26.11 19 | "KO",49.16 20 | "MCD",58.99 21 | "MMM",57.10 22 | "MRK",27.58 23 | "MSFT",20.89 24 | "PFE",15.19 25 | "PG",51.94 26 | "T",24.79 27 | "UTX",52.61 28 | "VZ",29.26 29 | "WMT",49.74 30 | "XOM",69.35 31 | 32 | -------------------------------------------------------------------------------- /Solutions/9_3/porty-app/print-report.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # print-report.py 3 | 4 | import sys 5 | from porty.report import main 6 | main(sys.argv) 7 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.csv 2 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/README.txt: -------------------------------------------------------------------------------- 1 | Code from Practical Python. 2 | 3 | The "porty" directory is a Python package of code that's loaded via 4 | import. The "print-report.py" program is a top-level script that 5 | produces a report. Try it: 6 | 7 | shell % python3 print-report.py portfolio.csv prices.csv txt 8 | Name Shares Price Change 9 | ---------- ---------- ---------- ---------- 10 | AA 100 9.22 -22.98 11 | IBM 50 106.28 15.18 12 | CAT 150 35.46 -47.98 13 | MSFT 200 20.89 -30.34 14 | GE 95 13.48 -26.89 15 | MSFT 50 20.89 -44.21 16 | IBM 100 106.28 35.84 17 | shell % 18 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/portfolio.csv: -------------------------------------------------------------------------------- 1 | name,shares,price 2 | "AA",100,32.20 3 | "IBM",50,91.10 4 | "CAT",150,83.44 5 | "MSFT",200,51.23 6 | "GE",95,40.37 7 | "MSFT",50,65.10 8 | "IBM",100,70.44 9 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/porty/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabeaz-course/practical-python/3c00ff81b7b61eae7ef79a760d60687f9fcf0a81/Solutions/9_5/porty-app/porty/__init__.py -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/porty/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | import csv 3 | import logging 4 | log = logging.getLogger(__name__) 5 | 6 | def parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False): 7 | ''' 8 | Parse a CSV file into a list of records with type conversion. 9 | ''' 10 | assert not (select and not has_headers), 'select requires column headers' 11 | rows = csv.reader(lines, delimiter=delimiter) 12 | 13 | # Read the file headers (if any) 14 | headers = next(rows) if has_headers else [] 15 | 16 | # If specific columns have been selected, make indices for filtering and set output columns 17 | if select: 18 | indices = [ headers.index(colname) for colname in select ] 19 | headers = select 20 | 21 | records = [] 22 | for rowno, row in enumerate(rows, 1): 23 | if not row: # Skip rows with no data 24 | continue 25 | 26 | # If specific column indices are selected, pick them out 27 | if select: 28 | row = [ row[index] for index in indices] 29 | 30 | # Apply type conversion to the row 31 | if types: 32 | try: 33 | row = [func(val) for func, val in zip(types, row)] 34 | except ValueError as e: 35 | if not silence_errors: 36 | log.warning("Row %d: Couldn't convert %s", rowno, row) 37 | log.debug("Row %d: Reason %s", rowno, e) 38 | continue 39 | 40 | # Make a dictionary or a tuple 41 | if headers: 42 | record = dict(zip(headers, row)) 43 | else: 44 | record = tuple(row) 45 | records.append(record) 46 | 47 | return records 48 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/porty/follow.py: -------------------------------------------------------------------------------- 1 | # follow.py 2 | 3 | import time 4 | import os 5 | 6 | def follow(filename): 7 | ''' 8 | Generator that produces a sequence of lines being written at the end of a file. 9 | ''' 10 | with open(filename,'r') as f: 11 | f.seek(0,os.SEEK_END) 12 | while True: 13 | line = f.readline() 14 | if line != '': 15 | yield line 16 | else: 17 | time.sleep(0.1) # Sleep briefly to avoid busy wait 18 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/porty/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | 3 | from . import report 4 | 5 | def portfolio_cost(filename): 6 | ''' 7 | Computes the total cost (shares*price) of a portfolio file 8 | ''' 9 | portfolio = report.read_portfolio(filename) 10 | return portfolio.total_cost 11 | 12 | def main(args): 13 | if len(args) != 2: 14 | raise SystemExit('Usage: %s portfoliofile' % args[0]) 15 | filename = args[1] 16 | print('Total cost:', portfolio_cost(filename)) 17 | 18 | if __name__ == '__main__': 19 | import sys 20 | main(sys.argv) 21 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/porty/portfolio.py: -------------------------------------------------------------------------------- 1 | # portfolio.py 2 | 3 | from . import fileparse 4 | from . import stock 5 | 6 | class Portfolio: 7 | def __init__(self): 8 | self._holdings = [] 9 | 10 | @classmethod 11 | def from_csv(cls, lines, **opts): 12 | self = cls() 13 | portdicts = fileparse.parse_csv(lines, 14 | select=['name','shares','price'], 15 | types=[str,int,float], 16 | **opts) 17 | 18 | for d in portdicts: 19 | self.append(stock.Stock(**d)) 20 | 21 | return self 22 | 23 | def append(self, holding): 24 | self._holdings.append(holding) 25 | 26 | def __iter__(self): 27 | return self._holdings.__iter__() 28 | 29 | def __len__(self): 30 | return len(self._holdings) 31 | 32 | def __getitem__(self, index): 33 | return self._holdings[index] 34 | 35 | def __contains__(self, name): 36 | return any(s.name == name for s in self._holdings) 37 | 38 | @property 39 | def total_cost(self): 40 | return sum(s.shares * s.price for s in self._holdings) 41 | 42 | def tabulate_shares(self): 43 | from collections import Counter 44 | total_shares = Counter() 45 | for s in self._holdings: 46 | total_shares[s.name] += s.shares 47 | return total_shares 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/porty/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | 3 | from . import fileparse 4 | from .stock import Stock 5 | from .portfolio import Portfolio 6 | from . import tableformat 7 | 8 | def read_portfolio(filename, **opts): 9 | ''' 10 | Read a stock portfolio file into a list of dictionaries with keys 11 | name, shares, and price. 12 | ''' 13 | with open(filename) as lines: 14 | return Portfolio.from_csv(lines, **opts) 15 | 16 | def read_prices(filename, **opts): 17 | ''' 18 | Read a CSV file of price data into a dict mapping names to prices. 19 | ''' 20 | with open(filename) as lines: 21 | return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False, **opts)) 22 | 23 | def make_report(portfolio, prices): 24 | ''' 25 | Make a list of (name, shares, price, change) tuples given a portfolio list 26 | and prices dictionary. 27 | ''' 28 | rows = [] 29 | for s in portfolio: 30 | current_price = prices[s.name] 31 | change = current_price - s.price 32 | summary = (s.name, s.shares, current_price, change) 33 | rows.append(summary) 34 | return rows 35 | 36 | def print_report(reportdata, formatter): 37 | ''' 38 | Print a nicely formated table from a list of (name, shares, price, change) tuples. 39 | ''' 40 | formatter.headings(['Name','Shares','Price','Change']) 41 | for name, shares, price, change in reportdata: 42 | rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ] 43 | formatter.row(rowdata) 44 | 45 | def portfolio_report(portfoliofile, pricefile, fmt='txt'): 46 | ''' 47 | Make a stock report given portfolio and price data files. 48 | ''' 49 | # Read data files 50 | portfolio = read_portfolio(portfoliofile) 51 | prices = read_prices(pricefile) 52 | 53 | # Create the report data 54 | report = make_report(portfolio, prices) 55 | 56 | # Print it out 57 | formatter = tableformat.create_formatter(fmt) 58 | print_report(report, formatter) 59 | 60 | def main(args): 61 | if len(args) != 4: 62 | raise SystemExit('Usage: %s portfile pricefile format' % args[0]) 63 | portfolio_report(args[1], args[2], args[3]) 64 | 65 | if __name__ == '__main__': 66 | import sys 67 | main(sys.argv) 68 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/porty/stock.py: -------------------------------------------------------------------------------- 1 | # stock.py 2 | 3 | from .typedproperty import String, Integer, Float 4 | 5 | class Stock: 6 | ''' 7 | An instance of a stock holding consisting of name, shares, and price. 8 | ''' 9 | if __debug__: 10 | name = String('name') 11 | shares = Integer('shares') 12 | price = Float('price') 13 | 14 | def __init__(self,name, shares, price): 15 | self.name = name 16 | self.shares = shares 17 | self.price = price 18 | 19 | def __repr__(self): 20 | return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})' 21 | 22 | @property 23 | def cost(self): 24 | ''' 25 | Return the cost as shares*price 26 | ''' 27 | return self.shares * self.price 28 | 29 | def sell(self, nshares): 30 | ''' 31 | Sell a number of shares and return the remaining number. 32 | ''' 33 | self.shares -= nshares 34 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/porty/tableformat.py: -------------------------------------------------------------------------------- 1 | # tableformat.py 2 | 3 | class TableFormatter: 4 | def headings(self, headers): 5 | ''' 6 | Emit the table headers 7 | ''' 8 | raise NotImplementedError() 9 | 10 | def row(self, rowdata): 11 | ''' 12 | Emit a single row of table data 13 | ''' 14 | raise NotImplementedError() 15 | 16 | class TextTableFormatter(TableFormatter): 17 | ''' 18 | Output data in plain-text format. 19 | ''' 20 | def headings(self, headers): 21 | for h in headers: 22 | print(f'{h:>10s}', end=' ') 23 | print() 24 | print(('-'*10 + ' ')*len(headers)) 25 | 26 | def row(self, rowdata): 27 | for d in rowdata: 28 | print(f'{d:>10s}', end=' ') 29 | print() 30 | 31 | class CSVTableFormatter(TableFormatter): 32 | ''' 33 | Output data in CSV format. 34 | ''' 35 | def headings(self, headers): 36 | print(','.join(headers)) 37 | 38 | def row(self, rowdata): 39 | print(','.join(rowdata)) 40 | 41 | class HTMLTableFormatter(TableFormatter): 42 | ''' 43 | Output data in HTML format. 44 | ''' 45 | def headings(self, headers): 46 | print('', end='') 47 | for h in headers: 48 | print(f'{h}', end='') 49 | print('') 50 | 51 | def row(self, rowdata): 52 | print('', end='') 53 | for d in rowdata: 54 | print(f'{d}', end='') 55 | print('') 56 | 57 | class FormatError(Exception): 58 | pass 59 | 60 | def create_formatter(name): 61 | ''' 62 | Create an appropriate formatter given an output format name 63 | ''' 64 | if name == 'txt': 65 | return TextTableFormatter() 66 | elif name == 'csv': 67 | return CSVTableFormatter() 68 | elif name == 'html': 69 | return HTMLTableFormatter() 70 | else: 71 | raise FormatError(f'Unknown table format {name}') 72 | 73 | def print_table(objects, columns, formatter): 74 | ''' 75 | Make a nicely formatted table from a list of objects and attribute names. 76 | ''' 77 | formatter.headings(columns) 78 | for obj in objects: 79 | rowdata = [ str(getattr(obj, name)) for name in columns ] 80 | formatter.row(rowdata) 81 | 82 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/porty/test_stock.py: -------------------------------------------------------------------------------- 1 | # test_stock.py 2 | 3 | import unittest 4 | from . import stock 5 | 6 | class TestStock(unittest.TestCase): 7 | def test_create(self): 8 | s = stock.Stock('GOOG', 100, 490.1) 9 | self.assertEqual(s.name, 'GOOG') 10 | self.assertEqual(s.shares, 100) 11 | self.assertEqual(s.price, 490.1) 12 | 13 | def test_cost(self): 14 | s = stock.Stock('GOOG', 100, 490.1) 15 | self.assertEqual(s.cost, 49010.0) 16 | 17 | def test_sell(self): 18 | s = stock.Stock('GOOG', 100, 490.1) 19 | s.sell(25) 20 | self.assertEqual(s.shares, 75) 21 | 22 | def test_shares_check(self): 23 | s = stock.Stock('GOOG', 100, 490.1) 24 | with self.assertRaises(TypeError): 25 | s.shares = '100' 26 | 27 | if __name__ == '__main__': 28 | unittest.main() 29 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/porty/ticker.py: -------------------------------------------------------------------------------- 1 | # ticker.py 2 | 3 | import csv 4 | from . import report 5 | from . import tableformat 6 | from .follow import follow 7 | 8 | def select_columns(rows, indices): 9 | for row in rows: 10 | yield [row[index] for index in indices] 11 | 12 | def convert_types(rows, types): 13 | for row in rows: 14 | yield [func(val) for func, val in zip(types, row)] 15 | 16 | def make_dicts(rows, headers): 17 | return (dict(zip(headers,row)) for row in rows) 18 | 19 | def parse_stock_data(lines): 20 | rows = csv.reader(lines) 21 | rows = select_columns(rows, [0, 1, 4]) 22 | rows = convert_types(rows, [str,float,float]) 23 | rows = make_dicts(rows, ['name','price','change']) 24 | return rows 25 | 26 | def ticker(portfile, logfile, fmt): 27 | portfolio = report.read_portfolio(portfile) 28 | lines = follow(logfile) 29 | rows = parse_stock_data(lines) 30 | rows = (row for row in rows if row['name'] in portfolio) 31 | formatter = tableformat.create_formatter(fmt) 32 | formatter.headings(['Name','Price','Change']) 33 | for row in rows: 34 | formatter.row([ row['name'], f"{row['price']:0.2f}", f"{row['change']:0.2f}"] ) 35 | 36 | def main(args): 37 | if len(args) != 4: 38 | raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0]) 39 | ticker(args[1], args[2], args[3]) 40 | 41 | if __name__ == '__main__': 42 | import sys 43 | main(sys.argv) 44 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/porty/typedproperty.py: -------------------------------------------------------------------------------- 1 | # typedproperty.py 2 | 3 | def typedproperty(name, expected_type): 4 | private_name = '_' + name 5 | @property 6 | def prop(self): 7 | return getattr(self, private_name) 8 | 9 | @prop.setter 10 | def prop(self, value): 11 | if not isinstance(value, expected_type): 12 | raise TypeError(f'Expected {expected_type}') 13 | setattr(self, private_name, value) 14 | 15 | return prop 16 | 17 | String = lambda name: typedproperty(name, str) 18 | Integer = lambda name: typedproperty(name, int) 19 | Float = lambda name: typedproperty(name, float) 20 | 21 | # Example 22 | if __name__ == '__main__': 23 | class Stock: 24 | name = typedproperty('name', str) 25 | shares = typedproperty('shares', int) 26 | price = typedproperty('price', float) 27 | 28 | def __init__(self, name, shares, price): 29 | self.name = name 30 | self.shares = shares 31 | self.price = price 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/prices.csv: -------------------------------------------------------------------------------- 1 | "AA",9.22 2 | "AXP",24.85 3 | "BA",44.85 4 | "BAC",11.27 5 | "C",3.72 6 | "CAT",35.46 7 | "CVX",66.67 8 | "DD",28.47 9 | "DIS",24.22 10 | "GE",13.48 11 | "GM",0.75 12 | "HD",23.16 13 | "HPQ",34.35 14 | "IBM",106.28 15 | "INTC",15.72 16 | "JNJ",55.16 17 | "JPM",36.90 18 | "KFT",26.11 19 | "KO",49.16 20 | "MCD",58.99 21 | "MMM",57.10 22 | "MRK",27.58 23 | "MSFT",20.89 24 | "PFE",15.19 25 | "PG",51.94 26 | "T",24.79 27 | "UTX",52.61 28 | "VZ",29.26 29 | "WMT",49.74 30 | "XOM",69.35 31 | 32 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/print-report.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # print-report.py 3 | 4 | import sys 5 | from porty.report import main 6 | main(sys.argv) 7 | -------------------------------------------------------------------------------- /Solutions/9_5/porty-app/setup.py: -------------------------------------------------------------------------------- 1 | # setup.py 2 | 3 | import setuptools 4 | 5 | setuptools.setup( 6 | name="porty", 7 | version="0.0.1", 8 | author="Your Name", 9 | author_email="you@example.com", 10 | description="Practical Python Code", 11 | packages=setuptools.find_packages(), 12 | scripts=['print-report.py'], 13 | ) 14 | -------------------------------------------------------------------------------- /Solutions/README.md: -------------------------------------------------------------------------------- 1 | # Solutions 2 | 3 | This directory contains solutions to selected exercises. The code is 4 | written to run within this directory and has file paths set 5 | accordingly. If you copy any of the code to the `Work/` directory, 6 | you might need to adjust filenames. 7 | 8 | -------------------------------------------------------------------------------- /Work/Data/missing.csv: -------------------------------------------------------------------------------- 1 | name,shares,price 2 | "AA",100,32.20 3 | "IBM",50,91.10 4 | "CAT",150,83.44 5 | "MSFT",,51.23 6 | "GE",95,40.37 7 | "MSFT",50,65.10 8 | "IBM",,70.44 9 | -------------------------------------------------------------------------------- /Work/Data/portfolio.csv: -------------------------------------------------------------------------------- 1 | name,shares,price 2 | "AA",100,32.20 3 | "IBM",50,91.10 4 | "CAT",150,83.44 5 | "MSFT",200,51.23 6 | "GE",95,40.37 7 | "MSFT",50,65.10 8 | "IBM",100,70.44 9 | -------------------------------------------------------------------------------- /Work/Data/portfolio.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabeaz-course/practical-python/3c00ff81b7b61eae7ef79a760d60687f9fcf0a81/Work/Data/portfolio.csv.gz -------------------------------------------------------------------------------- /Work/Data/portfolio.dat: -------------------------------------------------------------------------------- 1 | name shares price 2 | "AA" 100 32.20 3 | "IBM" 50 91.10 4 | "CAT" 150 83.44 5 | "MSFT" 200 51.23 6 | "GE" 95 40.37 7 | "MSFT" 50 65.10 8 | "IBM" 100 70.44 9 | -------------------------------------------------------------------------------- /Work/Data/portfolio2.csv: -------------------------------------------------------------------------------- 1 | name,shares,price 2 | "AA",50,27.10 3 | "HPQ",250,43.15 4 | "MSFT",25,50.15 5 | "GE",125,52.10 6 | -------------------------------------------------------------------------------- /Work/Data/portfolioblank.csv: -------------------------------------------------------------------------------- 1 | name,shares,price 2 | 3 | "AA",100,32.20 4 | 5 | "IBM",50,91.10 6 | 7 | "CAT",150,83.44 8 | 9 | "MSFT",200,51.23 10 | 11 | "GE",95,40.37 12 | 13 | "MSFT",50,65.10 14 | 15 | "IBM",100,70.44 16 | 17 | -------------------------------------------------------------------------------- /Work/Data/portfoliodate.csv: -------------------------------------------------------------------------------- 1 | name,date,time,shares,price 2 | "AA","6/11/2007","9:50am",100,32.20 3 | "IBM","5/13/2007","4:20pm",50,91.10 4 | "CAT","9/23/2006","1:30pm",150,83.44 5 | "MSFT","5/17/2007","10:30am",200,51.23 6 | "GE","2/1/2006","10:45am",95,40.37 7 | "MSFT","10/31/2006","12:05pm",50,65.10 8 | "IBM","7/9/2006","3:15pm",100,70.44 9 | -------------------------------------------------------------------------------- /Work/Data/prices.csv: -------------------------------------------------------------------------------- 1 | "AA",9.22 2 | "AXP",24.85 3 | "BA",44.85 4 | "BAC",11.27 5 | "C",3.72 6 | "CAT",35.46 7 | "CVX",66.67 8 | "DD",28.47 9 | "DIS",24.22 10 | "GE",13.48 11 | "GM",0.75 12 | "HD",23.16 13 | "HPQ",34.35 14 | "IBM",106.28 15 | "INTC",15.72 16 | "JNJ",55.16 17 | "JPM",36.90 18 | "KFT",26.11 19 | "KO",49.16 20 | "MCD",58.99 21 | "MMM",57.10 22 | "MRK",27.58 23 | "MSFT",20.89 24 | "PFE",15.19 25 | "PG",51.94 26 | "T",24.79 27 | "UTX",52.61 28 | "VZ",29.26 29 | "WMT",49.74 30 | "XOM",69.35 31 | 32 | -------------------------------------------------------------------------------- /Work/README.md: -------------------------------------------------------------------------------- 1 | # Work Area 2 | 3 | Do all of your coding work here, in the `Work/` directory. A number of starting 4 | files have been given (`bounce.py`, `mortgage.py`, `pcost.py`, etc.) along with 5 | their corresponding exercise number. 6 | 7 | Many of the programs you write reference files found in the `Data/` directory. 8 | That is also located here. 9 | -------------------------------------------------------------------------------- /Work/bounce.py: -------------------------------------------------------------------------------- 1 | # bounce.py 2 | # 3 | # Exercise 1.5 4 | -------------------------------------------------------------------------------- /Work/fileparse.py: -------------------------------------------------------------------------------- 1 | # fileparse.py 2 | # 3 | # Exercise 3.3 4 | -------------------------------------------------------------------------------- /Work/mortgage.py: -------------------------------------------------------------------------------- 1 | # mortgage.py 2 | # 3 | # Exercise 1.7 4 | -------------------------------------------------------------------------------- /Work/pcost.py: -------------------------------------------------------------------------------- 1 | # pcost.py 2 | # 3 | # Exercise 1.27 4 | -------------------------------------------------------------------------------- /Work/report.py: -------------------------------------------------------------------------------- 1 | # report.py 2 | # 3 | # Exercise 2.4 4 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% if site.google_analytics %} 6 | 7 | 13 | {% endif %} 14 | 15 | 16 | {% seo %} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 37 | 38 |
39 | {{ content }} 40 | 41 | 50 |
51 | 52 | 53 | --------------------------------------------------------------------------------