├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── chapter-1
├── animals
│ ├── __init__.py
│ ├── cat.py
│ ├── cow.py
│ ├── dog.py
│ ├── horse.py
│ └── sheep.py
├── cache
│ ├── cache.py
│ └── test_cache.py
├── shaghetti
│ ├── config.ini
│ └── spaghetti.py
└── stats
│ ├── stats.py
│ └── test_stats.py
├── chapter-2
└── inventoryControl
│ ├── datastorage.py
│ ├── items.json
│ ├── main.py
│ ├── reportgenerator.py
│ └── userinterface.py
├── chapter-3
├── double.py
├── funkycase.py
└── test_module.py
├── chapter-4
├── charter
│ ├── __init__.py
│ ├── chart.py
│ ├── constants.py
│ ├── generator.py
│ └── renderers
│ │ ├── __init__.py
│ │ ├── pdf
│ │ ├── __init__.py
│ │ ├── bar_series.py
│ │ ├── line_series.py
│ │ ├── title.py
│ │ ├── x_axis.py
│ │ └── y_axis.py
│ │ ├── png
│ │ ├── __init__.py
│ │ ├── bar_series.py
│ │ ├── line_series.py
│ │ ├── title.py
│ │ ├── x_axis.py
│ │ └── y_axis.py
│ │ └── renderer.py
└── test_charter.py
├── chapter-5
├── abstraction
│ └── happy_hour.py
├── dynamic imports
│ ├── load_module.py
│ ├── module_a.py
│ ├── module_b.py
│ └── module_c.py
├── encapsulation
│ └── recipes.py
├── plugins
│ ├── load_plugin.py
│ └── plugins
│ │ ├── plugin_a.py
│ │ ├── plugin_b.py
│ │ └── plugin_c.py
└── wrappers
│ ├── detect_unusual_transfers.py
│ └── numpy_wrapper.py
├── chapter-6
├── quantities
│ ├── README.txt
│ ├── __init__.py
│ ├── interface.py
│ ├── quantity.py
│ └── units.py
└── test.py
├── chapter-7
├── add to path gotcha
│ ├── bad_imports.py
│ ├── good_imports.py
│ └── package
│ │ ├── __init__.py
│ │ └── module.py
├── module name gotcha
│ ├── main.py
│ ├── math.py
│ └── math.pyc
├── package configuration
│ ├── configpackage
│ │ ├── __init__.py
│ │ └── interface.py
│ └── runner.py
├── package data
│ ├── datapackage
│ │ ├── __init__.py
│ │ ├── data
│ │ │ └── phone_numbers.txt
│ │ └── interface.py
│ └── runner.py
├── package globals
│ ├── globalspackage
│ │ ├── __init__.py
│ │ ├── globals.py
│ │ └── test.py
│ └── runner.py
├── script as module gotcha
│ ├── helpers.py
│ └── test.py
├── script name gotcha
│ └── re.py
└── stringutils
│ ├── stringutils (buggy).py
│ └── stringutils (fixed).py
├── chapter-8
├── test-package
│ ├── MANIFEST
│ ├── README.rst
│ ├── erikwestra_test_package
│ │ ├── __init__.py
│ │ └── test.py
│ └── setup.py
└── test_quantities
│ ├── quantities
│ ├── README.txt
│ ├── __init__.py
│ ├── interface.py
│ ├── quantity.py
│ └── units.py
│ └── test_quantities.py
└── chapter-9
├── counter-ranges
├── README.rst
├── counter
│ ├── __init__.py
│ └── interface.py
└── tests.py
└── counter
├── README.rst
├── counter
├── __init__.py
└── interface.py
└── tests.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows image file caches
2 | Thumbs.db
3 | ehthumbs.db
4 |
5 | # Folder config file
6 | Desktop.ini
7 |
8 | # Recycle Bin used on file shares
9 | $RECYCLE.BIN/
10 |
11 | # Windows Installer files
12 | *.cab
13 | *.msi
14 | *.msm
15 | *.msp
16 |
17 | # Windows shortcuts
18 | *.lnk
19 |
20 | # =========================
21 | # Operating System Files
22 | # =========================
23 |
24 | # OSX
25 | # =========================
26 |
27 | .DS_Store
28 | .AppleDouble
29 | .LSOverride
30 |
31 | # Thumbnails
32 | ._*
33 |
34 | # Files that might appear in the root of a volume
35 | .DocumentRevisions-V100
36 | .fseventsd
37 | .Spotlight-V100
38 | .TemporaryItems
39 | .Trashes
40 | .VolumeIcon.icns
41 |
42 | # Directories potentially created on remote AFP share
43 | .AppleDB
44 | .AppleDesktop
45 | Network Trash Folder
46 | Temporary Items
47 | .apdisk
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Packt Publishing
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # Modular-Programming-with-Python
5 |
6 |
7 | About the Code Samples
8 | ----------------------
9 |
10 | These code samples all comes from the book [Modular Progarmming with Python](https://www.packtpub.com/application-development/modular-programming-python?utm_source=github&utm_medium=related&utm_campaign=9781785884481).
11 | They are organized by chapter. Please feel free to copy and use these in
12 | whatever way you like -- there are no restrictions at all on the use of these
13 | code samples.
14 |
15 | These code samples will work on any Mac OS X, Windows or Linux computer. While
16 | they are all written to use Python version 3.3 or later, you can modify them to
17 | use Python 2.x with minimal changes.
18 |
19 | To run the code samples from Chapter 4, you will need to install [Reportlab](http://www.reportlab.com/opensource/) and [Pillow](http://python-pillow.org/). To use the "wrappers" example from Chapter 5, you will need to install [NumPy](http://www.numpy.org/).
20 | All the other examples only require the Python Standard Library.
21 |
22 | You can also refer to the following books:
23 |
24 | * [Expert Python Programming](https://www.packtpub.com/application-development/expert-python-programming?utm_source=github&utm_medium=related&utm_campaign=9781847194947)
25 | * [Learning Cython Programming](https://www.packtpub.com/application-development/learning-cython-programming?utm_source=github&utm_medium=related&utm_campaign=9781783280797)
26 | * [Instant Data Intensive Apps with pandas How-to](https://www.packtpub.com/big-data-and-business-intelligence/instant-data-intensive-apps-pandas-how-instant?utm_source=github&utm_medium=related&utm_campaign=9781782165583)
27 | ### Download a free PDF
28 |
29 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
30 |
https://packt.link/free-ebook/9781785884481
-------------------------------------------------------------------------------- /chapter-1/animals/__init__.py: -------------------------------------------------------------------------------- 1 | # Package initialization file. 2 | -------------------------------------------------------------------------------- /chapter-1/animals/cat.py: -------------------------------------------------------------------------------- 1 | # cat.py 2 | 3 | def speak(): 4 | print("meow") 5 | 6 | -------------------------------------------------------------------------------- /chapter-1/animals/cow.py: -------------------------------------------------------------------------------- 1 | # cow.py 2 | 3 | def speak(): 4 | print("moo") 5 | 6 | -------------------------------------------------------------------------------- /chapter-1/animals/dog.py: -------------------------------------------------------------------------------- 1 | # dog.py 2 | 3 | def speak(): 4 | print("woof") 5 | 6 | -------------------------------------------------------------------------------- /chapter-1/animals/horse.py: -------------------------------------------------------------------------------- 1 | # dog.py 2 | 3 | def speak(): 4 | print("neigh") 5 | 6 | -------------------------------------------------------------------------------- /chapter-1/animals/sheep.py: -------------------------------------------------------------------------------- 1 | # dog.py 2 | 3 | def speak(): 4 | print("baa") 5 | 6 | -------------------------------------------------------------------------------- /chapter-1/cache/cache.py: -------------------------------------------------------------------------------- 1 | # cache.py 2 | 3 | import datetime 4 | 5 | MAX_CACHE_SIZE = 100 # Maximum number of entries in the cache. 6 | 7 | ############################################################################# 8 | 9 | def init(): 10 | global _cache 11 | _cache = {} # Maps key to (timestamp, value) tuple. 12 | 13 | 14 | def set(key, value): 15 | global _cache 16 | if key not in _cache and len(_cache) >= MAX_CACHE_SIZE: 17 | _remove_oldest_entry() 18 | _cache[key] = [datetime.datetime.now(), value] 19 | 20 | 21 | def get(key): 22 | global _cache 23 | if key in _cache: 24 | _cache[key][0] = datetime.datetime.now() 25 | return _cache[key][1] 26 | else: 27 | return None 28 | 29 | 30 | def contains(key): 31 | global _cache 32 | return key in _cache 33 | 34 | 35 | def size(): 36 | global _cache 37 | return len(_cache) 38 | 39 | ############################################################################# 40 | 41 | # Private functions: 42 | 43 | def _remove_oldest_entry(): 44 | global _cache 45 | oldest = None 46 | for key in _cache.keys(): 47 | if oldest == None: 48 | oldest = key 49 | elif _cache[key][0] < _cache[oldest][0]: 50 | oldest = key 51 | 52 | if oldest != None: 53 | del _cache[oldest] 54 | 55 | -------------------------------------------------------------------------------- /chapter-1/cache/test_cache.py: -------------------------------------------------------------------------------- 1 | # test_cache.py 2 | 3 | import random 4 | import string 5 | import cache 6 | 7 | def random_string(length): 8 | s = '' 9 | for i in range(length): 10 | s = s + random.choice(string.ascii_letters) 11 | return s 12 | 13 | cache.init() 14 | 15 | for n in range(1000): 16 | while True: 17 | key = random_string(20) 18 | if cache.contains(key): 19 | continue 20 | else: 21 | break 22 | value = random_string(20) 23 | cache.set(key, value) 24 | print("After {} iterations, cache has {} entries".format(n+1, cache.size())) 25 | 26 | -------------------------------------------------------------------------------- /chapter-1/shaghetti/config.ini: -------------------------------------------------------------------------------- 1 | # Configuration file for the spaghetti.py program. 2 | 3 | [config] 4 | 5 | num_data_points=10 6 | 7 | -------------------------------------------------------------------------------- /chapter-1/shaghetti/spaghetti.py: -------------------------------------------------------------------------------- 1 | # spaghetti.py -- an example of bad coding. 2 | 3 | import configparser 4 | 5 | def load_config(): 6 | config = configparser.ConfigParser() 7 | config.read("config.ini") 8 | return config['config'] 9 | 10 | def get_data_from_user(): 11 | config = load_config() 12 | data = [] 13 | for n in range(config.getint('num_data_points')): 14 | value = input("Data point {}: ".format(n+1)) 15 | data.append(value) 16 | return data 17 | 18 | def print_results(results): 19 | for value,num_times in results: 20 | print("{} = {}".format(value, num_times)) 21 | 22 | def analyze_data(): 23 | data = get_data_from_user() 24 | results = {} 25 | for value in data: 26 | try: 27 | results[value] = results[value] + 1 28 | except KeyError: 29 | results[value] = 1 30 | return results 31 | 32 | def sort_results(results): 33 | sorted_results = [] 34 | for value in results.keys(): 35 | sorted_results.append((value, results[value])) 36 | sorted_results.sort() 37 | return sorted_results 38 | 39 | if __name__ == "__main__": 40 | results = analyze_data() 41 | sorted_results = sort_results(results) 42 | print_results(sorted_results) 43 | 44 | -------------------------------------------------------------------------------- /chapter-1/stats/stats.py: -------------------------------------------------------------------------------- 1 | # stats.py 2 | 3 | def init(): 4 | global _stats 5 | _stats = {} 6 | 7 | def event_occurred(event): 8 | global _stats 9 | try: 10 | _stats[event] = _stats[event] + 1 11 | except KeyError: 12 | _stats[event] = 1 13 | 14 | def get_stats(): 15 | global _stats 16 | return sorted(_stats.items()) 17 | -------------------------------------------------------------------------------- /chapter-1/stats/test_stats.py: -------------------------------------------------------------------------------- 1 | # test_stats.py 2 | 3 | import stats 4 | 5 | stats.init() 6 | stats.event_occurred("meal_eaten") 7 | stats.event_occurred("snack_eaten") 8 | stats.event_occurred("meal_eaten") 9 | stats.event_occurred("snack_eaten") 10 | stats.event_occurred("meal_eaten") 11 | stats.event_occurred("diet_started") 12 | stats.event_occurred("meal_eaten") 13 | stats.event_occurred("meal_eaten") 14 | stats.event_occurred("meal_eaten") 15 | stats.event_occurred("diet_abandoned") 16 | stats.event_occurred("snack_eaten") 17 | 18 | for event,num_times in stats.get_stats(): 19 | print("{} occurred {} times".format(event, num_times)) 20 | -------------------------------------------------------------------------------- /chapter-2/inventoryControl/datastorage.py: -------------------------------------------------------------------------------- 1 | # datastorage.py 2 | # 3 | # We store our data in three private globals: 4 | # 5 | # 'items' 6 | # 7 | # The list of current inventory items. Each item in this list is a 8 | # (product_code, location_code) tuple. 9 | # 10 | # 'products' 11 | # 12 | # The list of products we can hold inventory for. Each item in this list 13 | # is a (code, description, desired_number) tuple, where 'code is a code 14 | # identifying the product, 'description' is a string describing that 15 | # product so the user can identify it, and 'desired_number' is the 16 | # desired number of items that the user wants to keep in the inventory. 17 | # 18 | # 'locations' 19 | # 20 | # The list of locations where inventory can be stored. Each item in this 21 | # list is a (code, description) tuple, where 'code is the code for an 22 | # inventory location, and 'description' is a string describing that 23 | # location so that the user knows where it is. 24 | 25 | import json 26 | import os.path 27 | 28 | ############################################################################# 29 | 30 | def init(): 31 | """ Initialize the datastorage module. 32 | """ 33 | _load_items() 34 | 35 | ############################################################################# 36 | 37 | def items(): 38 | """ Return a list of inventory items. 39 | 40 | We return a list of inventory items. Each item in the returned list 41 | will be a (product_code, location_code) tuple. 42 | 43 | Note that the returned list of items are not sorted in any way. 44 | """ 45 | global _items 46 | return _items 47 | 48 | ############################################################################# 49 | 50 | def products(): 51 | """ Return a list of the known products. 52 | 53 | We return a list of (code, description, desired_number) tuples 54 | containing all the known inventory products. 55 | """ 56 | global _products 57 | return _products 58 | 59 | ############################################################################# 60 | 61 | def locations(): 62 | """ Return a list of the known locations where inventory can be stored. 63 | 64 | We return a list of (code, description) tuples containing all the known 65 | inventory locations. 66 | """ 67 | global _locations 68 | return _locations 69 | 70 | ############################################################################# 71 | 72 | def add_item(product_code, location_code): 73 | """ Add an item to the inventory with the given product code and location. 74 | """ 75 | global _items 76 | _items.append((product_code, location_code)) 77 | _save_items() 78 | 79 | ############################################################################# 80 | 81 | def remove_item(product_code, location_code): 82 | """ Remove an inventory item with the given product code from the given location. 83 | 84 | Returns True if and only if the item was successfully removed. 85 | """ 86 | global _items 87 | for i in range(len(_items)): 88 | prod_code,loc_code = _items[i] 89 | if prod_code == product_code and loc_code == location_code: 90 | del _items[i] 91 | _save_items() 92 | return True 93 | return False 94 | 95 | ############################################################################# 96 | 97 | def set_products(products): 98 | """ Set the (currently hardwired) list of inventory products. 99 | 100 | Each item in the 'products' list should be a (code, description, 101 | desired_number) tuple, where 'code is a code identifying the product, 102 | 'description' is a string describing that product so the user can 103 | identify it, and 'desired_number' is the desired number of items that 104 | you want to keep in the inventory. 105 | """ 106 | global _products 107 | _products = products 108 | 109 | ############################################################################# 110 | 111 | def set_locations(locations): 112 | """ Set the (currently hardwired) list of inventory locations. 113 | 114 | Each item in the 'locations' list should be a (code, description) 115 | tuple, where 'code is the code for an inventory location, and 116 | 'description' is a string describing that location so that the user 117 | knows where it is. 118 | """ 119 | global _locations 120 | _locations = locations 121 | 122 | ############################################################################# 123 | # 124 | # Private definitions: 125 | 126 | def _load_items(): 127 | """ Load the list of inventory items from disk. 128 | """ 129 | global _items 130 | 131 | if os.path.exists("items.json"): 132 | f = open("items.json", "r") 133 | _items = json.loads(f.read()) 134 | f.close() 135 | else: 136 | _items = [] 137 | 138 | ############################################################################# 139 | 140 | def _save_items(): 141 | """ Save the list of inventory items to disk. 142 | """ 143 | global _items 144 | 145 | f = open("items.json", "w") 146 | f.write(json.dumps(_items)) 147 | f.close() 148 | 149 | -------------------------------------------------------------------------------- /chapter-2/inventoryControl/items.json: -------------------------------------------------------------------------------- 1 | [["SKU145", "S2A1"], ["SKU123", "S1A1"], ["SKU123", "S1A1"]] -------------------------------------------------------------------------------- /chapter-2/inventoryControl/main.py: -------------------------------------------------------------------------------- 1 | # main.py 2 | # 3 | # This is the main program for the InventoryControl system. 4 | 5 | import datastorage 6 | import userinterface 7 | import reportgenerator 8 | 9 | ############################################################################# 10 | 11 | def main(): 12 | datastorage.init() 13 | 14 | datastorage.set_products([ 15 | ("SKU123", "4 mm flat-head wood screw", 50), 16 | ("SKU145", "6 mm flat-head wood screw", 50), 17 | ("SKU167", "4 mm countersunk head wood screw", 10), 18 | ("SKU169", "6 mm countersunk head wood screw", 10), 19 | ("SKU172", "4 mm metal self-tapping screw", 20), 20 | ("SKU185", "8 mm metal self-tapping screw", 20), 21 | ]) 22 | 23 | datastorage.set_locations([ 24 | ("S1A1", "Shelf 1, Aisle 1"), 25 | ("S2A1", "Shelf 2, Aisle 1"), 26 | ("S3A1", "Shelf 3, Aisle 1"), 27 | ("S1A2", "Shelf 1, Aisle 2"), 28 | ("S2A2", "Shelf 2, Aisle 2"), 29 | ("S3A2", "Shelf 3, Aisle 2"), 30 | ("BIN1", "Storage Bin 1"), 31 | ("BIN2", "Storage Bin 2"), 32 | ]) 33 | 34 | while True: 35 | action = userinterface.prompt_for_action() 36 | if action == "QUIT": 37 | break 38 | elif action == "ADD": 39 | product = userinterface.prompt_for_product() 40 | if product != None: 41 | location = userinterface.prompt_for_location() 42 | if location != None: 43 | datastorage.add_item(product, location) 44 | elif action == "REMOVE": 45 | product = userinterface.prompt_for_product() 46 | if product != None: 47 | location = userinterface.prompt_for_location() 48 | if location != None: 49 | if not datastorage.remove_item(product, location): 50 | userinterface.show_error("There is no product with " + 51 | "that code at that location!") 52 | elif action == "INVENTORY_REPORT": 53 | report = reportgenerator.generate_inventory_report() 54 | userinterface.show_report(report) 55 | elif action == "REORDER_REPORT": 56 | report = reportgenerator.generate_reorder_report() 57 | userinterface.show_report(report) 58 | 59 | ############################################################################# 60 | 61 | if __name__ == "__main__": 62 | main() 63 | 64 | -------------------------------------------------------------------------------- /chapter-2/inventoryControl/reportgenerator.py: -------------------------------------------------------------------------------- 1 | # reportgenerator.py 2 | # 3 | # This module implements the report-generation logic for the InventoryControl 4 | # system. 5 | 6 | import datastorage 7 | 8 | ############################################################################# 9 | 10 | def generate_inventory_report(): 11 | """ Generate a report of the current inventory levels. 12 | 13 | We return a list of strings containing the report's contents. 14 | """ 15 | # Get the list of known products and locations. 16 | 17 | product_names = {} # Maps product code to product name. 18 | for product_code,name,desired_number in datastorage.products(): 19 | product_names[product_code] = name 20 | 21 | location_names = {} # Maps location code to location name. 22 | for location_code,name in datastorage.locations(): 23 | location_names[location_code] = name 24 | 25 | # Calculate the report's contents. 26 | 27 | grouped_items = {} # Maps product code to dictionary mapping location code 28 | # to number of items with that product at that location. 29 | 30 | for product_code,location_code in datastorage.items(): 31 | if product_code not in grouped_items: 32 | grouped_items[product_code] = {} 33 | 34 | if location_code not in grouped_items[product_code]: 35 | grouped_items[product_code][location_code] = 1 36 | else: 37 | grouped_items[product_code][location_code] += 1 38 | 39 | # Generate the report. 40 | 41 | report = [] 42 | report.append("INVENTORY REPORT") 43 | report.append("") 44 | 45 | for product_code in sorted(grouped_items.keys()): 46 | product_name = product_names[product_code] 47 | report.append("Inventory for product: {} - {}".format(product_code, 48 | product_name)) 49 | report.append("") 50 | 51 | for location_code in sorted(grouped_items[product_code].keys()): 52 | location_name = location_names[location_code] 53 | num_items = grouped_items[product_code][location_code] 54 | report.append(" {} at {} - {}".format(num_items, 55 | location_code, 56 | location_name)) 57 | report.append("") 58 | 59 | return report 60 | 61 | ############################################################################# 62 | 63 | def generate_reorder_report(): 64 | """ Generate a report of the inventory items to re-order. 65 | 66 | We return a list of strings containing the report's contents. 67 | """ 68 | # Get the list of known products and locations. 69 | 70 | product_names = {} # Maps product code to product name. 71 | desired_numbers = {} # maps product code to desired number of items. 72 | 73 | for product_code,name,desired_number in datastorage.products(): 74 | product_names[product_code] = name 75 | desired_numbers[product_code] = desired_number 76 | 77 | # Calculate our current inventory levels. 78 | 79 | num_in_inventory = {} # Maps product code to number of items in inventory. 80 | 81 | for product_code,location_code in datastorage.items(): 82 | if product_code in num_in_inventory: 83 | num_in_inventory[product_code] += 1 84 | else: 85 | num_in_inventory[product_code] = 1 86 | 87 | # Generate the report. 88 | 89 | report = [] 90 | report.append("RE-ORDER REPORT") 91 | report.append("") 92 | 93 | for product_code in sorted(product_names.keys()): 94 | desired_number = desired_numbers[product_code] 95 | current_number = num_in_inventory.get(product_code, 0) 96 | if current_number < desired_number: 97 | product_name = product_names[product_code] 98 | num_to_reorder = desired_number - current_number 99 | report.append(" Re-order {} of {} - {}".format(num_to_reorder, 100 | product_code, 101 | product_name)) 102 | report.append("") 103 | 104 | return report 105 | 106 | -------------------------------------------------------------------------------- /chapter-2/inventoryControl/userinterface.py: -------------------------------------------------------------------------------- 1 | # userinterface.py 2 | # 3 | # This module implements the user interface for the InventoryControl system. 4 | 5 | import datastorage 6 | 7 | ############################################################################# 8 | 9 | def prompt_for_action(): 10 | """ Prompt the user to choose an action to perform. 11 | 12 | We return one of the following action codes: 13 | 14 | "QUIT" 15 | "ADD" 16 | "REMOVE" 17 | "INVENTORY_REPORT" 18 | "REORDER_REPORT" 19 | """ 20 | while True: 21 | print() 22 | print("What would you like to do?") 23 | print() 24 | print(" A = add an item to the inventory.") 25 | print(" R = remove an item from the inventory.") 26 | print(" C = generate a report of the current inventory levels.") 27 | print(" O = generate a report of the inventory items to re-order.") 28 | print(" Q = quit.") 29 | print() 30 | action = input("> ").strip().upper() 31 | if action == "A": return "ADD" 32 | elif action == "R": return "REMOVE" 33 | elif action == "C": return "INVENTORY_REPORT" 34 | elif action == "O": return "REORDER_REPORT" 35 | elif action == "Q": return "QUIT" 36 | else: 37 | print("Unknown action!") 38 | 39 | ############################################################################# 40 | 41 | def prompt_for_product(): 42 | """ Prompt the user to select a product. 43 | 44 | We return the code for the selected product, or None if the user 45 | cancelled. 46 | """ 47 | while True: 48 | print() 49 | print("Select a product:") 50 | print() 51 | n = 1 52 | for code,description,desired_number in datastorage.products(): 53 | print(" {}. {} - {}".format(n, code, description)) 54 | n = n + 1 55 | 56 | s = input("> ").strip() 57 | if s == "": return None 58 | 59 | try: 60 | n = int(s) 61 | except ValueError: 62 | n = -1 63 | 64 | if n < 1 or n > len(datastorage.products()): 65 | print("Invalid option: {}".format(s)) 66 | continue 67 | 68 | product_code = datastorage.products()[n-1][0] 69 | return product_code 70 | 71 | ############################################################################# 72 | 73 | def prompt_for_location(): 74 | """ Prompt the user to select a location. 75 | 76 | We return the code for the selected location, or None if the user 77 | cancelled. 78 | """ 79 | while True: 80 | print() 81 | print("Select a location:") 82 | print() 83 | n = 1 84 | for code,description in datastorage.locations(): 85 | print(" {}. {} - {}".format(n, code, description)) 86 | n = n + 1 87 | 88 | s = input("> ").strip() 89 | if s == "": return None 90 | 91 | try: 92 | n = int(s) 93 | except ValueError: 94 | n = -1 95 | 96 | if n < 1 or n > len(datastorage.locations()): 97 | print("Invalid option: {}".format(s)) 98 | continue 99 | 100 | location_code = datastorage.locations()[n-1][0] 101 | return location_code 102 | 103 | ############################################################################# 104 | 105 | def show_error(err_msg): 106 | """ Display the given error message to the user. 107 | """ 108 | print() 109 | print(err_msg) 110 | print() 111 | 112 | ############################################################################# 113 | 114 | def show_report(report): 115 | """ Display the given report to the user. 116 | 117 | 'report' is a list of strings containing the contents of the report. 118 | """ 119 | print() 120 | for line in report: 121 | print(line) 122 | print() 123 | 124 | -------------------------------------------------------------------------------- /chapter-3/double.py: -------------------------------------------------------------------------------- 1 | def double(n): 2 | return n * 2 3 | 4 | if __name__ == "__main__": 5 | print("double(3) =", double(3)) 6 | 7 | -------------------------------------------------------------------------------- /chapter-3/funkycase.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def funky_case(s): 4 | letters = [] 5 | capitalize = False 6 | for letter in s: 7 | if capitalize: 8 | letters.append(letter.upper()) 9 | else: 10 | letters.append(letter.lower()) 11 | capitalize = not capitalize 12 | return "".join(letters) 13 | 14 | if __name__ == "__main__": 15 | if len(sys.argv) != 2: 16 | print("You must supply exactly one string!") 17 | else: 18 | s = sys.argv[1] 19 | print(funky_case(s)) 20 | 21 | -------------------------------------------------------------------------------- /chapter-3/test_module.py: -------------------------------------------------------------------------------- 1 | def foo(): 2 | print("in foo") 3 | 4 | def bar(): 5 | print("in bar") 6 | 7 | my_var = 0 8 | 9 | print("importing test module") 10 | 11 | -------------------------------------------------------------------------------- /chapter-4/charter/__init__.py: -------------------------------------------------------------------------------- 1 | """ __init__.py 2 | 3 | This is the package initialization file for the Charter package. 4 | 5 | We import the various functions from our sub-modules so they can be 6 | accessed directly from the Charter package. 7 | """ 8 | from .chart import * 9 | from .generator import * 10 | -------------------------------------------------------------------------------- /chapter-4/charter/chart.py: -------------------------------------------------------------------------------- 1 | """ chart.py 2 | 3 | This module implements the chart-related functions for the Charter package. 4 | """ 5 | def new_chart(): 6 | return {} 7 | 8 | def set_title(chart, title): 9 | chart['title'] = title 10 | 11 | def set_x_axis(chart, x_axis): 12 | chart['x_axis'] = x_axis 13 | 14 | def set_y_axis(chart, minimum, maximum, labels): 15 | chart['y_min'] = minimum 16 | chart['y_max'] = maximum 17 | chart['y_labels'] = labels 18 | 19 | def set_series_type(chart, series_type): 20 | chart['series_type'] = series_type 21 | 22 | def set_series(chart, series): 23 | chart['series'] = series 24 | 25 | -------------------------------------------------------------------------------- /chapter-4/charter/constants.py: -------------------------------------------------------------------------------- 1 | """ constants.py 2 | 3 | This module defines various constants used by the Charter package. 4 | """ 5 | CHART_WIDTH = 600 6 | CHART_HEIGHT = 400 7 | TITLE_HEIGHT = 50 8 | X_AXIS_HEIGHT = 50 9 | Y_AXIS_WIDTH = 50 10 | MARGIN = 20 11 | TICKMARK_HEIGHT = 8 12 | -------------------------------------------------------------------------------- /chapter-4/charter/generator.py: -------------------------------------------------------------------------------- 1 | """ generator.py 2 | 3 | This module implements the chart-generation logic for the Charter package. 4 | """ 5 | from PIL import Image, ImageDraw 6 | 7 | from reportlab.pdfgen.canvas import Canvas 8 | 9 | from .constants import * 10 | from .renderers import renderer 11 | 12 | ############################################################################# 13 | 14 | def generate_chart(chart, filename): 15 | if filename.lower().endswith(".pdf"): 16 | format = "pdf" 17 | elif filename.lower().endswith(".png"): 18 | format = "png" 19 | else: 20 | print("Unsupported file format: " + filename) 21 | return 22 | 23 | if format == "pdf": 24 | output = Canvas(filename) 25 | elif format == "png": 26 | image = Image.new("RGB", (CHART_WIDTH, CHART_HEIGHT), 27 | "#ffffff") 28 | output = ImageDraw.Draw(image) 29 | 30 | renderer.draw(format, "title", chart, output) 31 | renderer.draw(format, "x_axis", chart, output) 32 | renderer.draw(format, "y_axis", chart, output) 33 | if chart['series_type'] == "bar": 34 | renderer.draw(format, "bar_series", chart, output) 35 | elif chart['series_type'] == "line": 36 | renderer.draw(format, "line_series", chart, output) 37 | 38 | if format == "pdf": 39 | output.showPage() 40 | output.save() 41 | elif format == "png": 42 | image.save(filename, format="png") 43 | 44 | -------------------------------------------------------------------------------- /chapter-4/charter/renderers/__init__.py: -------------------------------------------------------------------------------- 1 | """ __init__.py 2 | 3 | Empty package initialization file. 4 | """ 5 | 6 | -------------------------------------------------------------------------------- /chapter-4/charter/renderers/pdf/__init__.py: -------------------------------------------------------------------------------- 1 | """ __init__.py 2 | 3 | Empty package initialization file. 4 | """ 5 | 6 | -------------------------------------------------------------------------------- /chapter-4/charter/renderers/pdf/bar_series.py: -------------------------------------------------------------------------------- 1 | # charter.renderers.pdf.line_series 2 | # 3 | # Renderer for drawing a bar series onto the chart in PDF format. 4 | 5 | from ...constants import * 6 | 7 | ############################################################################# 8 | 9 | def draw(chart, canvas): 10 | avail_width = CHART_WIDTH - Y_AXIS_WIDTH - MARGIN 11 | bucket_width = avail_width / len(chart['x_axis']) 12 | 13 | bottom = X_AXIS_HEIGHT 14 | max_top = CHART_HEIGHT - TITLE_HEIGHT 15 | avail_height = max_top - bottom 16 | 17 | left = Y_AXIS_WIDTH 18 | for y_value in chart['series']: 19 | bar_left = left + MARGIN / 2 20 | bar_width = bucket_width - MARGIN 21 | 22 | y = ((y_value - chart['y_min']) / 23 | (chart['y_max'] - chart['y_min'])) 24 | 25 | bar_height = int(y * avail_height) 26 | 27 | canvas.setStrokeColorRGB(0.25, 0.25, 0.625) 28 | canvas.setFillColorRGB(0.906, 0.906, 0.953) 29 | canvas.rect(bar_left, bottom, bar_width, bar_height, 30 | stroke=True, fill=True) 31 | 32 | left = left + bucket_width 33 | 34 | -------------------------------------------------------------------------------- /chapter-4/charter/renderers/pdf/line_series.py: -------------------------------------------------------------------------------- 1 | # charter.renderers.pdf.line_series 2 | # 3 | # Renderer for drawing a line series onto the chart in PDF format. 4 | 5 | from ...constants import * 6 | 7 | ############################################################################# 8 | 9 | def draw(chart, canvas): 10 | avail_width = CHART_WIDTH - Y_AXIS_WIDTH - MARGIN 11 | bucket_width = avail_width / len(chart['x_axis']) 12 | 13 | bottom = X_AXIS_HEIGHT 14 | max_top = CHART_HEIGHT - TITLE_HEIGHT 15 | avail_height = max_top - bottom 16 | 17 | left = Y_AXIS_WIDTH 18 | prev_y = None 19 | for y_value in chart['series']: 20 | y = ((y_value - chart['y_min']) / 21 | (chart['y_max'] - chart['y_min'])) 22 | 23 | cur_y = bottom + int(y * avail_height) 24 | 25 | if prev_y != None: 26 | canvas.setStrokeColorRGB(0.25, 0.25, 0.625) 27 | canvas.setLineWidth(1) 28 | canvas.line(left - bucket_width / 2, prev_y, 29 | left + bucket_width / 2, cur_y) 30 | 31 | prev_y = cur_y 32 | left = left + bucket_width 33 | 34 | -------------------------------------------------------------------------------- /chapter-4/charter/renderers/pdf/title.py: -------------------------------------------------------------------------------- 1 | # charter.renderers.pdf.title 2 | # 3 | # Renderer for drawing a title onto the chart in PDF format. 4 | 5 | from ...constants import * 6 | 7 | ############################################################################# 8 | 9 | def draw(chart, canvas): 10 | text_width = canvas.stringWidth(chart['title'], "Helvetica", 24) 11 | text_height = 24 * 1.2 12 | 13 | left = CHART_WIDTH/2 - text_width/2 14 | bottom = CHART_HEIGHT - TITLE_HEIGHT/2 + text_height/2 15 | 16 | canvas.setFont("Helvetica", 24) 17 | canvas.setFillColorRGB(0.25, 0.25, 0.625) 18 | canvas.drawString(left, bottom, chart['title']) 19 | 20 | -------------------------------------------------------------------------------- /chapter-4/charter/renderers/pdf/x_axis.py: -------------------------------------------------------------------------------- 1 | # charter.renderers.pdf.x_axis 2 | # 3 | # Renderer for drawing an X axis onto the chart in PDF format. 4 | 5 | from ...constants import * 6 | 7 | ############################################################################# 8 | 9 | def draw(chart, canvas): 10 | label_height = 12 * 1.2 11 | 12 | avail_width = CHART_WIDTH - Y_AXIS_WIDTH - MARGIN 13 | bucket_width = avail_width / len(chart['x_axis']) 14 | 15 | # Draw main axis line. 16 | 17 | axis_top = X_AXIS_HEIGHT 18 | canvas.setStrokeColorRGB(0.25, 0.25, 0.625) 19 | canvas.setLineWidth(2) 20 | canvas.line(Y_AXIS_WIDTH, axis_top, CHART_WIDTH - MARGIN, axis_top) 21 | 22 | left = Y_AXIS_WIDTH 23 | for bucket_num in range(len(chart['x_axis'])): 24 | 25 | # Draw tickmark. 26 | 27 | canvas.setLineWidth(1) 28 | canvas.line(left, axis_top, left, axis_top - TICKMARK_HEIGHT) 29 | 30 | # Draw label. 31 | 32 | label_width = canvas.stringWidth(chart['x_axis'][bucket_num], 33 | "Helvetica", 12) 34 | label_left = max(left, left + bucket_width/2 - label_width/2) 35 | label_bottom = axis_top - TICKMARK_HEIGHT - 4 - label_height 36 | 37 | canvas.setFont("Helvetica", 12) 38 | canvas.setFillColorRGB(0.0, 0.0, 0.0) 39 | canvas.drawString(label_left, label_bottom, 40 | chart['x_axis'][bucket_num]) 41 | 42 | left = left + bucket_width 43 | 44 | # Draw final tickmark. 45 | 46 | canvas.setStrokeColorRGB(0.25, 0.25, 0.625) 47 | canvas.setLineWidth(1) 48 | canvas.line(left, axis_top, left, axis_top - TICKMARK_HEIGHT) 49 | 50 | -------------------------------------------------------------------------------- /chapter-4/charter/renderers/pdf/y_axis.py: -------------------------------------------------------------------------------- 1 | # charter.renderers.pdf.y_axis 2 | # 3 | # Renderer for drawing a Y axis onto the chart in PDF format. 4 | 5 | from ...constants import * 6 | 7 | ############################################################################# 8 | 9 | def draw(chart, canvas): 10 | label_height = 12 * 1.2 11 | 12 | axis_top = CHART_HEIGHT - TITLE_HEIGHT 13 | axis_bottom = X_AXIS_HEIGHT 14 | axis_height = axis_top - axis_bottom 15 | 16 | # Draw main axis line. 17 | 18 | canvas.setStrokeColorRGB(0.25, 0.25, 0.625) 19 | canvas.setLineWidth(2) 20 | canvas.line(Y_AXIS_WIDTH, axis_top, Y_AXIS_WIDTH, axis_bottom) 21 | 22 | for y_value in chart['y_labels']: 23 | y = ((y_value - chart['y_min']) / 24 | (chart['y_max'] - chart['y_min'])) 25 | 26 | y_pos = axis_bottom + int(y * axis_height) 27 | 28 | # Draw tickmark. 29 | 30 | canvas.setLineWidth(1) 31 | canvas.line(Y_AXIS_WIDTH - TICKMARK_HEIGHT, y_pos, 32 | Y_AXIS_WIDTH, y_pos) 33 | 34 | # Draw label. 35 | 36 | label_width = canvas.stringWidth(str(y_value), 37 | "Helvetica", 12) 38 | label_left = Y_AXIS_WIDTH - TICKMARK_HEIGHT - label_width - 4 39 | label_bottom = y_pos - label_height/4 40 | 41 | canvas.setFont("Helvetica", 12) 42 | canvas.setFillColorRGB(0.0, 0.0, 0.0) 43 | canvas.drawString(label_left, label_bottom, str(y_value)) 44 | 45 | -------------------------------------------------------------------------------- /chapter-4/charter/renderers/png/__init__.py: -------------------------------------------------------------------------------- 1 | """ __init__.py 2 | 3 | Empty package initialization file. 4 | """ 5 | -------------------------------------------------------------------------------- /chapter-4/charter/renderers/png/bar_series.py: -------------------------------------------------------------------------------- 1 | # charter.renderers.png.bar_series 2 | # 3 | # Renderer for drawing a bar series onto the chart in PNG format. 4 | 5 | from PIL import ImageFont 6 | 7 | from ...constants import * 8 | 9 | ############################################################################# 10 | 11 | def draw(chart, drawer): 12 | avail_width = CHART_WIDTH - Y_AXIS_WIDTH - MARGIN 13 | bucket_width = avail_width / len(chart['x_axis']) 14 | 15 | max_top = TITLE_HEIGHT 16 | bottom = CHART_HEIGHT - X_AXIS_HEIGHT 17 | avail_height = bottom - max_top 18 | 19 | left = Y_AXIS_WIDTH 20 | for y_value in chart['series']: 21 | bar_left = left + MARGIN / 2 22 | bar_right = left + bucket_width - MARGIN / 2 23 | 24 | y = ((y_value - chart['y_min']) / 25 | (chart['y_max'] - chart['y_min'])) 26 | 27 | bar_top = max_top + (avail_height - int(y * avail_height)) 28 | 29 | drawer.rectangle([(bar_left, bar_top), 30 | (bar_right + 1, 31 | bottom)], 32 | fill="#e8e8f4", outline="#4040a0") 33 | 34 | left = left + bucket_width 35 | -------------------------------------------------------------------------------- /chapter-4/charter/renderers/png/line_series.py: -------------------------------------------------------------------------------- 1 | # charter.renderers.png.line_series 2 | # 3 | # Renderer for drawing a line series onto the chart in PNG format. 4 | 5 | from PIL import ImageFont 6 | 7 | from ...constants import * 8 | 9 | ############################################################################# 10 | 11 | def draw(chart, drawer): 12 | avail_width = CHART_WIDTH - Y_AXIS_WIDTH - MARGIN 13 | bucket_width = avail_width / len(chart['x_axis']) 14 | 15 | max_top = TITLE_HEIGHT 16 | bottom = CHART_HEIGHT - X_AXIS_HEIGHT 17 | avail_height = bottom - max_top 18 | 19 | left = Y_AXIS_WIDTH 20 | prev_y = None 21 | for y_value in chart['series']: 22 | y = ((y_value - chart['y_min']) / 23 | (chart['y_max'] - chart['y_min'])) 24 | 25 | cur_y = max_top + (avail_height - int(y * avail_height)) 26 | 27 | if prev_y != None: 28 | drawer.line([(left - bucket_width / 2, prev_y), 29 | (left + bucket_width / 2), cur_y], 30 | fill="#4040a0", width=1) 31 | prev_y = cur_y 32 | left = left + bucket_width 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /chapter-4/charter/renderers/png/title.py: -------------------------------------------------------------------------------- 1 | # charter.renderers.png.title 2 | # 3 | # Renderer for drawing a title onto the chart in PNG format. 4 | 5 | from PIL import ImageFont 6 | 7 | from ...constants import * 8 | 9 | ############################################################################# 10 | 11 | def draw(chart, drawer): 12 | font = ImageFont.truetype("Helvetica", 24) 13 | text_width,text_height = font.getsize(chart['title']) 14 | 15 | left = CHART_WIDTH/2 - text_width/2 16 | top = TITLE_HEIGHT/2 - text_height/2 17 | 18 | drawer.text((left, top), chart['title'], "#4040a0", font) 19 | 20 | -------------------------------------------------------------------------------- /chapter-4/charter/renderers/png/x_axis.py: -------------------------------------------------------------------------------- 1 | # charter.renderers.png.x_axis 2 | # 3 | # Renderer for drawing an X axis onto the chart in PNG format. 4 | 5 | from PIL import ImageFont 6 | 7 | from ...constants import * 8 | 9 | ############################################################################# 10 | 11 | def draw(chart, drawer): 12 | font = ImageFont.truetype("Helvetica", 12) 13 | label_height = font.getsize("Test")[1] 14 | 15 | avail_width = CHART_WIDTH - Y_AXIS_WIDTH - MARGIN 16 | bucket_width = avail_width / len(chart['x_axis']) 17 | 18 | axis_top = CHART_HEIGHT - X_AXIS_HEIGHT 19 | drawer.line([(Y_AXIS_WIDTH, axis_top), (CHART_WIDTH - MARGIN, axis_top)], 20 | "#4040a0", 2) # Draw main axis line. 21 | 22 | left = Y_AXIS_WIDTH 23 | for bucket_num in range(len(chart['x_axis'])): 24 | drawer.line([(left, axis_top), (left, axis_top + TICKMARK_HEIGHT)], 25 | "#4040a0", 1) # Draw tickmark 26 | 27 | label_width = font.getsize(chart['x_axis'][bucket_num])[0] 28 | label_left = max(left, left + bucket_width/2 - label_width/2) 29 | label_top = axis_top + TICKMARK_HEIGHT + 4 30 | 31 | drawer.text((label_left, label_top), chart['x_axis'][bucket_num], 32 | "#000000", font) 33 | 34 | left = left + bucket_width 35 | 36 | drawer.line([(left, axis_top), (left, axis_top + TICKMARK_HEIGHT)], 37 | "#4040a0", 1) # Draw final tickmark. 38 | -------------------------------------------------------------------------------- /chapter-4/charter/renderers/png/y_axis.py: -------------------------------------------------------------------------------- 1 | # charter.renderers.png.y_axis 2 | # 3 | # Renderer for drawing a Y-axis onto the chart in PNG format. 4 | 5 | from PIL import ImageFont 6 | 7 | from ...constants import * 8 | 9 | ############################################################################# 10 | 11 | def draw(chart, drawer): 12 | font = ImageFont.truetype("Helvetica", 12) 13 | label_height = font.getsize("Test")[1] 14 | 15 | axis_top = TITLE_HEIGHT 16 | axis_bottom = CHART_HEIGHT - X_AXIS_HEIGHT 17 | axis_height = axis_bottom - axis_top 18 | 19 | drawer.line([(Y_AXIS_WIDTH, axis_top), 20 | (Y_AXIS_WIDTH, axis_bottom)], 21 | "#4040a0", 2) # Draw main axis line. 22 | 23 | for y_value in chart['y_labels']: 24 | y = ((y_value - chart['y_min']) / 25 | (chart['y_max'] - chart['y_min'])) 26 | 27 | y_pos = axis_top + (axis_height - int(y * axis_height)) 28 | 29 | drawer.line([(Y_AXIS_WIDTH - TICKMARK_HEIGHT, y_pos), 30 | (Y_AXIS_WIDTH, y_pos)], 31 | "#4040a0", 1) # Draw tickmark. 32 | 33 | label_width,label_height = font.getsize(str(y_value)) 34 | label_left = Y_AXIS_WIDTH - TICKMARK_HEIGHT - label_width - 4 35 | label_top = y_pos - label_height / 2 36 | 37 | drawer.text((label_left, label_top), str(y_value), 38 | "#000000", font) 39 | 40 | 41 | -------------------------------------------------------------------------------- /chapter-4/charter/renderers/renderer.py: -------------------------------------------------------------------------------- 1 | """ charter.renderers.renderer 2 | 3 | This module renders a given chart element in a given format. 4 | """ 5 | from .png import title as title_png 6 | from .png import x_axis as x_axis_png 7 | from .png import y_axis as y_axis_png 8 | from .png import bar_series as bar_series_png 9 | from .png import line_series as line_series_png 10 | 11 | from .pdf import title as title_pdf 12 | from .pdf import x_axis as x_axis_pdf 13 | from .pdf import y_axis as y_axis_pdf 14 | from .pdf import bar_series as bar_series_pdf 15 | from .pdf import line_series as line_series_pdf 16 | 17 | renderers = { 18 | 'png' : { 19 | 'title' : title_png, 20 | 'x_axis' : x_axis_png, 21 | 'y_axis' : y_axis_png, 22 | 'bar_series' : bar_series_png, 23 | 'line_series' : line_series_png 24 | }, 25 | 'pdf' : { 26 | 'title' : title_pdf, 27 | 'x_axis' : x_axis_pdf, 28 | 'y_axis' : y_axis_pdf, 29 | 'bar_series' : bar_series_pdf, 30 | 'line_series' : line_series_pdf 31 | } 32 | } 33 | 34 | def draw(format, element, chart, output): 35 | renderers[format][element].draw(chart, output) 36 | 37 | -------------------------------------------------------------------------------- /chapter-4/test_charter.py: -------------------------------------------------------------------------------- 1 | import charter 2 | chart = charter.new_chart() 3 | charter.set_title(chart, "Wild Parrot Deaths per Year") 4 | charter.set_x_axis(chart, 5 | ["2009", "2010", "2011", "2012", "2013", 6 | "2014", "2015"]) 7 | charter.set_y_axis(chart, minimum=0, maximum=700, 8 | labels=[0, 100, 200, 300, 400, 500, 600, 700]) 9 | charter.set_series(chart, [250, 270, 510, 420, 680, 580, 450]) 10 | charter.set_series_type(chart, "bar") 11 | charter.generate_chart(chart, "chart.pdf") 12 | 13 | -------------------------------------------------------------------------------- /chapter-5/abstraction/happy_hour.py: -------------------------------------------------------------------------------- 1 | # happy_hour.py 2 | # 3 | # This program demonstrates how to calculate "happy hour", where happy hour 4 | # takes place each day between 5 and 6 pm, except on Sundays, Christmas Day and 5 | # Easter. 6 | 7 | import datetime 8 | 9 | ############################################################################# 10 | 11 | def is_happy_hour(): 12 | today = datetime.date.today() 13 | 14 | easter_sunday = calc_easter_sunday(today.year) 15 | easter_friday = easter_sunday - datetime.timedelta(days=2) 16 | 17 | if today == easter_friday: 18 | return False 19 | 20 | if today.month == 12 and today.day == 25: # Christmas day. 21 | return False 22 | 23 | if today.weekday() == 6: # Sunday. 24 | return False 25 | 26 | if datetime.datetime.now().hour == 17: # 5pm. 27 | return True 28 | 29 | return False 30 | 31 | ############################################################################# 32 | 33 | # The following function calculates Easter Sunday for a given year, returning a 34 | # datetime.date object. This code is taken from: 35 | # 36 | # http://code.activestate.com/recipes/576517 37 | 38 | def calc_easter_sunday(year): 39 | a = year % 19 40 | b = year // 100 41 | c = year % 100 42 | d = (19 * a + b - b // 4 - ((b - (b + 8) // 25 + 1) // 3) + 15) % 30 43 | e = (32 + 2 * (b % 4) + 2 * (c // 4) - d - (c % 4)) % 7 44 | f = d + e - 7 * ((a + 11 * d + 22 * e) // 451) + 114 45 | month = f // 31 46 | day = f % 31 + 1 47 | return datetime.date(year, month, day) 48 | 49 | ############################################################################# 50 | 51 | if __name__ == "__main__": 52 | # Testing code. 53 | 54 | if is_happy_hour(): 55 | print "It's happy hour!" 56 | else: 57 | print "It's not happy hour." 58 | 59 | -------------------------------------------------------------------------------- /chapter-5/dynamic imports/load_module.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | module_name = input("Load module: ") 4 | if module_name != "": 5 | module = importlib.import_module(module_name) 6 | module.say_hello() 7 | 8 | -------------------------------------------------------------------------------- /chapter-5/dynamic imports/module_a.py: -------------------------------------------------------------------------------- 1 | def say_hello(): 2 | print("Hello from module_a") 3 | 4 | -------------------------------------------------------------------------------- /chapter-5/dynamic imports/module_b.py: -------------------------------------------------------------------------------- 1 | def say_hello(): 2 | print("Hello from module_b") 3 | 4 | -------------------------------------------------------------------------------- /chapter-5/dynamic imports/module_c.py: -------------------------------------------------------------------------------- 1 | def say_hello(): 2 | print("Hello from module_c") 3 | 4 | -------------------------------------------------------------------------------- /chapter-5/encapsulation/recipes.py: -------------------------------------------------------------------------------- 1 | # recipes.py 2 | # 3 | # This module demonstrate the concept of encapsulation within a Python module. 4 | 5 | def new(name, num_servings): 6 | """ Create and return a new recipe. 7 | """ 8 | return {'name' : name, 9 | 'num_servings' : num_servings, 10 | 'instructions' : [], 11 | 'ingredients' : []} 12 | 13 | 14 | def add_instruction(recipe, instruction): 15 | """ Add an instruction to the recipe. 16 | """ 17 | recipe['instructions'].append(instruction) 18 | 19 | 20 | def add_ingredient(recipe, ingredient, amount, units): 21 | """ Add an ingredient to the recipe. 22 | """ 23 | recipe['ingredients'].append({'ingredient' : ingredient, 24 | 'amount' : amount, 25 | 'units' : units}) 26 | 27 | 28 | def get_name(recipe): 29 | return recipe['name'] 30 | 31 | 32 | def get_num_servings(recipe): 33 | return recipe['num_servings'] 34 | 35 | 36 | def get_instructions(recipe): 37 | return recipe['instructions'] 38 | 39 | 40 | def get_ingredients(recipe): 41 | return recipe['ingredients'] 42 | 43 | 44 | def to_string(recipe, num_servings): 45 | """ Return a list of strings with a description of this recipe. 46 | 47 | The recipe will be customized for the given number of servings. 48 | """ 49 | s = [] 50 | s.append("Recipe for {}, {} servings:".format(recipe['name'], 51 | num_servings)) 52 | s.append("") 53 | s.append("Ingredients:") 54 | s.append("") 55 | for ingredient in recipe['ingredients']: 56 | s.append(" {} - {} {}".format(ingredient['ingredient'], 57 | ingredient['amount'] * num_servings / 58 | recipe['num_servings'], 59 | ingredient['units'])) 60 | s.append("") 61 | s.append("Instructions:") 62 | s.append("") 63 | for i,instruction in enumerate(recipe['instructions']): 64 | s.append("{}. {}".format(i+1, instruction)) 65 | 66 | return s 67 | 68 | -------------------------------------------------------------------------------- /chapter-5/plugins/load_plugin.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import sys 3 | import os.path 4 | 5 | plugin_name = input("Load plugin: ") 6 | if plugin_name != "": 7 | plugin_dir = os.path.join(os.path.dirname(__file__), "plugins") 8 | sys.path.append(plugin_dir) 9 | plugin = importlib.import_module(plugin_name) 10 | plugin.say_hello() 11 | 12 | -------------------------------------------------------------------------------- /chapter-5/plugins/plugins/plugin_a.py: -------------------------------------------------------------------------------- 1 | def say_hello(): 2 | print("Hello from plugin_a") 3 | 4 | -------------------------------------------------------------------------------- /chapter-5/plugins/plugins/plugin_b.py: -------------------------------------------------------------------------------- 1 | def say_hello(): 2 | print("Hello from plugin_b") 3 | 4 | -------------------------------------------------------------------------------- /chapter-5/plugins/plugins/plugin_c.py: -------------------------------------------------------------------------------- 1 | def say_hello(): 2 | print("Hello from plugin_c") 3 | 4 | -------------------------------------------------------------------------------- /chapter-5/wrappers/detect_unusual_transfers.py: -------------------------------------------------------------------------------- 1 | # detect_unusual_transfers.py 2 | 3 | import random 4 | import numpy_wrapper as npw 5 | 6 | ############################################################################# 7 | 8 | BANK_CODES = ["AMERUS33", "CERYUS33", "EQTYUS44", 9 | "LOYDUS33", "SYNEUS44", "WFBIUS6S"] 10 | 11 | BRANCH_IDS = ["125000249", "125000252", "125000371", 12 | "125000402", "125000596", "125001067"] 13 | 14 | ############################################################################# 15 | 16 | def main(): 17 | """ Our main program. 18 | """ 19 | # Create 10,000 random transfers. 20 | 21 | days = [1, 2, 3, 4, 5, 6, 7, 8] 22 | transfers = [] # List of (day, bank_code, branch_id, amount) tuples. 23 | 24 | for i in range(10000): 25 | day = random.choice(days) 26 | bank_code = random.choice(BANK_CODES) 27 | branch_id = random.choice(BRANCH_IDS) 28 | amount = random.randint(1000, 1000000) 29 | 30 | transfers.append((day, bank_code, branch_id, amount)) 31 | 32 | # Now process the transfers, grouping them by day and building a NumPy 33 | # array mapping each branch ID and bank code combination to the total for 34 | # that branch and bank for that day. 35 | 36 | transfers_by_day = {} 37 | for day in days: 38 | transfers_by_day[day] = npw.new(num_rows=len(BANK_CODES), 39 | num_cols=len(BRANCH_IDS)) 40 | 41 | for day,bank_code,branch_id,amount in transfers: 42 | array = transfers_by_day[day] 43 | row = BRANCH_IDS.index(branch_id) 44 | col = BANK_CODES.index(bank_code) 45 | array[row][col] = array[row][col] + amount 46 | 47 | # Get the most recent day. 48 | 49 | latest_day = max(days) 50 | 51 | # Collect the transfers for all days other than the latest one. 52 | 53 | transfers_to_average = [] 54 | for day in days: 55 | if day != latest_day: 56 | transfers_to_average.append(transfers_by_day[day]) 57 | 58 | # Get the transfers for the current day. 59 | 60 | current = transfers_by_day[latest_day] 61 | 62 | # Calculate the average for each day other than the last one. 63 | 64 | average = npw.average(transfers_to_average) 65 | 66 | # Find the entries in the current day which are more than 150% of the 67 | # average. 68 | 69 | unusual_transfers = current > average * 1.5 70 | 71 | for row,col in npw.get_indices(unusual_transfers): 72 | branch_id = BRANCH_IDS[row] 73 | bank_code = BANK_CODES[col] 74 | average_amt = int(average[row][col]) 75 | current_amt = current[row][col] 76 | 77 | print("Branch {} transferred ${:,d}".format(branch_id, 78 | current_amt) + 79 | " to bank {}, average = ${:,d}".format(bank_code, 80 | average_amt)) 81 | 82 | ############################################################################# 83 | 84 | if __name__ == "__main__": 85 | main() 86 | 87 | -------------------------------------------------------------------------------- /chapter-5/wrappers/numpy_wrapper.py: -------------------------------------------------------------------------------- 1 | # numpy_wrapper 2 | # 3 | # A wrapper around the NumPy library that makes it easier to use for simple 4 | # array mathematics. 5 | 6 | import numpy 7 | 8 | def new(num_rows, num_cols): 9 | return numpy.zeros((num_rows, num_cols), dtype=numpy.int32) 10 | 11 | def average(arrays_to_average): 12 | return numpy.mean(numpy.array(arrays_to_average), axis=0) 13 | 14 | def get_indices(array): 15 | return numpy.transpose(array.nonzero()) 16 | 17 | -------------------------------------------------------------------------------- /chapter-6/quantities/README.txt: -------------------------------------------------------------------------------- 1 | About the `quantities` package 2 | ------------------------------ 3 | 4 | The `quantities` package allows you to define and work with various quantities. 5 | A quantity is a number along with an associated unit, for example: 6 | 7 | 6 inches 8 | 21.2 square kilometers 9 | 0.75 cups 10 | 11 | Using this package, you can define new quantities using the `quantities.new()` 12 | function, or convert a string into a quantity using `quantities.parse()`. For 13 | example: 14 | 15 | >>> q1 = quantities.new(6, "inch") 16 | >>> q2 = quantities.parse("21.2 square kilometers") 17 | >>> q3 = quantities.new(0.75, "cups") 18 | 19 | You can then retrieve information about these quantities by calling the 20 | appropriate functions, for example: 21 | 22 | >>> print(quantities.kind(q1)) 23 | length 24 | >>> print(quantities.value(q2)) 25 | 25 26 | >>> print(quantities.units(q3) 27 | cup 28 | 29 | You can also print a quantity directly to get a summary of its contents: 30 | 31 | >>> print(q1) 32 | 6 inch 33 | 34 | Once you have a quantity, you can convert it into a new quantity in any 35 | compatible unit. For example: 36 | 37 | >>> print(quantities.convert(q1, "mm")) 38 | 152.4 39 | >>> print(quantities.convert(q2, "hectare")) 40 | 100.0 41 | >>> print(quantities.convert(q3, "inch")) 42 | Traceback (most recent call last): 43 | File "