├── README.md ├── ch01 ├── 01report1 │ └── report.py ├── 02report2 │ └── report.py ├── 03capitals1 │ └── capitals.py ├── 04capitals2 │ └── capitals.py ├── 05capitals3 │ └── capitals.py └── uscapitals-data.txt ├── ch02 ├── 01sales_tax1 │ └── sales_tax.py ├── 02sales_tax2 │ └── sales_tax.py ├── 03sales_tax3 │ ├── receipt.py │ └── sales_tax.py ├── 04modules │ ├── receipt1.py │ ├── receipt2.py │ ├── receipt3.py │ ├── receipt4.py │ └── sales_tax.py ├── 05time │ ├── dateboth.py │ ├── dateboth2.py │ ├── datetime-example.py │ └── time-example.py ├── 06datemylib │ ├── mycoollibrary.py │ └── time.py ├── 07sanbaka │ ├── sanbaka1.py │ ├── sanbaka2.py │ └── sanbaka3.py ├── 08group │ ├── groups1.py │ └── groups2.py └── 09janken │ ├── janken1-j.py │ ├── janken1.py │ ├── janken2.py │ ├── janken3-j.py │ ├── janken3.py │ └── janken4.py ├── ch03 ├── 01reviews1 │ └── reviews.py ├── 02reviews2 │ └── reviews.py ├── 03greeter1 │ └── greeter.py ├── 04greeter2 │ └── greeter.py ├── 05procedural │ └── procedural.py ├── 06functional │ ├── for-loop-functional.js │ ├── for-loop-python.py │ ├── functional-python.py │ ├── imperative-python.py │ └── partial.py ├── 07plotly │ ├── plotly-example.py │ └── plotly-example2.py └── 08composition │ └── composition.py ├── ch04 ├── 01forloop │ ├── forloop.py │ ├── forloop1.py │ ├── forloop2.py │ ├── forloop3.py │ └── forloop4.py ├── 02colors │ ├── all-favorite-colors.txt │ ├── color-at-once.py │ ├── color-one-at-a-time.py │ └── color-set.py ├── 03generator │ └── generators.py ├── 04counting │ ├── counting1.py │ ├── counting2.py │ ├── counting3.py │ └── counting4.py ├── 05timing │ ├── timing1.py │ └── timing2.py └── 06cpu_profiling │ └── cpu_profiling.py ├── ch05 ├── 01mean │ └── calculate_mean.py ├── 02unittest1 │ ├── product.py │ └── test_product.py ├── 03unittest2 │ ├── product.py │ └── test_product.py ├── 04unittest3 │ ├── product.py │ └── test_product.py ├── 05unittest4 │ ├── product.py │ └── test_product.py ├── 06unittest5 │ ├── cart.py │ ├── product.py │ ├── test_cart.py │ └── test_product.py ├── 07testdouble │ ├── cart.py │ ├── product.py │ ├── tax.py │ ├── test_cart.py │ ├── test_product.py │ └── test_tax.py ├── 08tryitout1 │ ├── cart.py │ ├── product.py │ ├── tax.py │ ├── test_cart.py │ ├── test_product.py │ └── test_tax.py ├── 09tryitout2 │ ├── cart.py │ ├── product.py │ ├── tax.py │ ├── test_cart.py │ ├── test_product.py │ └── test_tax.py ├── 10goodtest1 │ ├── cart.py │ ├── product.py │ ├── tax.py │ ├── test_cart.py │ ├── test_product.py │ └── test_tax.py ├── 11goodtest2 │ ├── cart.py │ ├── product.py │ ├── tax.py │ ├── test_cart.py │ ├── test_product.py │ └── test_tax.py ├── 12pytest1 │ ├── cart.py │ ├── product.py │ ├── tax.py │ ├── test_cart.py │ ├── test_product.py │ └── test_tax.py └── 13pytest2 │ ├── cart.py │ ├── product.py │ ├── tax.py │ ├── test_cart.py │ ├── test_product.py │ └── test_tax.py ├── ch06 └── bark1 │ ├── bark.py │ ├── commands.py │ └── database.py ├── ch07 ├── 01bicycle1 │ └── bicycle.py ├── 02bicycle2 │ └── bicycle.py ├── 03bicycle3 │ └── bicycle.py ├── 04bark2 │ ├── bark.py │ ├── commands.py │ └── database.py └── 05bark3 │ ├── bark.py │ ├── commands.py │ └── database.py ├── ch08 ├── 01gastropods1 │ └── gastropods.py ├── 02birds │ └── birds.py ├── 03bicycle │ └── bicycle.py ├── 04bicycle2 │ └── bicycle.py ├── 05banking │ └── banking.py ├── 06cats1 │ └── cats.py ├── 07cats2 │ └── cats.py ├── 08predators1 │ └── predators.py ├── 09predators2 │ └── predators.py ├── 10predators3 │ └── predators.py ├── 11predators4 │ └── predators.py └── 12bark4 │ ├── bark.py │ ├── commands.py │ └── database.py ├── ch09 ├── 01complexity │ └── complexity.py ├── 02configuration1 │ └── configuration.py ├── 03configuration2 │ └── configuration.py ├── 04configuration3 │ └── configuration.py ├── 05configuration4 │ └── configuration.py ├── 06configuration5 │ └── configuration.py ├── 07configuration6 │ └── configuration.py ├── 08book1 │ └── book.py ├── 09book2 │ └── book.py ├── 10book3 │ └── book.py ├── 11book4 │ └── book.py ├── 12book5 │ └── book.py └── 13book6 │ └── book.py └── ch10 ├── 01book1 └── book.py ├── 02book2 └── book.py ├── 03search1 └── search.py ├── 04search2 └── search.py ├── 05search3 └── search.py ├── 06search4 └── search.py ├── 07bark5 ├── bark.py ├── commands.py └── database.py ├── 08bark6 ├── bark.py ├── commands.py └── database.py └── 09bark7 ├── bark.py ├── commands.py ├── database.py └── persistence.py /README.md: -------------------------------------------------------------------------------- 1 | # 『プロフェッショナルPython ソフトウェアデザインの原則と実践』 例題 2 | 3 | ![表紙](https://www.marlin-arms.com/jpn/arts/books/python-pro.png) 4 | 5 | [インプレス](https://book.impress.co.jp/books/1120101043)発行(Dane Hillard著、武舎広幸訳)の『[プロフェッショナルPython](https://www.marlin-arms.com/support/python-pro/)』の例題用リポジトリです。 6 | 7 |
8 |
16 | 17 | ## ファイル構成 18 | 19 | |フォルダ名 |説明 | 20 | |:-- |:-- | 21 | |Chapter01 |1章の例題 | 22 | |Chapter02 |2章の例題 | 23 | |Chapter03 |3章の例題 | 24 | |... |... | 25 | |Chapter10 |10章の例題 | 26 | 27 | 31 | 32 | -------------------------------------------------------------------------------- /ch01/01report1/report.py: -------------------------------------------------------------------------------- 1 | col1_name = col2_name = col3_name = col4_name = "ABC" 2 | first_val = second_val = third_val = fourth_val = "99" 3 | 4 | #@@range_begin(list1) 5 | print(col1_name + ',' + col2_name + ',' + col3_name + ',' + col4_name) 6 | print(first_val + ',' + second_val + ',' + third_val + ',' + fourth_val) 7 | #@@range_end(list1) 8 | 9 | -------------------------------------------------------------------------------- /ch01/02report2/report.py: -------------------------------------------------------------------------------- 1 | col1_name = col2_name = col3_name = col4_name = "ABC" 2 | first_val = second_val = third_val = fourth_val = "99" 3 | 4 | #@@range_begin(list1) 5 | DELIMITER = '\t' 6 | print(DELIMITER.join([col1_name, col2_name, col3_name, col4_name])) 7 | print(DELIMITER.join([first_val, second_val, third_val, fourth_val])) 8 | #@@range_end(list1) 9 | 10 | -------------------------------------------------------------------------------- /ch01/03capitals1/capitals.py: -------------------------------------------------------------------------------- 1 | us_capitals_by_state = { # 米国の州とその州都の「辞書」 2 | 'Alabama': 'Montgomery', 3 | 'Alaska': 'Juneau', 4 | 'Arizona': 'Phoenix', 5 | 'Arkansas': 'Little Rock', 6 | 'California': 'Sacramento', 7 | 'Colorado': 'Denver', 8 | 'Connecticut': 'Hartford', 9 | 'Delaware': 'Dover', 10 | 'Florida': 'Tallahassee', 11 | 'Georgia': 'Atlanta', 12 | 'Hawaii': 'Honolulu', 13 | 'Idaho': 'Boise', 14 | 'Illinois': 'Springfield', 15 | 'Indiana': 'Indianapolis', 16 | 'Iowa': 'Des Moines', 17 | 'Kansas': 'Topeka', 18 | 'Kentucky': 'Frankfort', 19 | 'Louisiana': 'Baton Rouge', 20 | 'Maine': 'Augusta', 21 | 'Maryland': 'Annapolis', 22 | 'Massachusetts': 'Boston', 23 | 'Michigan': 'Lansing', 24 | 'Minnesota': 'St. Paul', 25 | 'Mississippi': 'Jackson', 26 | 'Missouri': 'Jefferson City', 27 | 'Montana': 'Helena', 28 | 'Nebraska': 'Lincoln', 29 | 'Nevada': 'Carson City', 30 | 'New Hampshire': 'Concord', 31 | 'New Jersey': 'Trenton', 32 | 'New Mexico': 'Santa Fe', 33 | 'New York': 'Albany', 34 | 'North Carolina': 'Raleigh', 35 | 'North Dakota': 'Bismarck', 36 | 'Ohio': 'Columbus', 37 | 'Oklahoma': 'Oklahoma City', 38 | 'Oregon': 'Salem', 39 | 'Pennsylvania': 'Harrisburg', 40 | 'Rhode Island': 'Providence', 41 | 'South Carolina': 'Columbia', 42 | 'South Dakota': 'Pierre', 43 | 'Tennessee': 'Nashville', 44 | 'Texas': 'Austin', 45 | 'Utah': 'Salt Lake City', 46 | 'Vermont': 'Montpelier', 47 | 'Virginia': 'Richmond', 48 | 'Washington': 'Olympia', 49 | 'West Virginia': 'Charleston', 50 | 'Wisconsin': 'Madison', 51 | 'Wyoming': 'Cheyenne' 52 | } 53 | 54 | capitals = us_capitals_by_state.values() 55 | sorted_capitals = sorted(capitals) 56 | print(sorted_capitals) 57 | -------------------------------------------------------------------------------- /ch01/04capitals2/capitals.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください。本文に引用するためのものです。 2 | def get_united_states_capitals(): 3 | us_capitals_by_state = { 4 | 'Alabama': 'Montgomery', 5 | 'Alaska': 'Juneau', 6 | #@@range_end(list1) # ←この行は無視してください。本文に引用するためのものです。 7 | 'Arizona': 'Phoenix', 8 | 'Arkansas': 'Little Rock', 9 | 'California': 'Sacramento', 10 | 'Colorado': 'Denver', 11 | 'Connecticut': 'Hartford', 12 | 'Delaware': 'Dover', 13 | 'Florida': 'Tallahassee', 14 | 'Georgia': 'Atlanta', 15 | 'Hawaii': 'Honolulu', 16 | 'Idaho': 'Boise', 17 | 'Illinois': 'Springfield', 18 | 'Indiana': 'Indianapolis', 19 | 'Iowa': 'Des Moines', 20 | 'Kansas': 'Topeka', 21 | 'Kentucky': 'Frankfort', 22 | 'Louisiana': 'Baton Rouge', 23 | 'Maine': 'Augusta', 24 | 'Maryland': 'Annapolis', 25 | 'Massachusetts': 'Boston', 26 | 'Michigan': 'Lansing', 27 | 'Minnesota': 'St. Paul', 28 | 'Mississippi': 'Jackson', 29 | 'Missouri': 'Jefferson City', 30 | 'Montana': 'Helena', 31 | 'Nebraska': 'Lincoln', 32 | 'Nevada': 'Carson City', 33 | 'New Hampshire': 'Concord', 34 | 'New Jersey': 'Trenton', 35 | 'New Mexico': 'Santa Fe', 36 | 'New York': 'Albany', 37 | 'North Carolina': 'Raleigh', 38 | 'North Dakota': 'Bismarck', 39 | 'Ohio': 'Columbus', 40 | 'Oklahoma': 'Oklahoma City', 41 | 'Oregon': 'Salem', 42 | 'Pennsylvania': 'Harrisburg', 43 | 'Rhode Island': 'Providence', 44 | 'South Carolina': 'Columbia', 45 | 'South Dakota': 'Pierre', 46 | 'Tennessee': 'Nashville', 47 | 'Texas': 'Austin', 48 | 'Utah': 'Salt Lake City', 49 | 'Vermont': 'Montpelier', 50 | 'Virginia': 'Richmond', 51 | 'Washington': 'Olympia', 52 | 'West Virginia': 'Charleston', 53 | 'Wisconsin': 'Madison', 54 | #@@range_begin(list2) # ←この行は無視してください。本文に引用するためのものです。 55 | 'Wyoming': 'Cheyenn', 56 | } 57 | 58 | capitals = us_capitals_by_state.values() 59 | return sorted(capitals) 60 | #@@range_end(list2) # ←この行は無視してください。本文に引用するためのものです。 61 | 62 | print(get_united_states_capitals()) 63 | 64 | -------------------------------------------------------------------------------- /ch01/05capitals3/capitals.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください。本文に引用するためのものです。 2 | US_CAPITALS_BY_STATE = { 3 | 'Alabama': 'Montgomery', 4 | 'Alaska': 'Juneau', 5 | #@@range_end(list1) # ←この行は無視してください。本文に引用するためのものです。 6 | 'Arizona': 'Phoenix', 7 | 'Arkansas': 'Little Rock', 8 | 'California': 'Sacramento', 9 | 'Colorado': 'Denver', 10 | 'Connecticut': 'Hartford', 11 | 'Delaware': 'Dover', 12 | 'Florida': 'Tallahassee', 13 | 'Georgia': 'Atlanta', 14 | 'Hawaii': 'Honolulu', 15 | 'Idaho': 'Boise', 16 | 'Illinois': 'Springfield', 17 | 'Indiana': 'Indianapolis', 18 | 'Iowa': 'Des Moines', 19 | 'Kansas': 'Topeka', 20 | 'Kentucky': 'Frankfort', 21 | 'Louisiana': 'Baton Rouge', 22 | 'Maine': 'Augusta', 23 | 'Maryland': 'Annapolis', 24 | 'Massachusetts': 'Boston', 25 | 'Michigan': 'Lansing', 26 | 'Minnesota': 'St. Paul', 27 | 'Mississippi': 'Jackson', 28 | 'Missouri': 'Jefferson City', 29 | 'Montana': 'Helena', 30 | 'Nebraska': 'Lincoln', 31 | 'Nevada': 'Carson City', 32 | 'New Hampshire': 'Concord', 33 | 'New Jersey': 'Trenton', 34 | 'New Mexico': 'Santa Fe', 35 | 'New York': 'Albany', 36 | 'North Carolina': 'Raleigh', 37 | 'North Dakota': 'Bismarck', 38 | 'Ohio': 'Columbus', 39 | 'Oklahoma': 'Oklahoma City', 40 | 'Oregon': 'Salem', 41 | 'Pennsylvania': 'Harrisburg', 42 | 'Rhode Island': 'Providence', 43 | 'South Carolina': 'Columbia', 44 | 'South Dakota': 'Pierre', 45 | 'Tennessee': 'Nashville', 46 | 'Texas': 'Austin', 47 | 'Utah': 'Salt Lake City', 48 | 'Vermont': 'Montpelier', 49 | 'Virginia': 'Richmond', 50 | 'Washington': 'Olympia', 51 | 'West Virginia': 'Charleston', 52 | 'Wisconsin': 'Madison', 53 | #@@range_begin(list2) 54 | 'Wyoming': 'Cheyenn' 55 | } 56 | 57 | US_CAPITALS = sorted(US_CAPITALS_BY_STATE.values()) 58 | #@@range_end(list2) 59 | print(US_CAPITALS); 60 | -------------------------------------------------------------------------------- /ch01/uscapitals-data.txt: -------------------------------------------------------------------------------- 1 | ## 出典 https://www.britannica.com/topic/list-of-state-capitals-in-the-United-States-2119210 2 | 'Alabama': 'Montgomery', 3 | 'Alaska': 'Juneau', 4 | 'Arizona': 'Phoenix', 5 | 'Arkansas': 'Little Rock', 6 | 'California': 'Sacramento', 7 | 'Colorado': 'Denver', 8 | 'Connecticut': 'Hartford', 9 | 'Delaware': 'Dover', 10 | 'Florida': 'Tallahassee', 11 | 'Georgia': 'Atlanta', 12 | 'Hawaii': 'Honolulu', 13 | 'Idaho': 'Boise', 14 | 'Illinois': 'Springfield', 15 | 'Indiana': 'Indianapolis', 16 | 'Iowa': 'Des Moines', 17 | 'Kansas': 'Topeka', 18 | 'Kentucky': 'Frankfort', 19 | 'Louisiana': 'Baton Rouge', 20 | 'Maine': 'Augusta', 21 | 'Maryland': 'Annapolis', 22 | 'Massachusetts': 'Boston', 23 | 'Michigan': 'Lansing', 24 | 'Minnesota': 'St. Paul', 25 | 'Mississippi': 'Jackson', 26 | 'Missouri': 'Jefferson City', 27 | 'Montana': 'Helena', 28 | 'Nebraska': 'Lincoln', 29 | 'Nevada': 'Carson City', 30 | 'New Hampshire': 'Concord', 31 | 'New Jersey': 'Trenton', 32 | 'New Mexico': 'Santa Fe', 33 | 'New York': 'Albany', 34 | 'North Carolina': 'Raleigh', 35 | 'North Dakota': 'Bismarck', 36 | 'Ohio': 'Columbus', 37 | 'Oklahoma': 'Oklahoma City', 38 | 'Oregon': 'Salem', 39 | 'Pennsylvania': 'Harrisburg', 40 | 'Rhode Island': 'Providence', 41 | 'South Carolina': 'Columbia', 42 | 'South Dakota': 'Pierre', 43 | 'Tennessee': 'Nashville', 44 | 'Texas': 'Austin', 45 | 'Utah': 'Salt Lake City', 46 | 'Vermont': 'Montpelier', 47 | 'Virginia': 'Richmond', 48 | 'Washington': 'Olympia', 49 | 'West Virginia': 'Charleston', 50 | 'Wisconsin': 'Madison', 51 | 'Wyoming': 'Cheyenne', 52 | -------------------------------------------------------------------------------- /ch02/01sales_tax1/sales_tax.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | def add_sales_tax(total, tax_rate): # 売上税を追加 3 | return total * tax_rate # 日本の消費税とは違い、最終消費者のみに課せられる 4 | #@@range_end(list1) 5 | 6 | def test(): 7 | total = 1000 8 | rate = 1.1 # 10%の売上税(日本の消費税とは違い、最終消費者のみに課せられる) 9 | grand_total = add_sales_tax(total, rate) 10 | print(f"合計金額={grand_total}") 11 | 12 | if __name__ == '__main__': test() # ほかのプログラムにimportされた場合は実行しない 13 | # 詳細は、『Python基礎&実践プログラミング [プロへのスキルアップ+プロジェクトサンプル]』(インプレス)の第10章などを参照 14 | -------------------------------------------------------------------------------- /ch02/02sales_tax2/sales_tax.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | TAX_RATES_BY_STATE = { # 州ごとの売上税率 3 | 'MI': 1.06, # ミシガン州 4 | # ... 5 | #@@range_end(list1) 6 | 'AK': 1.00, # アラスカ州 7 | 'AL': 1.04, # アラバマ州 8 | 'AR': 1.065, # アーカンソー州 9 | 'AZ': 1.056, # アリゾナ州 10 | # ... 11 | # ... 12 | # ... 13 | 'WY': 1.04 # ワイオミング州 14 | #@@range_begin(list2) # ←この行は無視してください。書籍の本文に引用するためのものです。 15 | } 16 | 17 | def add_sales_tax(total, state): 18 | return total * TAX_RATES_BY_STATE[state] 19 | #@@range_end(list2) # ←この行は無視してください。書籍の本文に引用するためのものです。 20 | 21 | def test(): 22 | total = 1000 23 | grand_total = add_sales_tax(total, 'MI') 24 | print(f"合計金額={grand_total}") 25 | 26 | if __name__ == '__main__': test() # ほかのプログラムにimportされた場合は実行しない 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /ch02/03sales_tax3/receipt.py: -------------------------------------------------------------------------------- 1 | from sales_tax import add_sales_tax 2 | 3 | def print_receipt(): 4 | total = 1000.0 5 | state = 'MI' 6 | print(f'合計: {total}') 7 | print(f'税込合計: {add_sales_tax(total, state)}') 8 | 9 | 10 | print_receipt() 11 | -------------------------------------------------------------------------------- /ch02/03sales_tax3/sales_tax.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 2 | TAX_RATES_BY_STATE = { # 州ごとの税率 3 | 'MI': 1.06, # ミシガン州 4 | # ... 5 | #@@range_end(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 6 | 'AK': 1.00, # アラスカ州 7 | 'AL': 1.04, # アラバマ州 8 | 'AR': 1.065, # アーカンソー州 9 | 'AZ': 1.056, # アリゾナ州 10 | # ... 11 | # ... 12 | # ... 13 | 'WY': 1.04 # ワイオミング州 14 | #@@range_begin(list2) # ←この行は無視してください。書籍の本文に引用するためのものです。 15 | } 16 | 17 | def add_sales_tax(total, state): 18 | tax_rate = TAX_RATES_BY_STATE[state] 19 | return total * tax_rate 20 | #@@range_end(list2) # ←この行は無視してください。書籍の本文に引用するためのものです。 21 | 22 | def test(): 23 | total = 1000 24 | grand_total = add_sales_tax(total, 'MI') 25 | print(f"合計金額={grand_total}") 26 | 27 | if __name__ == '__main__': test() # ほかのプログラムにimportされた場合は実行しない 28 | -------------------------------------------------------------------------------- /ch02/04modules/receipt1.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 2 | from sales_tax import add_sales_tax, add_state_tax, add_city_tax, add_local_millage_tax 3 | #@@range_end(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 4 | 5 | def print_receipt(): 6 | total = 1000.0 7 | state = 'MI' 8 | grand_total = add_local_millage_tax(add_city_tax(add_state_tax(add_sales_tax(total, state)))) 9 | print(f'合計: {grand_total}') 10 | 11 | print_receipt() 12 | 13 | 14 | -------------------------------------------------------------------------------- /ch02/04modules/receipt2.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 2 | from sales_tax import ( 3 | add_sales_tax, 4 | add_state_tax, 5 | add_city_tax, 6 | add_local_millage_tax # 財産にかかる税 7 | ) 8 | #@@range_end(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 9 | 10 | def print_receipt(): 11 | total = 1000.0 12 | state = 'MI' 13 | grand_total = add_local_millage_tax(add_city_tax(add_state_tax(add_sales_tax(total, state)))) 14 | print(f'合計: {grand_total}') 15 | 16 | print_receipt() 17 | -------------------------------------------------------------------------------- /ch02/04modules/receipt3.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 2 | import sales_tax 3 | 4 | def print_receipt(): 5 | total = 1000.0 6 | state = 'MI' 7 | grand_total = sales_tax.add_local_millage_tax(sales_tax.add_city_tax( 8 | sales_tax.add_state_tax(sales_tax.add_sales_tax(total, state)))) 9 | print(f'合計: {grand_total}') 10 | #@@range_end(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 11 | 12 | print_receipt() 13 | -------------------------------------------------------------------------------- /ch02/04modules/receipt4.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 2 | # ch02/04modules/receipt4.py 3 | from sales_tax import * 4 | 5 | def print_receipt(): 6 | total = 1000.0 7 | state = 'MI' 8 | # 「sales_tax.」は不要になるが、 9 | grand_total = add_local_millage_tax(add_city_tax( 10 | add_state_tax(add_sales_tax(total, state)))) 11 | print(f'合計: {grand_total}') 12 | #@@range_end(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 13 | 14 | print_receipt() 15 | -------------------------------------------------------------------------------- /ch02/04modules/sales_tax.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 2 | # ch02/04modules/sales_tax.py 3 | 4 | TAX_RATES_BY_STATE = { # 州ごとの税率 5 | 'MI': 1.06, # ミシガン州 6 | # ... 7 | #@@range_end(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 8 | 'AK': 1.00, # アラスカ州 9 | 'AL': 1.04, # アラバマ州 10 | 'AR': 1.065, # アーカンソー州 11 | 'AZ': 1.056, # アリゾナ州 12 | # ... 13 | # ... 14 | # ... 15 | 'WY': 1.04 # ワイオミング州 16 | #@@range_begin(list2) # ←この行は無視してください。書籍の本文に引用するためのものです。 17 | } 18 | 19 | def add_sales_tax(total, state): 20 | tax_rate = TAX_RATES_BY_STATE[state] 21 | return total * tax_rate 22 | #@@range_end(list2) # ←この行は無視してください。書籍の本文に引用するためのものです。 23 | 24 | def add_state_tax(total): 25 | return total * 1.1 26 | 27 | def add_city_tax(total): 28 | return total * 1.1 29 | 30 | def add_local_millage_tax(total): 31 | return total * 1.1 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /ch02/05time/dateboth.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | from datetime import time 3 | 4 | print(time()) 5 | -------------------------------------------------------------------------------- /ch02/05time/dateboth2.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 2 | import time 3 | import datetime 4 | 5 | now = time.time() 6 | midnight = datetime.time() 7 | #@@range_end(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 8 | print(F'Unix時間:', now) 9 | print(F'デフォルトの時刻:', midnight) 10 | -------------------------------------------------------------------------------- /ch02/05time/datetime-example.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | print(time()) 3 | -------------------------------------------------------------------------------- /ch02/05time/time-example.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | print(time()) 3 | -------------------------------------------------------------------------------- /ch02/06datemylib/mycoollibrary.py: -------------------------------------------------------------------------------- 1 | def datetime(): 2 | return "11:59:59" 3 | -------------------------------------------------------------------------------- /ch02/06datemylib/time.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 2 | import datetime 3 | 4 | from mycoollibrary import datetime as cooldatetime 5 | #@@range_end(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 6 | print(cooldatetime()) 7 | print(datetime.time()) 8 | 9 | 10 | -------------------------------------------------------------------------------- /ch02/07sanbaka/sanbaka1.py: -------------------------------------------------------------------------------- 1 | # 最初のバージョン 2 | names = ['ラリー', 'カーリー', 'モー'] 3 | message = '三ばか大将:' 4 | for index, name in enumerate(names): # namesの各要素をループ 5 | if index > 0: 6 | message += 'に' 7 | if index == len(names) - 1: 8 | message += '、それから' 9 | message += name 10 | print(message) 11 | -------------------------------------------------------------------------------- /ch02/07sanbaka/sanbaka2.py: -------------------------------------------------------------------------------- 1 | # 単純に繰り返す 2 | names = ['モー', 'ラリー', 'シェンプ'] 3 | message = '三ばか大将:' 4 | for index, name in enumerate(names): 5 | if index > 0: 6 | message += 'に' 7 | if index == len(names) - 1: 8 | message += '、それから' 9 | message += name 10 | print(message) 11 | 12 | names = ['ラリー', 'カーリー', 'モー'] 13 | message = '三ばか大将:' 14 | for index, name in enumerate(names): 15 | if index > 0: 16 | message += 'に' 17 | if index == len(names) -1: 18 | message += '、それから' 19 | message += name 20 | print(message) 21 | -------------------------------------------------------------------------------- /ch02/07sanbaka/sanbaka3.py: -------------------------------------------------------------------------------- 1 | # 関数にまとめる 2 | def introduce_stooges(names): # 三ばか大将を紹介 3 | message = '三ばか大将:' 4 | 5 | for index, name in enumerate(names): 6 | if index > 0: 7 | message += 'に' 8 | if index == len(names) - 1: 9 | message += '、それから' 10 | message += name 11 | print(message) 12 | 13 | introduce_stooges(['モー', 'ラリー', 'シェンプ']) 14 | introduce_stooges(['ラリー', 'カーリー', 'モー']) 15 | -------------------------------------------------------------------------------- /ch02/08group/groups1.py: -------------------------------------------------------------------------------- 1 | # ほかのグループにも対応 2 | def introduce(title, names): # 紹介する。グループ名と名前のリストが引数 3 | message = f'{title}: ' 4 | for index, name in enumerate(names): 5 | if index > 0: 6 | message += 'に' 7 | if index == len(names) - 1: 8 | message += '、それから' 9 | message += name 10 | print(message) 11 | 12 | introduce('三ばか大将', ['モー', 'ラリー', 'シェンプ']) 13 | introduce('三ばか大将', ['ラリー', 'カーリー', 'モー']) 14 | introduce('忍者タートルズ', ['ドナテロ', 'ファエロ', 'ミケランジェロ', 'レオナルド']) 15 | introduce('アルビンとチップマンクス', ['アルビン', 'サイモン', 'セオドア']) 16 | -------------------------------------------------------------------------------- /ch02/08group/groups2.py: -------------------------------------------------------------------------------- 1 | # 名前のリスト部分を別関数に 2 | def join_names(names): # 名前を合体(join)してひとつの文字列にする 3 | name_string = '' 4 | for index, name in enumerate(names): 5 | if index > 0: 6 | name_string += 'に' 7 | if index == len(names) -1: 8 | name_string += '、それから' 9 | name_string += name 10 | return name_string 11 | 12 | def introduce(title, names): 13 | print(f'{title}: {join_names(names)}') 14 | 15 | introduce('三ばか大将', ['モー', 'ラリー', 'シェンプ']) 16 | introduce('三ばか大将', ['ラリー', 'カーリー', 'モー']) 17 | introduce('忍者タートルズ', ['ドナテロ', 'ファエロ', 'ミケランジェロ', 'レオナルド']) 18 | introduce('アルビンとチップマンクス', ['アルビン', 'サイモン', 'セオドア']) 19 | introduce('桃太郎', ['イヌ', 'サル', 'キジ']) 20 | -------------------------------------------------------------------------------- /ch02/09janken/janken1-j.py: -------------------------------------------------------------------------------- 1 | # 日本語識別子版 2 | 3 | import random 4 | 5 | 手の選択肢 = ['グー', 'チョキ', 'パー'] 6 | print('(1) グー\n(2) チョキ\n(3) パー') 7 | ユーザーの選択 = 手の選択肢[int(input('「グー」か「チョキ」か「パー」を番号で選んでください: ')) - 1] 8 | print(f'あなたが選んだのは「{ユーザーの選択}」です。') 9 | コンピュータの選択 = random.choice(手の選択肢) 10 | print(f'コンピュータが選んだのは「{コンピュータの選択}」です。') 11 | if ユーザーの選択 == 'グー': 12 | if コンピュータの選択 == 'パー': 13 | print('残念でした。パーの勝ちです。') 14 | elif コンピュータの選択 == 'チョキ': 15 | print('おめでとうございます! グーの勝ちです。') 16 | else: 17 | print('引き分けです。') 18 | elif ユーザーの選択 == 'パー': 19 | if コンピュータの選択 == 'チョキ': 20 | print('残念でした。チョキの勝ちです。') 21 | elif コンピュータの選択 == 'グー': 22 | print('おめでとうございます! パーの勝ちです。') 23 | else: 24 | print('引き分けです。') 25 | elif ユーザーの選択 == 'チョキ': 26 | if コンピュータの選択 == 'グー': 27 | print('残念でした。グーの勝ちです。') 28 | elif コンピュータの選択 == 'パー': 29 | print('おめでとうございます! チョキの勝ちです。') 30 | else: 31 | print('引き分けです!') 32 | 33 | -------------------------------------------------------------------------------- /ch02/09janken/janken1.py: -------------------------------------------------------------------------------- 1 | # 最初のコード 2 | import random 3 | 4 | options = ['グー', 'チョキ', 'パー'] # 選択肢 5 | print('(1) グー\n(2) チョキ\n(3) パー') 6 | human_choice = options[int(input('「グー」か「チョキ」か「パー」を番号で選んでください: ')) - 1] 7 | print(f'あなたが選んだのは「{human_choice}」です。') 8 | computer_choice = random.choice(options) # コンピュータが選択したもの 9 | print(f'コンピュータが選んだのは「{computer_choice}」です。') 10 | if human_choice == 'グー': # 人間が選択したものが「グー」ならば 11 | if computer_choice == 'パー': 12 | print('残念でした。パーの勝ちです。') 13 | elif computer_choice == 'チョキ': 14 | print('おめでとうございます! グーの勝ちです。') 15 | else: 16 | print('引き分けです。') 17 | elif human_choice == 'パー': 18 | if computer_choice == 'チョキ': 19 | print('残念でした。チョキの勝ちです。') 20 | elif computer_choice == 'グー': 21 | print('おめでとうございます! パーの勝ちです。') 22 | else: 23 | print('引き分けです。') 24 | elif human_choice == 'チョキ': 25 | if computer_choice == 'グー': 26 | print('残念でした。グーの勝ちです。') 27 | elif computer_choice == 'パー': 28 | print('おめでとうございます! チョキの勝ちです。') 29 | else: 30 | print('引き分けです!') 31 | -------------------------------------------------------------------------------- /ch02/09janken/janken2.py: -------------------------------------------------------------------------------- 1 | # 関数を抽出 2 | import random 3 | 4 | OPTIONS = ['グー', 'チョキ', 'パー'] 5 | 6 | def get_computer_choice(): 7 | return random.choice(OPTIONS) 8 | 9 | def get_human_choice(): 10 | choice_number = int(input('「グー」か「チョキ」か「パー」を番号で選んでください: ')) 11 | return OPTIONS[choice_number - 1] 12 | 13 | def print_options(): 14 | print('\n'.join(f'({i}) {option.title()}' for i, option in enumerate(OPTIONS, 1))) 15 | 16 | def print_choices(human_choice, computer_choice): 17 | print(f'あなたが選んだのは「{human_choice}」です。') 18 | print(f'コンピュータが選んだのは「{computer_choice}」です。') 19 | 20 | def print_win_lose(human_choice, computer_choice, human_beats, human_loses_to): 21 | if computer_choice == human_loses_to: 22 | print(f'残念でした。「{computer_choice}」の勝ちです。') 23 | elif computer_choice == human_beats: 24 | print(f'おめでとうございます! 「{human_choice}」の勝ちです。') 25 | 26 | def print_result(human_choice, computer_choice): 27 | if human_choice == computer_choice: 28 | print('引き分けです。') 29 | 30 | if human_choice == 'グー': 31 | print_win_lose('グー', computer_choice, 'チョキ', 'パー') 32 | elif human_choice == 'パー': 33 | print_win_lose('パー', computer_choice, 'グー', 'チョキ') 34 | elif human_choice == 'チョキ': 35 | print_win_lose('チョキ', computer_choice, 'パー', 'グー') 36 | 37 | print_options() # 選択肢を表示 38 | human_choice = get_human_choice() # 人間が選択したもの 39 | computer_choice = get_computer_choice() # コンピュータが選択したもの 40 | print_choices(human_choice, computer_choice) # 選択したものを印刷 41 | print_result(human_choice, computer_choice) # 結果を印刷 42 | -------------------------------------------------------------------------------- /ch02/09janken/janken3-j.py: -------------------------------------------------------------------------------- 1 | # 日本語識別子版 2 | # 属性にアクセスするのにselfを用いる 3 | import random 4 | 5 | 手の選択肢 = ['グー', 'チョキ', 'パー'] # Rock, Scissors, Paper 6 | 7 | 8 | class ジャンケンシミュレータ: 9 | def __init__(self): 10 | self.コンピュータの選択肢 = None 11 | self.ユーザーの選択肢 = None 12 | 13 | def get_コンピュータの選択肢(self): # <1> 14 | self.コンピュータの選択肢 = random.choice(手の選択肢) 15 | 16 | def get_ユーザーの選択肢(self): 17 | 選択肢の番号 = int(input('「グー」か「チョキ」か「パー」を番号で選んでください: ')) 18 | self.ユーザーの選択肢 = 手の選択肢[選択肢の番号 - 1] 19 | 20 | def print_手の選択肢(self): 21 | print('\n'.join(f'({i}) {ひとつの手.title()}' for i, ひとつの手 in enumerate(手の選択肢, 1))) 22 | 23 | def print_それぞれの手(self): # <2> 24 | print(f'あなたが選んだのは「{self.ユーザーの選択肢}」です。') # <3> 25 | print(f'コンピュータが選んだのは「{self.コンピュータの選択肢}」です。') 26 | 27 | def print_勝敗(self, 人間が勝つ手, 人間が負ける手): 28 | if self.コンピュータの選択肢 == 人間が負ける手: 29 | print(f'残念でした。{self.コンピュータの選択肢}の勝ちです。') 30 | elif self.コンピュータの選択肢 == 人間が勝つ手: 31 | print(f'おめでとうございます! {self.ユーザーの選択肢}の勝ちです。') 32 | 33 | def print_結果(self): 34 | if self.ユーザーの選択肢 == self.コンピュータの選択肢: 35 | print('引き分けです。') 36 | 37 | if self.ユーザーの選択肢 == 'グー': 38 | self.print_勝敗('チョキ', 'パー') 39 | elif self.ユーザーの選択肢 == 'パー': 40 | self.print_勝敗('グー', 'チョキ') 41 | elif self.ユーザーの選択肢 == 'チョキ': 42 | self.print_勝敗('パー', 'グー') 43 | 44 | def シミュレートする(self): 45 | self.print_手の選択肢() 46 | self.get_ユーザーの選択肢() 47 | self.get_コンピュータの選択肢() 48 | self.print_それぞれの手() 49 | self.print_結果() 50 | 51 | # 実行 52 | ジャンケンマシン = ジャンケンシミュレータ() 53 | ジャンケンマシン.シミュレートする() 54 | -------------------------------------------------------------------------------- /ch02/09janken/janken3.py: -------------------------------------------------------------------------------- 1 | # 関数をクラスのメソッドにする 2 | import random 3 | OPTIONS = ['グー', 'チョキ', 'パー'] 4 | 5 | class JankenSimulator: 6 | #@@range_begin(list2) # ←この行は無視してください。本文に引用するためのものです。 7 | def __init__(self): 8 | self.computer_choice = None 9 | self.human_choice = None 10 | #@@range_end(list2) # ←この行は無視してください。本文に引用するためのものです。 11 | 12 | def get_computer_choice(self): # <1> 13 | return random.choice(OPTIONS) 14 | 15 | def get_human_choice(self): 16 | choice_number = int(input('「グー」か「チョキ」か「パー」を番号で選んでください: ')) 17 | return OPTIONS[choice_number - 1] 18 | 19 | def print_options(self): 20 | print('\n'.join(f'({i}) {option.title()}' for i, option in enumerate(OPTIONS, 1))) 21 | 22 | def print_choices(self, human_choice, computer_choice): # <2> 23 | print(f'あなたが選んだのは「{human_choice}」です。') 24 | print(f'コンピュータが選んだのは「{computer_choice}」です。') 25 | 26 | def print_win_lose(self, human_choice, computer_choice, human_beats, human_loses_to): 27 | if computer_choice == human_loses_to: 28 | print(f'残念でした。{computer_choice}の勝ちです。') 29 | elif computer_choice == human_beats: 30 | print(f'おめでとうございます! {human_choice}の勝ちです。') 31 | 32 | def print_result(self, human_choice, computer_choice): 33 | if human_choice == computer_choice: 34 | print('引き分けです。') 35 | 36 | if human_choice == 'グー': 37 | self.print_win_lose('グー', computer_choice, 'チョキ', 'パー') 38 | elif human_choice == 'パー': 39 | self.print_win_lose('パー', computer_choice, 'グー', 'チョキ') 40 | elif human_choice == 'チョキ': 41 | self.print_win_lose('チョキ', computer_choice, 'パー', 'グー') 42 | 43 | #@@range_begin(list1) # ←この行は無視してください。本文に引用するためのものです。 44 | def simulate(self): 45 | self.print_options() 46 | human_choice = self.get_human_choice() 47 | computer_choice = self.get_computer_choice() 48 | self.print_choices(human_choice, computer_choice) 49 | self.print_result(human_choice, computer_choice) 50 | #@@range_end(list1) # ←この行は無視してください。本文に引用するためのものです。 51 | 52 | # 実行 53 | #@@range_begin(list3) # ←この行は無視してください。本文に引用するためのものです。 54 | janken = JankenSimulator() 55 | janken.simulate() 56 | #@@range_end(list3) # ←この行は無視してください。本文に引用するためのものです。 57 | -------------------------------------------------------------------------------- /ch02/09janken/janken4.py: -------------------------------------------------------------------------------- 1 | # 属性にアクセスするのにselfを用いる 2 | import random 3 | OPTIONS = ['グー', 'チョキ', 'パー'] 4 | 5 | class JankenSimulator: 6 | def __init__(self): 7 | self.computer_choice = None 8 | self.human_choice = None 9 | 10 | def get_computer_choice(self): # <1> 11 | self.computer_choice = random.choice(OPTIONS) 12 | 13 | def get_human_choice(self): 14 | choice_number = int(input('「グー」か「チョキ」か「パー」を番号で選んでください: ')) 15 | self.human_choice = OPTIONS[choice_number - 1] 16 | 17 | def print_options(self): 18 | print('\n'.join(f'({i}) {option.title()}' for i, option in enumerate(OPTIONS, 1))) 19 | 20 | def print_choices(self): # <2> 21 | print(f'あなたが選んだのは「{self.human_choice}」です。') # <3> 22 | print(f'コンピュータが選んだのは「{self.computer_choice}」です。') 23 | 24 | def print_win_lose(self, human_beats, human_loses_to): 25 | if self.computer_choice == human_loses_to: 26 | print(f'残念でした。{self.computer_choice}の勝ちです。') 27 | elif self.computer_choice == human_beats: 28 | print(f'おめでとうございます! {self.human_choice}の勝ちです。') 29 | 30 | def print_result(self): 31 | if self.human_choice == self.computer_choice: 32 | print('引き分けです。') 33 | 34 | if self.human_choice == 'グー': 35 | self.print_win_lose('チョキ', 'パー') 36 | elif self.human_choice == 'パー': 37 | self.print_win_lose('グー', 'チョキ') 38 | elif self.human_choice == 'チョキ': 39 | self.print_win_lose('パー', 'グー') 40 | 41 | def simulate(self): 42 | self.print_options() 43 | self.get_human_choice() 44 | self.get_computer_choice() 45 | self.print_choices() 46 | self.print_result() 47 | 48 | #@@range_begin(list3) # ←この行は無視してください。書籍の本文に引用するためのものです。 49 | janken = JankenSimulator() 50 | janken.simulate() 51 | #@@range_end(list3) # ←この行は無視してください。書籍の本文に引用するためのものです。 52 | -------------------------------------------------------------------------------- /ch03/01reviews1/reviews.py: -------------------------------------------------------------------------------- 1 | # 段落を文とトークンに分割。単純なコードの列 2 | import re # https://docs.python.org/ja/3/howto/regex.html参照 3 | 4 | product_review = '''This is a fine milk, but the product line appears 5 | to be limited in available colors. I could only find white.''' # <1> 6 | 7 | sentence_pattern = re.compile(r'(.*?\.)(\s|$)', re.DOTALL) # <2> 8 | matches = sentence_pattern.findall(product_review) # <3> 9 | sentences = [match[0] for match in matches] # <4> 10 | 11 | word_pattern = re.compile(r"([\w\-']+)([\s,.])?") # <5> 12 | for sentence in sentences: 13 | matches = word_pattern.findall(sentence) 14 | words = [match[0] for match in matches] # <6> 15 | print(words) 16 | -------------------------------------------------------------------------------- /ch03/02reviews2/reviews.py: -------------------------------------------------------------------------------- 1 | # 段落を文とトークンに分割。改良版 2 | import re 3 | 4 | def get_matches_for_pattern(pattern, string): # <1> 5 | matches = pattern.findall(string) 6 | return [match[0] for match in matches] 7 | 8 | product_review = '''This is a fine milk, but the product line appears 9 | to be limited in available colors. I could only find white.''' 10 | 11 | sentence_pattern = re.compile(r'(.*?\.)(\s|$)', re.DOTALL) 12 | sentences = get_matches_for_pattern(sentence_pattern, product_review) #<2> 13 | 14 | word_pattern = re.compile(r"([\w\-']+)([\s,.])?") 15 | for sentence in sentences: 16 | words = get_matches_for_pattern( word_pattern, sentence ) # <3> 17 | print(words) 18 | -------------------------------------------------------------------------------- /ch03/03greeter1/greeter.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | from datetime import datetime 3 | import locale # 日時等日本語化のため 4 | locale.setlocale(locale.LC_TIME, 'ja_JP.UTF-8') # 日本語に設定 5 | 6 | class Greeter: 7 | def __init__(self, name): 8 | self.name = name 9 | 10 | def _day(self): # <1> 11 | return datetime.now().strftime('%A') 12 | 13 | def _part_of_day(self): # <2> 14 | current_hour = datetime.now().hour 15 | 16 | if current_hour < 12: 17 | part_of_day = '午前' 18 | elif 12 <= current_hour < 17: # 「elif current_hour < 17:」でも同じ 19 | part_of_day = '午後' 20 | else: 21 | part_of_day = '夜' 22 | 23 | return part_of_day 24 | 25 | def greet(self, store): # <3> 26 | print(f'{store}へようこそ! 私、{self.name}と申します。') 27 | print(f'{self._day()}の{self._part_of_day()}、いかがお過ごしですか?') 28 | print('本日のお客様に20% OFFのクーポンを差し上げます!') 29 | #@@range_end(list1) 30 | 31 | if __name__ == '__main__': # ほかのプログラムにimportされた場合は実行しない 32 | greeter = Greeter("竈門"); 33 | greeter.greet("Pythonプロショップ") 34 | -------------------------------------------------------------------------------- /ch03/04greeter2/greeter.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 2 | # 日時関連の関数をクラスの外に出す 3 | from datetime import datetime 4 | import locale 5 | locale.setlocale(locale.LC_TIME, 'ja_JP.UTF-8') 6 | 7 | def day(): 8 | return datetime.now().strftime('%A') 9 | 10 | def part_of_day(): 11 | current_hour = datetime.now().hour 12 | if current_hour < 12: 13 | part_of_day = '午前' 14 | elif 12 <= current_hour < 17: 15 | part_of_day = '午後' 16 | else: 17 | part_of_day = '夜' 18 | return part_of_day 19 | #@@range_end(list1) # ←この行は無視してください。書籍の本文に引用するためのものです。 20 | 21 | #@@range_begin(list2) # ←この行は無視してください。書籍の本文に引用するためのものです。 22 | class Greeter: 23 | def __init__(self, name): 24 | self.name = name 25 | def greet(self, store): 26 | print(f'{store}へようこそ! 私、{self.name}と申します。') 27 | print(f'{day()}の{part_of_day()}、いかがお過ごしですか?') 28 | print('本日のお客様に20% OFFのクーポンを差し上げます!') 29 | #@@range_end(list2) # ←この行は無視してください。書籍の本文に引用するためのものです。 30 | 31 | if __name__ == '__main__': # ほかのプログラムにimportされた場合は実行しない 32 | greeter = Greeter("竈門"); 33 | greeter.greet("Pythonショップショップ") 34 | -------------------------------------------------------------------------------- /ch03/05procedural/procedural.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | NAMES = ['小山', '大山', '中山'] 3 | 4 | def print_greetings(): # <1> 5 | greeting_pattern = '{name}さんに、ごあいさつ。' 6 | nice_person_pattern = '{name}さんはとても良い人です!' 7 | for name in NAMES: 8 | print(greeting_pattern.format(name=name)) 9 | print(nice_person_pattern.format(name=name)) 10 | #@@range_end(list1) 11 | 12 | if __name__ == '__main__': # ほかのプログラムにimportされた場合は実行しない 13 | print_greetings() 14 | -------------------------------------------------------------------------------- /ch03/06functional/for-loop-functional.js: -------------------------------------------------------------------------------- 1 | // node.js の例 2 | // node.jsのインストールが必要 3 | // コマンドラインで「node for-loop-functional.js」を実行 4 | 5 | print([1, 2, 3, 4, 5].map((i) =>i*i)) 6 | 7 | function print(x) { 8 | console.log(x) 9 | } 10 | -------------------------------------------------------------------------------- /ch03/06functional/for-loop-python.py: -------------------------------------------------------------------------------- 1 | numbers = [1, 2, 3, 4, 5] 2 | for i in numbers: 3 | print(i * i) 4 | -------------------------------------------------------------------------------- /ch03/06functional/functional-python.py: -------------------------------------------------------------------------------- 1 | # ch03/06functional/functional-python.py 2 | squares = map(lambda x: x * x, [1, 2, 3, 4, 5]) # 2乗 3 | for i in squares: 4 | print(i, end=" ") # 1 4 9 16 25 5 | print() # 改行 6 | 7 | from functools import reduce 8 | should = reduce(lambda x, y: x and y, [True, True, False]) 9 | print(should) # False 10 | should = reduce(lambda x, y: x and y, [True, True, True]) 11 | print(should) # True 12 | 13 | evens = filter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5]) 14 | for i in evens: 15 | print(i, end=" ") # 2 4 16 | print() # 改行 17 | -------------------------------------------------------------------------------- /ch03/06functional/imperative-python.py: -------------------------------------------------------------------------------- 1 | # ch03/06functional/imperative-python.py 2 | squares = [x * x for x in [1, 2, 3, 4, 5]] 3 | for i in squares: 4 | print(i, end=" ") # 1 4 9 16 25 5 | print() # 改行 6 | 7 | should = all([True, True, False]) 8 | print(should) # False 9 | should = all([True, True, True]) 10 | print(should) # True 11 | 12 | evens = [x for x in [1, 2, 3, 4, 5] if x % 2 == 0] 13 | for i in evens: 14 | print(i, end=" ") # 2 4 15 | print() # 改行 16 | -------------------------------------------------------------------------------- /ch03/06functional/partial.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | from functools import partial 3 | 4 | def pow(x, power=1): 5 | return x ** power 6 | 7 | square = partial(pow, power=2) # <1> 8 | cube = partial(pow, power=3) # <2> 9 | #@@range_end(list1) 10 | 11 | print(square(3)) 12 | print(cube(3)) 13 | print(square(8)) 14 | print(cube(8)) 15 | -------------------------------------------------------------------------------- /ch03/07plotly/plotly-example.py: -------------------------------------------------------------------------------- 1 | # pip install plotly==5.3.1 2 | # plotly.pyという名前にすると、名前の「衝突」が起きてしまうので、ファイル名を変えてください 3 | # グラフを表示する例は plotly-example2.py にあります 4 | #@@range_begin(list1) # ←この行は無視してください 5 | import plotly.graph_objects as go 6 | 7 | trace1 = go.Scatter( 8 | x=[1, 2, 3], 9 | y=[4, 5, 6], 10 | marker={'color': 'red', 'symbol': 104}, 11 | mode='markers+lines', 12 | text=['one', 'two', 'three'], 13 | name='1st Trace', 14 | ) 15 | #@@range_end(list1) # ←この行は無視してください 16 | -------------------------------------------------------------------------------- /ch03/07plotly/plotly-example2.py: -------------------------------------------------------------------------------- 1 | # https://plotly.com/python/getting-started/ 参照 2 | # $ pip install plotly==5.3.1 3 | # $ pip install dash 4 | # $ python3 plotly-example2.py 5 | #@@range_begin(list1) # ←この行は無視してください 6 | import plotly.graph_objects as go 7 | 8 | fig = go.Figure(data=go.Bar(y=[2, 3, 1])) 9 | fig.write_html('first_figure.html', auto_open=True) 10 | 11 | 12 | # trace1 = go.Scatter( 13 | # x=[1, 2, 3], 14 | # y=[4, 5, 6], 15 | # marker={'color': 'red', 'symbol': 104}, 16 | # mode='markers+lines', 17 | # text=['one', 'two', 'three'], 18 | # name='1st Trace', 19 | # ) 20 | 21 | #@@range_end(list1) # ←この行は無視してください 22 | -------------------------------------------------------------------------------- /ch03/08composition/composition.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | # ミックスイン 3 | class SpeakMixin: # <1> 4 | def speak(self): 5 | name = self.__class__.__name__.lower() 6 | print(f'一匹の{name}が「こんにちは!」と言った。') 7 | 8 | class RollOverMixin: # <2> 9 | def roll_over(self): 10 | print('横回転をした!') 11 | 12 | class Dog(SpeakMixin, RollOverMixin): # <3> 13 | pass # 何もしない 14 | #@@range_end(list1) 15 | 16 | # Dogクラスを利用 17 | #@@range_begin(list2) 18 | dog = Dog() 19 | dog.speak() 20 | dog.roll_over() 21 | #@@range_end(list2) 22 | -------------------------------------------------------------------------------- /ch04/01forloop/forloop.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください 2 | # 1重のループ、1ステップ 3 | names = ['小山', '中山', '大山', '高山'] 4 | for name in names: 5 | print(name) 6 | #@@range_end(list1) # ←この行は無視してください 7 | 8 | 9 | #@@range_begin(list2) # ←この行は無視してください 10 | # 1重のループ、複数ステップ 11 | names = ['小山', '中山', '大山', '高山'] 12 | for index, name in enumerate(names): 13 | greeting = 'こんにちは。私の名前は' 14 | print(f'{index+1}. {greeting}{name}です。') 15 | 16 | # 結果 17 | # 1. こんにちは。私の名前は小山です。 18 | # 2. こんにちは。私の名前は中山です。 19 | # ... 20 | #@@range_end(list2) # ←この行は無視してください 21 | 22 | #@@range_begin(list3) # ←この行は無視してください 23 | names = ['小山', '中山', '大山', '高山'] 24 | for name in names: 25 | print(f'私は{name}です。') 26 | 27 | message = '本日のゲスト: ' 28 | for name in names: 29 | message += f'{name}さん ' 30 | print(message) 31 | #@@range_end(list3) # ←この行は無視してください 32 | 33 | #@@range_begin(list4) # ←この行は無視してください 34 | # 入れ子になったforループ 35 | def has_duplicates(sequence): 36 | for index1, item1 in enumerate(sequence): # <1> 37 | for index2, item2 in enumerate(sequence): # <2> 38 | if item1 == item2 and index1 != index2: # <3> 39 | return True 40 | return False 41 | #@@range_end(list4) # ←この行は無視してください 42 | sequence = [0, 5, 10, 7, 19, 4] 43 | print(has_duplicates(sequence)) 44 | sequence = [0, 5, 4, 10, 7, 19, 4] 45 | print(has_duplicates(sequence)) 46 | 47 | -------------------------------------------------------------------------------- /ch04/01forloop/forloop1.py: -------------------------------------------------------------------------------- 1 | # (1重の)ループ 2 | names = ['小山', '中山', '大山', '高山'] 3 | for name in names: 4 | print(name) 5 | -------------------------------------------------------------------------------- /ch04/01forloop/forloop2.py: -------------------------------------------------------------------------------- 1 | # 1重のループ、複数ステップ 2 | names = ['小山', '中山', '大山', '高山'] 3 | for index, name in enumerate(names): 4 | greeting = 'こんにちは。私の名前は' 5 | print(f'{index+1}. {greeting}{name}です。') 6 | -------------------------------------------------------------------------------- /ch04/01forloop/forloop3.py: -------------------------------------------------------------------------------- 1 | # 複数個の1重のループ 2 | names = ['小山', '中山', '大山', '高山'] 3 | for name in names: 4 | print(f'私は{name}です。') 5 | 6 | message = '本日のゲスト: ' 7 | for name in names: 8 | message += f'{name}さん ' 9 | print(message) 10 | -------------------------------------------------------------------------------- /ch04/01forloop/forloop4.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | # 入れ子になったforループ 3 | def has_duplicates(sequence): 4 | for index1, item1 in enumerate(sequence): # <1> 5 | for index2, item2 in enumerate(sequence): # <2> 6 | if item1 == item2 and index1 != index2: # <3> 7 | return True 8 | return False 9 | #@@range_end(list1) 10 | sequence = [0, 5, 10, 7, 19, 4] 11 | print(has_duplicates(sequence)) 12 | sequence = [0, 5, 4, 10, 7, 19, 4] 13 | print(has_duplicates(sequence)) 14 | 15 | -------------------------------------------------------------------------------- /ch04/02colors/all-favorite-colors.txt: -------------------------------------------------------------------------------- 1 | 黄 2 | 紫 3 | 赤 4 | 青 5 | 黄 6 | 青 7 | -------------------------------------------------------------------------------- /ch04/02colors/color-at-once.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | # メモリにすべて読み込んでから処理 3 | color_counts = {} 4 | with open('all-favorite-colors.txt') as favorite_colors_file: 5 | favorite_colors = favorite_colors_file.read().splitlines() # <1> 6 | 7 | for color in favorite_colors: 8 | if color in color_counts: 9 | color_counts[color] += 1 10 | else: 11 | color_counts[color] = 1 12 | #@@range_end(list1) 13 | print(color_counts) 14 | -------------------------------------------------------------------------------- /ch04/02colors/color-one-at-a-time.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | # 1行ずつ読み込み 3 | color_counts = {} 4 | with open('all-favorite-colors.txt') as favorite_colors_file: 5 | for color in favorite_colors_file: # <1> 6 | color = color.strip() # <2> 7 | 8 | if color in color_counts: 9 | color_counts[color] += 1 10 | else: 11 | color_counts[color] = 1 12 | #@@range_end(list1) 13 | print(color_counts); 14 | -------------------------------------------------------------------------------- /ch04/02colors/color-set.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください 2 | # 集合(set)を使って色を記録 3 | all_colors = set() 4 | 5 | with open('all-favorite-colors.txt') as favorite_colors_file: 6 | for line in favorite_colors_file: # <1> 7 | all_colors.add(line.strip()) # <2> 8 | 9 | print('琥珀' in all_colors) # <3> 10 | #@@range_end(list1) # ←この行は無視してください 11 | print('青' in all_colors) 12 | 13 | -------------------------------------------------------------------------------- /ch04/03generator/generators.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください 2 | # rangeの処理を真似たもの 3 | def range(*args): 4 | if len(args) == 1: # <1> 5 | start = 0 6 | stop = args[0] 7 | else: 8 | start = args[0] 9 | stop = args[1] 10 | 11 | current = start 12 | 13 | while current < stop: 14 | yield current # <2> 15 | current += 1 # <3> 16 | #@@range_end(list1) # ←この行は無視してください 17 | 18 | 19 | # Creating a generator for calculating squares of numbers in a list 20 | #@@range_begin(list2) # ←この行は無視してください 21 | def squares(items): 22 | for item in items: 23 | yield item ** 2 24 | #@@range_end(list2) # ←この行は無視してください 25 | 26 | print(list(squares(range(10)))) 27 | print(list(squares(range(11,21)))) 28 | -------------------------------------------------------------------------------- /ch04/04counting/counting1.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | # 第1版 3 | def get_number_with_highest_count(counts): # <1> 4 | max_count = 0 5 | for number, count in counts.items(): 6 | if count > max_count: 7 | max_count = count 8 | number_with_highest_count = number 9 | return number_with_highest_count 10 | 11 | def most_frequent(numbers): 12 | counts = {} 13 | for number in numbers: # <2> 14 | if number in counts: 15 | counts[number] += 1 16 | else: 17 | counts[number] = 1 18 | 19 | return get_number_with_highest_count(counts) 20 | #@@range_end(list1) 21 | 22 | print (most_frequent([0, 3, 5, 10, 20, 5, 3, 4, 20, 4, 5, 3, 12, 4])) 23 | -------------------------------------------------------------------------------- /ch04/04counting/counting2.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | # 第2版 defaultdictの利用 3 | from collections import defaultdict # <1> 4 | 5 | def get_number_with_highest_count(counts): 6 | max_count = 0 7 | for number, count in counts.items(): 8 | if count > max_count: 9 | max_count = count 10 | number_with_highest_count = number 11 | return number_with_highest_count 12 | 13 | def most_frequent(numbers): 14 | counts = defaultdict(int) # <2> 15 | for number in numbers: 16 | counts[number] += 1 # <3> 17 | 18 | return get_number_with_highest_count(counts) 19 | #@@range_end(list1) 20 | 21 | print (most_frequent([0, 3, 5, 10, 20, 5, 3, 4, 20, 4, 5, 3, 12, 4])) 22 | 23 | -------------------------------------------------------------------------------- /ch04/04counting/counting3.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | # 第3版 Counterの利用 3 | from collections import Counter # <1> 4 | 5 | def get_number_with_highest_count(counts): 6 | max_count = 0 7 | for number, count in counts.items(): 8 | if count > max_count: 9 | max_count = count 10 | number_with_highest_count = number 11 | return number_with_highest_count 12 | 13 | def most_frequent(numbers): 14 | counts = Counter(numbers) # <2> 15 | return get_number_with_highest_count(counts) 16 | 17 | #@@range_end(list1) 18 | 19 | print (most_frequent([0, 3, 5, 10, 20, 5, 3, 4, 20, 4, 5, 3, 12, 4])) 20 | -------------------------------------------------------------------------------- /ch04/04counting/counting4.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | # 第4版 ラムダ関数の利用 3 | from collections import Counter 4 | 5 | def get_number_with_highest_count(counts): # countsの要素のうち、頻度最高のものを得る 6 | return max( # <1> 7 | counts, 8 | key=lambda number: counts[number] 9 | # 第2引数keyの値として「numberを引数として受け取りcount[number]を返す関数」を指定 10 | ) 11 | 12 | def most_frequent(numbers): 13 | counts = Counter(numbers) 14 | return get_number_with_highest_count(counts) 15 | #@@range_end(list1) 16 | 17 | print (most_frequent([0, 3, 5, 10, 20, 5, 3, 4, 20, 4, 5, 3, 12, 4])) 18 | -------------------------------------------------------------------------------- /ch04/05timing/timing1.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | from timeit import timeit 3 | 4 | setup = 'from datetime import datetime' # <1> 5 | statement = 'datetime.now()' # <2> 6 | result = timeit(setup=setup, stmt=statement, number=1000) # <3> 7 | print(f'実行時間の平均: {result / 1000}s == {result}ms') 8 | #@@range_end(list1) 9 | -------------------------------------------------------------------------------- /ch04/05timing/timing2.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | def sort_expensive(): 4 | the_list = random.sample(range(1_000_000), 1000) # 「_」は桁区切り 5 | the_list.sort() 6 | 7 | def sort_cheap(): 8 | the_list = random.sample(range(1_000), 10) 9 | the_list.sort() 10 | 11 | if __name__ == '__main__': 12 | sort_expensive() 13 | for i in range(1000): 14 | sort_cheap() 15 | -------------------------------------------------------------------------------- /ch04/06cpu_profiling/cpu_profiling.py: -------------------------------------------------------------------------------- 1 | import random 2 | import time 3 | 4 | def an_expensive_function(): 5 | execution_time = random.random() / 100 # <1> 6 | time.sleep(execution_time) 7 | 8 | if __name__ == '__main__': 9 | for _ in range(1000): # <2> 10 | an_expensive_function() 11 | -------------------------------------------------------------------------------- /ch05/01mean/calculate_mean.py: -------------------------------------------------------------------------------- 1 | def calculate_mean(numbers): 2 | if not numbers: 3 | return 0 4 | return sum(numbers) / len(numbers) 5 | 6 | assert 10.0 == calculate_mean([0, 10, 20]) 7 | assert 1.0 == calculate_mean([1000, 3500, 7_000_000]) 8 | -------------------------------------------------------------------------------- /ch05/02unittest1/product.py: -------------------------------------------------------------------------------- 1 | class Product: 2 | def __init__(self, name, size, color): # <1> 3 | self.name = name 4 | self.size = size 5 | self.color = color 6 | 7 | def transform_name_for_sku(self): # 名前→SKUの名前部分 8 | return self.name.upper() 9 | 10 | def transform_color_for_sku(self): # 色→SKUの色部分 11 | return self.color.upper() 12 | 13 | def generate_sku(self): # <2> 14 | """ 15 | この製品のSKU(stock keeping unit: 最小管理単位)を生成する 16 | 17 | 例: 18 | >>> small_black_shoes = Product('shoes', 'S', 'black') 19 | >>> small_black_shoes.generate_sku() 20 | 'SHOES-S-BLACK' 21 | """ 22 | name = self.transform_name_for_sku() 23 | color = self.transform_color_for_sku() 24 | return f'{name}-{self.size}-{color}' # 名前、サイズ、色を連結 25 | -------------------------------------------------------------------------------- /ch05/02unittest1/test_product.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | class ProductTestCase(unittest.TestCase): 4 | pass 5 | -------------------------------------------------------------------------------- /ch05/03unittest2/product.py: -------------------------------------------------------------------------------- 1 | # ch05/03unittest2/product.py 2 | class Product: 3 | def __init__(self, name, size, color): # <1> 4 | self.name = name 5 | self.size = size 6 | self.color = color 7 | 8 | def transform_name_for_sku(self): 9 | return self.name.upper() 10 | 11 | def transform_color_for_sku(self): 12 | return self.color.upper() 13 | 14 | def generate_sku(self): # <2> 15 | """ 16 | この製品のSKU(stock keeping unit: 最小管理単位)を生成する 17 | 18 | 例: 19 | >>> small_black_shoes = Product('shoes', 'S', 'black') 20 | >>> small_black_shoes.generate_sku() 21 | 'SHOES-S-BLACK' 22 | """ 23 | name = self.transform_name_for_sku() 24 | color = self.transform_color_for_sku() 25 | return f'{name}-{self.size}-{color}' 26 | -------------------------------------------------------------------------------- /ch05/03unittest2/test_product.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | class ProductTestCase(unittest.TestCase): 4 | def test_working(self): 5 | pass 6 | -------------------------------------------------------------------------------- /ch05/04unittest3/product.py: -------------------------------------------------------------------------------- 1 | # ch05/04product3/product.py 2 | class Product: 3 | def __init__(self, name, size, color): # <1> 4 | self.name = name 5 | self.size = size 6 | self.color = color 7 | 8 | def transform_name_for_sku(self): 9 | return self.name.upper() 10 | 11 | def transform_color_for_sku(self): 12 | return self.color.upper() 13 | 14 | def generate_sku(self): # <2> 15 | """ 16 | この製品のSKU(stock keeping unit: 最小管理単位)を生成する 17 | 18 | 例: 19 | >>> small_black_shoes = Product('shoes', 'S', 'black') 20 | >>> small_black_shoes.generate_sku() 21 | 'SHOES-S-BLACK' 22 | """ 23 | name = self.transform_name_for_sku() 24 | color = self.transform_color_for_sku() 25 | return f'{name}-{self.size}-{color}' 26 | -------------------------------------------------------------------------------- /ch05/04unittest3/test_product.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from product import Product 4 | 5 | class ProductTestCase(unittest.TestCase): 6 | def test_transform_name_for_sku(self): 7 | small_black_shoes = Product('shoes', 'S', 'black') 8 | expected_value = 'SHOES' # 期待される値 9 | actual_value = small_black_shoes.transform_name_for_sku() # 実際の値 10 | self.assertEqual(expected_value, actual_value) 11 | -------------------------------------------------------------------------------- /ch05/05unittest4/product.py: -------------------------------------------------------------------------------- 1 | # ch05/04product3/product.py 2 | class Product: 3 | def __init__(self, name, size, color): # <1> 4 | self.name = name 5 | self.size = size 6 | self.color = color 7 | 8 | def transform_name_for_sku(self): 9 | return self.name.upper() 10 | 11 | def transform_color_for_sku(self): 12 | return self.color.upper() 13 | 14 | def generate_sku(self): # <2> 15 | """ 16 | この製品のSKU(stock keeping unit: 最小管理単位)を生成する 17 | 18 | 例: 19 | >>> small_black_shoes = Product('shoes', 'S', 'black') 20 | >>> small_black_shoes.generate_sku() 21 | 'SHOES-S-BLACK' 22 | """ 23 | name = self.transform_name_for_sku() 24 | color = self.transform_color_for_sku() 25 | return f'{name}-{self.size}-{color}' 26 | -------------------------------------------------------------------------------- /ch05/05unittest4/test_product.py: -------------------------------------------------------------------------------- 1 | # ch05/04unittest3/test_product.py 2 | import unittest 3 | 4 | from product import Product 5 | 6 | class ProductTestCase(unittest.TestCase): 7 | def test_transform_name_for_sku(self): 8 | small_black_shoes = Product('shoes', 'S', 'black') 9 | expected_value = 'SHOEZ' 10 | actual_value = small_black_shoes.transform_name_for_sku() 11 | self.assertEqual(expected_value, actual_value) 12 | -------------------------------------------------------------------------------- /ch05/06unittest5/cart.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | from collections import defaultdict 3 | 4 | class ShoppingCart: 5 | def __init__(self): 6 | self.products = defaultdict(lambda: defaultdict(int)) # <1> 7 | 8 | def add_product(self, product, quantity=1): # <2> 9 | self.products[product.generate_sku()]['quantity'] += quantity 10 | 11 | def remove_product(self, product, quantity=1): # <3> 12 | sku = product.generate_sku() 13 | self.products[sku]['quantity'] -= quantity 14 | if self.products[sku]['quantity'] == 0: 15 | del self.products[sku] 16 | #@@range_end(list1) 17 | """ 18 | To fix the bug: 19 | if self.products[sku]['quantity'] <= 0: 20 | del self.products[sku] 21 | """ 22 | -------------------------------------------------------------------------------- /ch05/06unittest5/product.py: -------------------------------------------------------------------------------- 1 | # ch05/06unittest5/product.py 2 | class Product: 3 | def __init__(self, name, size, color): # <1> 4 | self.name = name 5 | self.size = size 6 | self.color = color 7 | 8 | def transform_name_for_sku(self): 9 | return self.name.upper() 10 | 11 | def transform_color_for_sku(self): 12 | return self.color.upper() 13 | 14 | def generate_sku(self): # <2> 15 | """ 16 | この製品のSKU(stock keeping unit: 最小管理単位)を生成する 17 | 18 | 例: 19 | >>> small_black_shoes = Product('shoes', 'S', 'black') 20 | >>> small_black_shoes.generate_sku() 21 | 'SHOES-S-BLACK' 22 | """ 23 | name = self.transform_name_for_sku() 24 | color = self.transform_color_for_sku() 25 | return f'{name}-{self.size}-{color}' 26 | -------------------------------------------------------------------------------- /ch05/06unittest5/test_cart.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from cart import ShoppingCart 4 | from product import Product 5 | 6 | class ShoppingCartTestCase(unittest.TestCase): # <1> 7 | def test_add_and_remove_product(self): 8 | cart = ShoppingCart() # <2> 9 | product = Product('shoes', 'S', 'blue') # <3> 10 | 11 | cart.add_product(product) # <4> 12 | cart.remove_product(product) # <5> 13 | 14 | self.assertDictEqual({}, cart.products) # <6> 15 | -------------------------------------------------------------------------------- /ch05/06unittest5/test_product.py: -------------------------------------------------------------------------------- 1 | # ch05/06unittest5/test_puroduct.py 2 | import unittest 3 | 4 | from product import Product 5 | 6 | 7 | class ProductTestCase(unittest.TestCase): 8 | def test_working(self): 9 | pass 10 | 11 | def test_transform_name_for_sku(self): 12 | small_black_shoes = Product('shoes', 'S', 'black') # <1> 13 | expected_value = 'SHOES' # <2> 14 | actual_value = small_black_shoes.transform_name_for_sku() # <3> 15 | self.assertEqual(expected_value, actual_value) # <4> 16 | -------------------------------------------------------------------------------- /ch05/07testdouble/cart.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください 2 | # ch05/06unittest5/cart.py 3 | from collections import defaultdict 4 | 5 | class ShoppingCart: 6 | def __init__(self): 7 | self.products = defaultdict(lambda: defaultdict(int)) # <1> 8 | 9 | def add_product(self, product, quantity=1): # <2> 10 | self.products[product.generate_sku()]['quantity'] += quantity 11 | 12 | def remove_product(self, product, quantity=1): # <3> 13 | sku = product.generate_sku() 14 | self.products[sku]['quantity'] -= quantity 15 | if self.products[sku]['quantity'] == 0: 16 | del self.products[sku] 17 | #@@range_end(list1) # ←この行は無視してください 18 | """ 19 | To fix the bug: 20 | if self.products[sku]['quantity'] <= 0: 21 | del self.products[sku] 22 | """ 23 | -------------------------------------------------------------------------------- /ch05/07testdouble/product.py: -------------------------------------------------------------------------------- 1 | # ch05/07testdouble/product.py 2 | class Product: 3 | def __init__(self, name, size, color): # <1> 4 | self.name = name 5 | self.size = size 6 | self.color = color 7 | 8 | def transform_name_for_sku(self): 9 | return self.name.upper() 10 | 11 | def transform_color_for_sku(self): 12 | return self.color.upper() 13 | 14 | def generate_sku(self): # <2> 15 | """ 16 | この製品のSKU(stock keeping unit: 最小管理単位)を生成する 17 | 18 | 例: 19 | >>> small_black_shoes = Product('shoes', 'S', 'black') 20 | >>> small_black_shoes.generate_sku() 21 | 'SHOES-S-BLACK' 22 | """ 23 | name = self.transform_name_for_sku() 24 | color = self.transform_color_for_sku() 25 | return f'{name}-{self.size}-{color}' 26 | -------------------------------------------------------------------------------- /ch05/07testdouble/tax.py: -------------------------------------------------------------------------------- 1 | from urllib.request import urlopen 2 | 3 | def add_sales_tax(original_amount, country, region): 4 | sales_tax_rate = urlopen(f'https://tax-api.com/{country}/{region}').read().decode() 5 | return original_amount * float(sales_tax_rate) 6 | -------------------------------------------------------------------------------- /ch05/07testdouble/test_cart.py: -------------------------------------------------------------------------------- 1 | # ch05/07testdouble/test_cart.py 2 | import unittest 3 | 4 | from cart import ShoppingCart 5 | from product import Product 6 | 7 | class ShoppingCartTestCase(unittest.TestCase): # <1> 8 | def test_add_and_remove_product(self): 9 | cart = ShoppingCart() # <2> 10 | product = Product('shoes', 'S', 'blue') # <3> 11 | 12 | cart.add_product(product) # <4> 13 | cart.remove_product(product) # <5> 14 | 15 | self.assertDictEqual({}, cart.products) # <6> 16 | -------------------------------------------------------------------------------- /ch05/07testdouble/test_product.py: -------------------------------------------------------------------------------- 1 | # ch05/06unittest5/test_puroduct.py 2 | import unittest 3 | 4 | from product import Product 5 | 6 | 7 | class ProductTestCase(unittest.TestCase): 8 | def test_working(self): 9 | pass 10 | 11 | def test_transform_name_for_sku(self): 12 | small_black_shoes = Product('shoes', 'S', 'black') # <1> 13 | expected_value = 'SHOES' # <2> 14 | actual_value = small_black_shoes.transform_name_for_sku() # <3> 15 | self.assertEqual(expected_value, actual_value) # <4> 16 | -------------------------------------------------------------------------------- /ch05/07testdouble/test_tax.py: -------------------------------------------------------------------------------- 1 | import io 2 | import unittest 3 | from unittest import mock 4 | from tax import add_sales_tax 5 | 6 | class SalesTaxTestCase(unittest.TestCase): 7 | @mock.patch('tax.urlopen') # <1> 8 | def test_get_sales_tax_returns_proper_value_from_api( 9 | self, 10 | mock_urlopen # <2> 11 | ): 12 | test_tax_rate = 1.06 13 | mock_urlopen.return_value = io.BytesIO( # <3> 14 | str(test_tax_rate).encode('utf-8') 15 | ) 16 | 17 | self.assertEqual( 18 | 5 * test_tax_rate, 19 | add_sales_tax(5, 'USA', 'MI') 20 | ) # <4> 21 | -------------------------------------------------------------------------------- /ch05/08tryitout1/cart.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) # ←この行は無視してください 2 | # ch05/08tryitout1/cart.py 3 | from collections import defaultdict 4 | 5 | class ShoppingCart: 6 | def __init__(self): 7 | self.products = defaultdict(lambda: defaultdict(int)) # <1> 8 | 9 | def add_product(self, product, quantity=1): # <2> 10 | self.products[product.generate_sku()]['quantity'] += quantity 11 | 12 | def remove_product(self, product, quantity=1): # <3> 13 | sku = product.generate_sku() 14 | self.products[sku]['quantity'] -= quantity 15 | if self.products[sku]['quantity'] == 0: 16 | del self.products[sku] 17 | #@@range_end(list1) # ←この行は無視してください 18 | -------------------------------------------------------------------------------- /ch05/08tryitout1/product.py: -------------------------------------------------------------------------------- 1 | # ch05/06unittest5/product.py 2 | class Product: 3 | def __init__(self, name, size, color): # <1> 4 | self.name = name 5 | self.size = size 6 | self.color = color 7 | 8 | def transform_name_for_sku(self): 9 | return self.name.upper() 10 | 11 | def transform_color_for_sku(self): 12 | return self.color.upper() 13 | 14 | def generate_sku(self): # <2> 15 | """ 16 | この製品のSKU(stock keeping unit: 最小管理単位)を生成する 17 | 18 | 例: 19 | >>> small_black_shoes = Product('shoes', 'S', 'black') 20 | >>> small_black_shoes.generate_sku() 21 | 'SHOES-S-BLACK' 22 | """ 23 | name = self.transform_name_for_sku() 24 | color = self.transform_color_for_sku() 25 | return f'{name}-{self.size}-{color}' 26 | -------------------------------------------------------------------------------- /ch05/08tryitout1/tax.py: -------------------------------------------------------------------------------- 1 | # ch05/07testdouble/tax.py 2 | from urllib.request import urlopen 3 | 4 | def add_sales_tax(original_amount, country, region): 5 | sales_tax_rate = urlopen(f'https://tax-api.com/{country}/{region}').read().decode() 6 | return original_amount * float(sales_tax_rate) 7 | -------------------------------------------------------------------------------- /ch05/08tryitout1/test_cart.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from cart import ShoppingCart 4 | from product import Product 5 | 6 | # Full test suite for product and shopping cart 7 | class ProductTestCase(unittest.TestCase): 8 | #@@range_begin(list1) 9 | def test_transform_name_for_sku(self): 10 | small_black_shoes = Product('shoes', 'S', 'black') 11 | self.assertEqual( 12 | 'SHOES', 13 | small_black_shoes.transform_name_for_sku(), 14 | ) 15 | #@@range_end(list1) 16 | def test_transform_color_for_sku(self): 17 | small_black_shoes = Product('shoes', 'S', 'black') 18 | self.assertEqual( 19 | 'BLACK', 20 | small_black_shoes.transform_color_for_sku(), 21 | ) 22 | 23 | def test_generate_sku(self): 24 | small_black_shoes = Product('shoes', 'S', 'black') 25 | self.assertEqual( 26 | 'SHOES-S-BLACK', 27 | small_black_shoes.generate_sku(), 28 | ) 29 | 30 | 31 | class ShoppingCartTestCase(unittest.TestCase): 32 | def test_cart_initially_empty(self): 33 | cart = ShoppingCart() 34 | self.assertDictEqual({}, cart.products) 35 | 36 | def test_add_product(self): 37 | cart = ShoppingCart() 38 | product = Product('shoes', 'S', 'blue') 39 | 40 | cart.add_product(product) 41 | 42 | self.assertDictEqual({'SHOES-S-BLUE': {'quantity': 1}}, cart.products) 43 | 44 | def test_add_two_of_a_product(self): # 同じものを2つ入れる 45 | cart = ShoppingCart() 46 | product = Product('shoes', 'S', 'blue') 47 | 48 | cart.add_product(product, quantity=2) 49 | 50 | self.assertDictEqual({'SHOES-S-BLUE': {'quantity': 2}}, cart.products) 51 | 52 | def test_add_two_different_products(self): # 2つの違うものを入れる 53 | cart = ShoppingCart() 54 | product_one = Product('shoes', 'S', 'blue') 55 | product_two = Product('shirt', 'M', 'gray') 56 | 57 | cart.add_product(product_one) 58 | cart.add_product(product_two) 59 | 60 | self.assertDictEqual( 61 | { 62 | 'SHOES-S-BLUE': {'quantity': 1}, 63 | 'SHIRT-M-GRAY': {'quantity': 1} 64 | }, 65 | cart.products 66 | ) 67 | 68 | def test_add_and_remove_product(self): # 入れてから削除 69 | cart = ShoppingCart() 70 | product = Product('shoes', 'S', 'blue') 71 | 72 | cart.add_product(product) 73 | cart.remove_product(product) 74 | 75 | self.assertDictEqual({}, cart.products) 76 | 77 | def test_remove_too_many_products(self): # 余分に削除 78 | cart = ShoppingCart() 79 | product = Product('shoes', 'S', 'blue') 80 | 81 | cart.add_product(product) 82 | cart.remove_product(product, quantity=2) # ←失敗する 83 | 84 | self.assertDictEqual({}, cart.products) 85 | -------------------------------------------------------------------------------- /ch05/08tryitout1/test_product.py: -------------------------------------------------------------------------------- 1 | # ch05/06unittest5/test_puroduct.py 2 | import unittest 3 | 4 | from product import Product 5 | 6 | 7 | class ProductTestCase(unittest.TestCase): 8 | def test_working(self): 9 | pass 10 | 11 | def test_transform_name_for_sku(self): 12 | small_black_shoes = Product('shoes', 'S', 'black') # <1> 13 | expected_value = 'SHOES' # <2> 14 | actual_value = small_black_shoes.transform_name_for_sku() # <3> 15 | self.assertEqual(expected_value, actual_value) # <4> 16 | -------------------------------------------------------------------------------- /ch05/08tryitout1/test_tax.py: -------------------------------------------------------------------------------- 1 | # ch05/07testdouble/test_tax.py 2 | import io 3 | import unittest 4 | from unittest import mock 5 | from tax import add_sales_tax 6 | 7 | class SalesTaxTestCase(unittest.TestCase): 8 | @mock.patch('tax.urlopen') # <1> 9 | def test_get_sales_tax_returns_proper_value_from_api( 10 | self, 11 | mock_urlopen # <2> 12 | ): 13 | test_tax_rate = 1.06 14 | mock_urlopen.return_value = io.BytesIO( # <3> 15 | str(test_tax_rate).encode('utf-8') 16 | ) 17 | 18 | self.assertEqual( 19 | 5 * test_tax_rate, 20 | add_sales_tax(5, 'USA', 'MI') 21 | ) # <4> 22 | -------------------------------------------------------------------------------- /ch05/09tryitout2/cart.py: -------------------------------------------------------------------------------- 1 | # ch05/09tryitout2/cart.py 2 | from collections import defaultdict 3 | 4 | class ShoppingCart: 5 | def __init__(self): 6 | self.products = defaultdict(lambda: defaultdict(int)) 7 | 8 | def add_product(self, product, quantity=1): 9 | self.products[product.generate_sku()]['quantity'] += quantity 10 | 11 | def remove_product(self, product, quantity=1): 12 | sku = product.generate_sku() 13 | self.products[sku]['quantity'] -= quantity 14 | #@@range_begin(list1) # ←この行は無視してください 15 | if self.products[sku]['quantity'] <= 0: 16 | del self.products[sku] 17 | #@@range_end(list1) # ←この行は無視してください 18 | -------------------------------------------------------------------------------- /ch05/09tryitout2/product.py: -------------------------------------------------------------------------------- 1 | # ch05/06unittest5/product.py 2 | class Product: 3 | def __init__(self, name, size, color): # <1> 4 | self.name = name 5 | self.size = size 6 | self.color = color 7 | 8 | def transform_name_for_sku(self): 9 | return self.name.upper() 10 | 11 | def transform_color_for_sku(self): 12 | return self.color.upper() 13 | 14 | def generate_sku(self): # <2> 15 | """ 16 | この製品のSKU(stock keeping unit: 最小管理単位)を生成する 17 | 18 | 例: 19 | >>> small_black_shoes = Product('shoes', 'S', 'black') 20 | >>> small_black_shoes.generate_sku() 21 | 'SHOES-S-BLACK' 22 | """ 23 | name = self.transform_name_for_sku() 24 | color = self.transform_color_for_sku() 25 | return f'{name}-{self.size}-{color}' 26 | -------------------------------------------------------------------------------- /ch05/09tryitout2/tax.py: -------------------------------------------------------------------------------- 1 | # ch05/07testdouble/tax.py 2 | from urllib.request import urlopen 3 | 4 | def add_sales_tax(original_amount, country, region): 5 | sales_tax_rate = urlopen(f'https://tax-api.com/{country}/{region}').read().decode() 6 | return original_amount * float(sales_tax_rate) 7 | -------------------------------------------------------------------------------- /ch05/09tryitout2/test_cart.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from cart import ShoppingCart 4 | from product import Product 5 | 6 | # Full test suite for product and shopping cart 7 | class ProductTestCase(unittest.TestCase): 8 | #@@range_begin(list1) 9 | def test_transform_name_for_sku(self): 10 | small_black_shoes = Product('shoes', 'S', 'black') 11 | self.assertEqual( 12 | 'SHOES', 13 | small_black_shoes.transform_name_for_sku(), 14 | ) 15 | #@@range_end(list1) 16 | def test_transform_color_for_sku(self): 17 | small_black_shoes = Product('shoes', 'S', 'black') 18 | self.assertEqual( 19 | 'BLACK', 20 | small_black_shoes.transform_color_for_sku(), 21 | ) 22 | 23 | def test_generate_sku(self): 24 | small_black_shoes = Product('shoes', 'S', 'black') 25 | self.assertEqual( 26 | 'SHOES-S-BLACK', 27 | small_black_shoes.generate_sku(), 28 | ) 29 | 30 | 31 | class ShoppingCartTestCase(unittest.TestCase): 32 | def test_cart_initially_empty(self): 33 | cart = ShoppingCart() 34 | self.assertDictEqual({}, cart.products) 35 | 36 | def test_add_product(self): 37 | cart = ShoppingCart() 38 | product = Product('shoes', 'S', 'blue') 39 | 40 | cart.add_product(product) 41 | 42 | self.assertDictEqual({'SHOES-S-BLUE': {'quantity': 1}}, cart.products) 43 | 44 | def test_add_two_of_a_product(self): # 同じものを2つ入れる 45 | cart = ShoppingCart() 46 | product = Product('shoes', 'S', 'blue') 47 | 48 | cart.add_product(product, quantity=2) 49 | 50 | self.assertDictEqual({'SHOES-S-BLUE': {'quantity': 2}}, cart.products) 51 | 52 | def test_add_two_different_products(self): # 2つの違うものを入れる 53 | cart = ShoppingCart() 54 | product_one = Product('shoes', 'S', 'blue') 55 | product_two = Product('shirt', 'M', 'gray') 56 | 57 | cart.add_product(product_one) 58 | cart.add_product(product_two) 59 | 60 | self.assertDictEqual( 61 | { 62 | 'SHOES-S-BLUE': {'quantity': 1}, 63 | 'SHIRT-M-GRAY': {'quantity': 1} 64 | }, 65 | cart.products 66 | ) 67 | 68 | def test_add_and_remove_product(self): # 入れてから削除 69 | cart = ShoppingCart() 70 | product = Product('shoes', 'S', 'blue') 71 | 72 | cart.add_product(product) 73 | cart.remove_product(product) 74 | 75 | self.assertDictEqual({}, cart.products) 76 | 77 | def test_remove_too_many_products(self): # 余分に削除 78 | cart = ShoppingCart() 79 | product = Product('shoes', 'S', 'blue') 80 | 81 | cart.add_product(product) 82 | cart.remove_product(product, quantity=2) # ←失敗する 83 | 84 | self.assertDictEqual({}, cart.products) 85 | -------------------------------------------------------------------------------- /ch05/09tryitout2/test_product.py: -------------------------------------------------------------------------------- 1 | # ch05/06unittest5/test_puroduct.py 2 | import unittest 3 | 4 | from product import Product 5 | 6 | 7 | class ProductTestCase(unittest.TestCase): 8 | def test_working(self): 9 | pass 10 | 11 | def test_transform_name_for_sku(self): 12 | small_black_shoes = Product('shoes', 'S', 'black') # <1> 13 | expected_value = 'SHOES' # <2> 14 | actual_value = small_black_shoes.transform_name_for_sku() # <3> 15 | self.assertEqual(expected_value, actual_value) # <4> 16 | -------------------------------------------------------------------------------- /ch05/09tryitout2/test_tax.py: -------------------------------------------------------------------------------- 1 | # ch05/07testdouble/test_tax.py 2 | import io 3 | import unittest 4 | from unittest import mock 5 | from tax import add_sales_tax 6 | 7 | class SalesTaxTestCase(unittest.TestCase): 8 | @mock.patch('tax.urlopen') # <1> 9 | def test_get_sales_tax_returns_proper_value_from_api( 10 | self, 11 | mock_urlopen # <2> 12 | ): 13 | test_tax_rate = 1.06 14 | mock_urlopen.return_value = io.BytesIO( # <3> 15 | str(test_tax_rate).encode('utf-8') 16 | ) 17 | 18 | self.assertEqual( 19 | 5 * test_tax_rate, 20 | add_sales_tax(5, 'USA', 'MI') 21 | ) # <4> 22 | -------------------------------------------------------------------------------- /ch05/10goodtest1/cart.py: -------------------------------------------------------------------------------- 1 | # ch05/09tryitout2/cart.py 2 | from collections import defaultdict 3 | 4 | class ShoppingCart: 5 | def __init__(self): 6 | self.products = defaultdict(lambda: defaultdict(int)) 7 | 8 | def add_product(self, product, quantity=1): 9 | self.products[product.generate_sku()]['quantity'] += quantity 10 | 11 | def remove_product(self, product, quantity=1): 12 | sku = product.generate_sku() 13 | self.products[sku]['quantity'] -= quantity 14 | #@@range_begin(list1) # ←この行は無視してください 15 | if self.products[sku]['quantity'] <= 0: 16 | del self.products[sku] 17 | #@@range_end(list1) # ←この行は無視してください 18 | -------------------------------------------------------------------------------- /ch05/10goodtest1/product.py: -------------------------------------------------------------------------------- 1 | # ch05/06unittest5/product.py 2 | class Product: 3 | def __init__(self, name, size, color): # <1> 4 | self.name = name 5 | self.size = size 6 | self.color = color 7 | 8 | def transform_name_for_sku(self): 9 | return self.name.upper() 10 | 11 | def transform_color_for_sku(self): 12 | return self.color.upper() 13 | 14 | def generate_sku(self): # <2> 15 | """ 16 | この製品のSKU(stock keeping unit: 最小管理単位)を生成する 17 | 18 | 例: 19 | >>> small_black_shoes = Product('shoes', 'S', 'black') 20 | >>> small_black_shoes.generate_sku() 21 | 'SHOES-S-BLACK' 22 | """ 23 | name = self.transform_name_for_sku() 24 | color = self.transform_color_for_sku() 25 | return f'{name}-{self.size}-{color}' 26 | -------------------------------------------------------------------------------- /ch05/10goodtest1/tax.py: -------------------------------------------------------------------------------- 1 | # ch05/07testdouble/tax.py 2 | from urllib.request import urlopen 3 | 4 | def add_sales_tax(original_amount, country, region): 5 | sales_tax_rate = urlopen(f'https://tax-api.com/{country}/{region}').read().decode() 6 | return original_amount * float(sales_tax_rate) 7 | -------------------------------------------------------------------------------- /ch05/10goodtest1/test_cart.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from cart import ShoppingCart 4 | from product import Product 5 | 6 | # Full test suite for product and shopping cart 7 | class ProductTestCase(unittest.TestCase): 8 | #@@range_begin(list1) 9 | def test_transform_name_for_sku(self): 10 | small_black_shoes = Product('shoes', 'S', 'black') 11 | self.assertEqual( 12 | 'SHOES', 13 | small_black_shoes.transform_name_for_sku(), 14 | ) 15 | #@@range_end(list1) 16 | def test_transform_color_for_sku(self): 17 | small_black_shoes = Product('shoes', 'S', 'black') 18 | self.assertEqual( 19 | 'BLACK', 20 | small_black_shoes.transform_color_for_sku(), 21 | ) 22 | 23 | def test_generate_sku(self): 24 | small_black_shoes = Product('shoes', 'S', 'black') 25 | self.assertEqual( 26 | 'SHOES-S-BLACK', 27 | small_black_shoes.generate_sku(), 28 | ) 29 | 30 | 31 | class ShoppingCartTestCase(unittest.TestCase): 32 | def test_cart_initially_empty(self): 33 | cart = ShoppingCart() 34 | self.assertDictEqual({}, cart.products) 35 | 36 | def test_add_product(self): 37 | cart = ShoppingCart() 38 | product = Product('shoes', 'S', 'blue') 39 | 40 | cart.add_product(product) 41 | 42 | self.assertDictEqual({'SHOES-S-BLUE': {'quantity': 1}}, cart.products) 43 | 44 | def test_add_two_of_a_product(self): # 同じものを2つ入れる 45 | cart = ShoppingCart() 46 | product = Product('shoes', 'S', 'blue') 47 | 48 | cart.add_product(product, quantity=2) 49 | 50 | self.assertDictEqual({'SHOES-S-BLUE': {'quantity': 2}}, cart.products) 51 | 52 | def test_add_two_different_products(self): # 2つの違うものを入れる 53 | cart = ShoppingCart() 54 | product_one = Product('shoes', 'S', 'blue') 55 | product_two = Product('shirt', 'M', 'gray') 56 | 57 | cart.add_product(product_one) 58 | cart.add_product(product_two) 59 | 60 | self.assertDictEqual( 61 | { 62 | 'SHOES-S-BLUE': {'quantity': 1}, 63 | 'SHIRT-M-GRAY': {'quantity': 1} 64 | }, 65 | cart.products 66 | ) 67 | 68 | def test_add_and_remove_product(self): # 入れてから削除 69 | cart = ShoppingCart() 70 | product = Product('shoes', 'S', 'blue') 71 | 72 | cart.add_product(product) 73 | cart.remove_product(product) 74 | 75 | self.assertDictEqual({}, cart.products) 76 | 77 | def test_remove_too_many_products(self): # 余分に削除 78 | cart = ShoppingCart() 79 | product = Product('shoes', 'S', 'blue') 80 | 81 | cart.add_product(product) 82 | cart.remove_product(product, quantity=2) # ←失敗する 83 | 84 | self.assertDictEqual({}, cart.products) 85 | -------------------------------------------------------------------------------- /ch05/10goodtest1/test_product.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from product import Product 4 | 5 | 6 | class ProductTestCase(unittest.TestCase): 7 | 8 | def test_transform_name_for_sku(self): 9 | small_black_shoes = Product('shoes', 'S', 'black') 10 | self.assertEqual( 11 | 'SHOES', 12 | small_black_shoes.transform_name_for_sku() 13 | ) 14 | 15 | medium_pink_tank_top = Product('tank top', 'M', 'pink') 16 | self.assertEqual( 17 | 'TANKTOP', 18 | medium_pink_tank_top.transform_name_for_sku() 19 | ) 20 | -------------------------------------------------------------------------------- /ch05/10goodtest1/test_tax.py: -------------------------------------------------------------------------------- 1 | # ch05/07testdouble/test_tax.py 2 | import io 3 | import unittest 4 | from unittest import mock 5 | from tax import add_sales_tax 6 | 7 | class SalesTaxTestCase(unittest.TestCase): 8 | @mock.patch('tax.urlopen') # <1> 9 | def test_get_sales_tax_returns_proper_value_from_api( 10 | self, 11 | mock_urlopen # <2> 12 | ): 13 | test_tax_rate = 1.06 14 | mock_urlopen.return_value = io.BytesIO( # <3> 15 | str(test_tax_rate).encode('utf-8') 16 | ) 17 | 18 | self.assertEqual( 19 | 5 * test_tax_rate, 20 | add_sales_tax(5, 'USA', 'MI') 21 | ) # <4> 22 | -------------------------------------------------------------------------------- /ch05/11goodtest2/cart.py: -------------------------------------------------------------------------------- 1 | # ch05/09tryitout2/cart.py 2 | from collections import defaultdict 3 | 4 | class ShoppingCart: 5 | def __init__(self): 6 | self.products = defaultdict(lambda: defaultdict(int)) 7 | 8 | def add_product(self, product, quantity=1): 9 | self.products[product.generate_sku()]['quantity'] += quantity 10 | 11 | def remove_product(self, product, quantity=1): 12 | sku = product.generate_sku() 13 | self.products[sku]['quantity'] -= quantity 14 | #@@range_begin(list1) # ←この行は無視してください 15 | if self.products[sku]['quantity'] <= 0: 16 | del self.products[sku] 17 | #@@range_end(list1) # ←この行は無視してください 18 | -------------------------------------------------------------------------------- /ch05/11goodtest2/product.py: -------------------------------------------------------------------------------- 1 | class Product: 2 | def __init__(self, name, size, color): # <1> 3 | self.name = name 4 | self.size = size 5 | self.color = color 6 | 7 | def transform_name_for_sku(self): 8 | return self.name.upper().replace(' ','') 9 | 10 | def transform_color_for_sku(self): 11 | return self.color.upper() 12 | 13 | def generate_sku(self): # <2> 14 | """ 15 | Generates a SKU (stock keeping unit: 最小管理単位) for this product. 16 | 17 | Example: 18 | >>> small_black_shoes = Product('shoes', 'S', 'black') 19 | >>> small_black_shoes.generate_sku() 20 | 'SHOES-S-BLACK' 21 | """ 22 | name = self.transform_name_for_sku() 23 | color = self.transform_color_for_sku() 24 | return f'{name}-{self.size}-{color}' 25 | -------------------------------------------------------------------------------- /ch05/11goodtest2/tax.py: -------------------------------------------------------------------------------- 1 | # ch05/07testdouble/tax.py 2 | from urllib.request import urlopen 3 | 4 | def add_sales_tax(original_amount, country, region): 5 | sales_tax_rate = urlopen(f'https://tax-api.com/{country}/{region}').read().decode() 6 | return original_amount * float(sales_tax_rate) 7 | -------------------------------------------------------------------------------- /ch05/11goodtest2/test_cart.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from cart import ShoppingCart 4 | from product import Product 5 | 6 | # Full test suite for product and shopping cart 7 | class ProductTestCase(unittest.TestCase): 8 | #@@range_begin(list1) 9 | def test_transform_name_for_sku(self): 10 | small_black_shoes = Product('shoes', 'S', 'black') 11 | self.assertEqual( 12 | 'SHOES', 13 | small_black_shoes.transform_name_for_sku(), 14 | ) 15 | #@@range_end(list1) 16 | def test_transform_color_for_sku(self): 17 | small_black_shoes = Product('shoes', 'S', 'black') 18 | self.assertEqual( 19 | 'BLACK', 20 | small_black_shoes.transform_color_for_sku(), 21 | ) 22 | 23 | def test_generate_sku(self): 24 | small_black_shoes = Product('shoes', 'S', 'black') 25 | self.assertEqual( 26 | 'SHOES-S-BLACK', 27 | small_black_shoes.generate_sku(), 28 | ) 29 | 30 | 31 | class ShoppingCartTestCase(unittest.TestCase): 32 | def test_cart_initially_empty(self): 33 | cart = ShoppingCart() 34 | self.assertDictEqual({}, cart.products) 35 | 36 | def test_add_product(self): 37 | cart = ShoppingCart() 38 | product = Product('shoes', 'S', 'blue') 39 | 40 | cart.add_product(product) 41 | 42 | self.assertDictEqual({'SHOES-S-BLUE': {'quantity': 1}}, cart.products) 43 | 44 | def test_add_two_of_a_product(self): # 同じものを2つ入れる 45 | cart = ShoppingCart() 46 | product = Product('shoes', 'S', 'blue') 47 | 48 | cart.add_product(product, quantity=2) 49 | 50 | self.assertDictEqual({'SHOES-S-BLUE': {'quantity': 2}}, cart.products) 51 | 52 | def test_add_two_different_products(self): # 2つの違うものを入れる 53 | cart = ShoppingCart() 54 | product_one = Product('shoes', 'S', 'blue') 55 | product_two = Product('shirt', 'M', 'gray') 56 | 57 | cart.add_product(product_one) 58 | cart.add_product(product_two) 59 | 60 | self.assertDictEqual( 61 | { 62 | 'SHOES-S-BLUE': {'quantity': 1}, 63 | 'SHIRT-M-GRAY': {'quantity': 1} 64 | }, 65 | cart.products 66 | ) 67 | 68 | def test_add_and_remove_product(self): # 入れてから削除 69 | cart = ShoppingCart() 70 | product = Product('shoes', 'S', 'blue') 71 | 72 | cart.add_product(product) 73 | cart.remove_product(product) 74 | 75 | self.assertDictEqual({}, cart.products) 76 | 77 | def test_remove_too_many_products(self): # 余分に削除 78 | cart = ShoppingCart() 79 | product = Product('shoes', 'S', 'blue') 80 | 81 | cart.add_product(product) 82 | cart.remove_product(product, quantity=2) # ←失敗する 83 | 84 | self.assertDictEqual({}, cart.products) 85 | -------------------------------------------------------------------------------- /ch05/11goodtest2/test_product.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from product import Product 4 | 5 | 6 | class ProductTestCase(unittest.TestCase): 7 | 8 | def test_transform_name_for_sku(self): 9 | small_black_shoes = Product('shoes', 'S', 'black') 10 | self.assertEqual( 11 | 'SHOES', 12 | small_black_shoes.transform_name_for_sku() 13 | ) 14 | 15 | medium_pink_tank_top = Product('tank top', 'M', 'pink') 16 | self.assertEqual( 17 | 'TANKTOP', 18 | medium_pink_tank_top.transform_name_for_sku() 19 | ) 20 | -------------------------------------------------------------------------------- /ch05/11goodtest2/test_tax.py: -------------------------------------------------------------------------------- 1 | # ch05/07testdouble/test_tax.py 2 | import io 3 | import unittest 4 | from unittest import mock 5 | from tax import add_sales_tax 6 | 7 | class SalesTaxTestCase(unittest.TestCase): 8 | @mock.patch('tax.urlopen') # <1> 9 | def test_get_sales_tax_returns_proper_value_from_api( 10 | self, 11 | mock_urlopen # <2> 12 | ): 13 | test_tax_rate = 1.06 14 | mock_urlopen.return_value = io.BytesIO( # <3> 15 | str(test_tax_rate).encode('utf-8') 16 | ) 17 | 18 | self.assertEqual( 19 | 5 * test_tax_rate, 20 | add_sales_tax(5, 'USA', 'MI') 21 | ) # <4> 22 | -------------------------------------------------------------------------------- /ch05/12pytest1/cart.py: -------------------------------------------------------------------------------- 1 | # ch05/09tryitout2/cart.py 2 | from collections import defaultdict 3 | 4 | class ShoppingCart: 5 | def __init__(self): 6 | self.products = defaultdict(lambda: defaultdict(int)) 7 | 8 | def add_product(self, product, quantity=1): 9 | self.products[product.generate_sku()]['quantity'] += quantity 10 | 11 | def remove_product(self, product, quantity=1): 12 | sku = product.generate_sku() 13 | self.products[sku]['quantity'] -= quantity 14 | if self.products[sku]['quantity'] <= 0: 15 | del self.products[sku] 16 | 17 | -------------------------------------------------------------------------------- /ch05/12pytest1/product.py: -------------------------------------------------------------------------------- 1 | class Product: 2 | def __init__(self, name, size, color): # <1> 3 | self.name = name 4 | self.size = size 5 | self.color = color 6 | 7 | def transform_name_for_sku(self): 8 | return self.name.upper().replace(' ','') 9 | 10 | def transform_color_for_sku(self): 11 | return self.color.upper() 12 | 13 | def generate_sku(self): # <2> 14 | """ 15 | Generates a SKU (stock keeping unit: 最小管理単位) for this product. 16 | 17 | Example: 18 | >>> small_black_shoes = Product('shoes', 'S', 'black') 19 | >>> small_black_shoes.generate_sku() 20 | 'SHOES-S-BLACK' 21 | """ 22 | name = self.transform_name_for_sku() 23 | color = self.transform_color_for_sku() 24 | return f'{name}-{self.size}-{color}' 25 | -------------------------------------------------------------------------------- /ch05/12pytest1/tax.py: -------------------------------------------------------------------------------- 1 | # ch05/07testdouble/tax.py 2 | from urllib.request import urlopen 3 | 4 | def add_sales_tax(original_amount, country, region): 5 | sales_tax_rate = urlopen(f'https://tax-api.com/{country}/{region}').read().decode() 6 | return original_amount * float(sales_tax_rate) 7 | -------------------------------------------------------------------------------- /ch05/12pytest1/test_cart.py: -------------------------------------------------------------------------------- 1 | # ch05/08tryitout1/test_cart.py 2 | import unittest 3 | 4 | from cart import ShoppingCart 5 | from product import Product 6 | 7 | # Full test suite for product and shopping cart 8 | class ProductTestCase(unittest.TestCase): 9 | #@@range_begin(list2) # ←この行は無視してください 10 | # ch05/08tryitout/test_cart.py 11 | #@@range_begin(list1) # ←この行は無視してください 12 | def test_transform_name_for_sku(self): 13 | small_black_shoes = Product('shoes', 'S', 'black') 14 | self.assertEqual( 15 | 'SHOES', 16 | small_black_shoes.transform_name_for_sku(), 17 | ) 18 | #@@range_end(list1) # ←この行は無視してください 19 | def test_transform_color_for_sku(self): 20 | small_black_shoes = Product('shoes', 'S', 'black') 21 | self.assertEqual( 22 | 'BLACK', 23 | small_black_shoes.transform_color_for_sku(), 24 | ) 25 | 26 | def test_generate_sku(self): 27 | small_black_shoes = Product('shoes', 'S', 'black') 28 | self.assertEqual( 29 | 'SHOES-S-BLACK', 30 | small_black_shoes.generate_sku(), 31 | ) 32 | 33 | 34 | class ShoppingCartTestCase(unittest.TestCase): 35 | def test_cart_initially_empty(self): 36 | cart = ShoppingCart() 37 | self.assertDictEqual({}, cart.products) 38 | 39 | def test_add_product(self): 40 | cart = ShoppingCart() 41 | product = Product('shoes', 'S', 'blue') 42 | 43 | cart.add_product(product) 44 | 45 | self.assertDictEqual({'SHOES-S-BLUE': {'quantity': 1}}, cart.products) 46 | 47 | def test_add_two_of_a_product(self): 48 | cart = ShoppingCart() 49 | product = Product('shoes', 'S', 'blue') 50 | 51 | cart.add_product(product, quantity=2) 52 | 53 | self.assertDictEqual({'SHOES-S-BLUE': {'quantity': 2}}, cart.products) 54 | 55 | def test_add_two_different_products(self): 56 | cart = ShoppingCart() 57 | product_one = Product('shoes', 'S', 'blue') 58 | product_two = Product('shirt', 'M', 'gray') 59 | 60 | cart.add_product(product_one) 61 | cart.add_product(product_two) 62 | 63 | self.assertDictEqual( 64 | { 65 | 'SHOES-S-BLUE': {'quantity': 1}, 66 | 'SHIRT-M-GRAY': {'quantity': 1} 67 | }, 68 | cart.products 69 | ) 70 | 71 | def test_add_and_remove_product(self): 72 | cart = ShoppingCart() 73 | product = Product('shoes', 'S', 'blue') 74 | 75 | cart.add_product(product) 76 | cart.remove_product(product) 77 | 78 | self.assertDictEqual({}, cart.products) 79 | 80 | def test_remove_too_many_products(self): 81 | cart = ShoppingCart() 82 | product = Product('shoes', 'S', 'blue') 83 | 84 | cart.add_product(product) 85 | cart.remove_product(product, quantity=2) # ←失敗する 86 | 87 | self.assertDictEqual({}, cart.products) 88 | #@@range_end(list2) # ←この行は無視してください 89 | -------------------------------------------------------------------------------- /ch05/12pytest1/test_product.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from product import Product 4 | 5 | 6 | class ProductTestCase(unittest.TestCase): 7 | 8 | def test_transform_name_for_sku(self): 9 | small_black_shoes = Product('shoes', 'S', 'black') 10 | self.assertEqual( 11 | 'SHOES', 12 | small_black_shoes.transform_name_for_sku() 13 | ) 14 | 15 | medium_pink_tank_top = Product('tank top', 'M', 'pink') 16 | self.assertEqual( 17 | 'TANKTOP', 18 | medium_pink_tank_top.transform_name_for_sku() 19 | ) 20 | -------------------------------------------------------------------------------- /ch05/12pytest1/test_tax.py: -------------------------------------------------------------------------------- 1 | # ch05/07testdouble/test_tax.py 2 | import io 3 | import unittest 4 | from unittest import mock 5 | from tax import add_sales_tax 6 | 7 | class SalesTaxTestCase(unittest.TestCase): 8 | @mock.patch('tax.urlopen') # <1> 9 | def test_get_sales_tax_returns_proper_value_from_api( 10 | self, 11 | mock_urlopen # <2> 12 | ): 13 | test_tax_rate = 1.06 14 | mock_urlopen.return_value = io.BytesIO( # <3> 15 | str(test_tax_rate).encode('utf-8') 16 | ) 17 | 18 | self.assertEqual( 19 | 5 * test_tax_rate, 20 | add_sales_tax(5, 'USA', 'MI') 21 | ) # <4> 22 | -------------------------------------------------------------------------------- /ch05/13pytest2/cart.py: -------------------------------------------------------------------------------- 1 | # ch05/09tryitout2/cart.py 2 | from collections import defaultdict 3 | 4 | class ShoppingCart: 5 | def __init__(self): 6 | self.products = defaultdict(lambda: defaultdict(int)) 7 | 8 | def add_product(self, product, quantity=1): 9 | self.products[product.generate_sku()]['quantity'] += quantity 10 | 11 | def remove_product(self, product, quantity=1): 12 | sku = product.generate_sku() 13 | self.products[sku]['quantity'] -= quantity 14 | if self.products[sku]['quantity'] <= 0: 15 | del self.products[sku] 16 | 17 | -------------------------------------------------------------------------------- /ch05/13pytest2/product.py: -------------------------------------------------------------------------------- 1 | class Product: 2 | def __init__(self, name, size, color): # <1> 3 | self.name = name 4 | self.size = size 5 | self.color = color 6 | 7 | def transform_name_for_sku(self): 8 | return self.name.upper().replace(' ','') 9 | 10 | def transform_color_for_sku(self): 11 | return self.color.upper() 12 | 13 | def generate_sku(self): # <2> 14 | """ 15 | Generates a SKU (stock keeping unit: 最小管理単位) for this product. 16 | 17 | Example: 18 | >>> small_black_shoes = Product('shoes', 'S', 'black') 19 | >>> small_black_shoes.generate_sku() 20 | 'SHOES-S-BLACK' 21 | """ 22 | name = self.transform_name_for_sku() 23 | color = self.transform_color_for_sku() 24 | return f'{name}-{self.size}-{color}' 25 | -------------------------------------------------------------------------------- /ch05/13pytest2/tax.py: -------------------------------------------------------------------------------- 1 | # ch05/07testdouble/tax.py 2 | from urllib.request import urlopen 3 | 4 | def add_sales_tax(original_amount, country, region): 5 | sales_tax_rate = urlopen(f'https://tax-api.com/{country}/{region}').read().decode() 6 | return original_amount * float(sales_tax_rate) 7 | -------------------------------------------------------------------------------- /ch05/13pytest2/test_cart.py: -------------------------------------------------------------------------------- 1 | # ch05/08tryitout1/test_cart.py 2 | import unittest 3 | 4 | from cart import ShoppingCart 5 | from product import Product 6 | 7 | # Full test suite for product and shopping cart 8 | class ProductTestCase(unittest.TestCase): 9 | #@@range_begin(list2) # ←この行は無視してください 10 | # ch05/08tryitout/test_cart.py 11 | #@@range_begin(list1) # ←この行は無視してください 12 | def test_transform_name_for_sku(self): 13 | small_black_shoes = Product('shoes', 'S', 'black') 14 | self.assertEqual( 15 | 'SHOES', 16 | small_black_shoes.transform_name_for_sku(), 17 | ) 18 | #@@range_end(list1) # ←この行は無視してください 19 | def test_transform_color_for_sku(self): 20 | small_black_shoes = Product('shoes', 'S', 'black') 21 | self.assertEqual( 22 | 'BLACK', 23 | small_black_shoes.transform_color_for_sku(), 24 | ) 25 | 26 | def test_generate_sku(self): 27 | small_black_shoes = Product('shoes', 'S', 'black') 28 | self.assertEqual( 29 | 'SHOES-S-BLACK', 30 | small_black_shoes.generate_sku(), 31 | ) 32 | 33 | 34 | class ShoppingCartTestCase(unittest.TestCase): 35 | def test_cart_initially_empty(self): 36 | cart = ShoppingCart() 37 | self.assertDictEqual({}, cart.products) 38 | 39 | def test_add_product(self): 40 | cart = ShoppingCart() 41 | product = Product('shoes', 'S', 'blue') 42 | 43 | cart.add_product(product) 44 | 45 | self.assertDictEqual({'SHOES-S-BLUE': {'quantity': 1}}, cart.products) 46 | 47 | def test_add_two_of_a_product(self): 48 | cart = ShoppingCart() 49 | product = Product('shoes', 'S', 'blue') 50 | 51 | cart.add_product(product, quantity=2) 52 | 53 | self.assertDictEqual({'SHOES-S-BLUE': {'quantity': 2}}, cart.products) 54 | 55 | def test_add_two_different_products(self): 56 | cart = ShoppingCart() 57 | product_one = Product('shoes', 'S', 'blue') 58 | product_two = Product('shirt', 'M', 'gray') 59 | 60 | cart.add_product(product_one) 61 | cart.add_product(product_two) 62 | 63 | self.assertDictEqual( 64 | { 65 | 'SHOES-S-BLUE': {'quantity': 1}, 66 | 'SHIRT-M-GRAY': {'quantity': 1} 67 | }, 68 | cart.products 69 | ) 70 | 71 | def test_add_and_remove_product(self): 72 | cart = ShoppingCart() 73 | product = Product('shoes', 'S', 'blue') 74 | 75 | cart.add_product(product) 76 | cart.remove_product(product) 77 | 78 | self.assertDictEqual({}, cart.products) 79 | 80 | def test_remove_too_many_products(self): 81 | cart = ShoppingCart() 82 | product = Product('shoes', 'S', 'blue') 83 | 84 | cart.add_product(product) 85 | cart.remove_product(product, quantity=2) # ←失敗する 86 | 87 | self.assertDictEqual({}, cart.products) 88 | #@@range_end(list2) # ←この行は無視してください 89 | -------------------------------------------------------------------------------- /ch05/13pytest2/test_product.py: -------------------------------------------------------------------------------- 1 | from product import Product 2 | 3 | class TestProduct: 4 | def test_transform_name_for_sku(self): 5 | small_black_shoes = Product('shoes', 'S', 'black') 6 | assert small_black_shoes.transform_name_for_sku() == 'SHOES' 7 | 8 | def test_transform_color_for_sku(self): 9 | small_black_shoes = Product('shoes', 'S', 'black') 10 | assert small_black_shoes.transform_color_for_sku() == 'BLACK' 11 | 12 | def test_generate_sku(self): 13 | small_black_shoes = Product('shoes', 'S', 'black') 14 | assert small_black_shoes.generate_sku() == 'SHOES-S-BLACK' 15 | -------------------------------------------------------------------------------- /ch05/13pytest2/test_tax.py: -------------------------------------------------------------------------------- 1 | # ch05/07testdouble/test_tax.py 2 | import io 3 | import unittest 4 | from unittest import mock 5 | from tax import add_sales_tax 6 | 7 | class SalesTaxTestCase(unittest.TestCase): 8 | @mock.patch('tax.urlopen') # <1> 9 | def test_get_sales_tax_returns_proper_value_from_api( 10 | self, 11 | mock_urlopen # <2> 12 | ): 13 | test_tax_rate = 1.06 14 | mock_urlopen.return_value = io.BytesIO( # <3> 15 | str(test_tax_rate).encode('utf-8') 16 | ) 17 | 18 | self.assertEqual( 19 | 5 * test_tax_rate, 20 | add_sales_tax(5, 'USA', 'MI') 21 | ) # <4> 22 | -------------------------------------------------------------------------------- /ch06/bark1/bark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | from collections import OrderedDict 5 | 6 | import commands 7 | 8 | 9 | def print_bookmarks(bookmarks): 10 | for bookmark in bookmarks: 11 | print('\t'.join( 12 | str(field) if field else '' 13 | for field in bookmark 14 | )) 15 | 16 | 17 | #@@range_begin(list1) 18 | class Option: 19 | def __init__(self, name, command, prep_call=None): 20 | self.name = name # <1> 21 | self.command = command # <2> 22 | self.prep_call = prep_call # <3> 23 | 24 | def _handle_message(self, message): 25 | if isinstance(message, list): 26 | print_bookmarks(message) 27 | else: 28 | print(message) 29 | 30 | def choose(self): # <4> 31 | data = self.prep_call() if self.prep_call else None # <5> 32 | message = self.command.execute(data) if data else self.command.execute() # <6> 33 | self._handle_message(message) 34 | 35 | def __str__(self): # <7> 36 | return self.name 37 | #@@range_end(list1) 38 | 39 | 40 | #@@range_begin(list6) 41 | def clear_screen(): 42 | clear = 'cls' if os.name == 'nt' else 'clear' 43 | os.system(clear) 44 | #@@range_end(list6) 45 | 46 | 47 | #@@range_begin(list2) 48 | def print_options(options): 49 | for shortcut, option in options.items(): 50 | print(f'({shortcut}) {option}') 51 | print() 52 | #@@range_end(list2) 53 | 54 | 55 | #@@range_begin(list4) 6.13 56 | def option_choice_is_valid(choice, options): 57 | return choice in options or choice.upper() in options # <1> 58 | 59 | 60 | def get_option_choice(options): 61 | choice = input('操作を選択してください: ') # <2> 62 | while not option_choice_is_valid(choice, options): # <3> 63 | print('A, B, T, D, Qのいずれかを入力してください(小文字でもOK。ただし半角文字)') 64 | choice = input('操作を選択してください: ') 65 | return options[choice.upper()] # <4> 66 | #@@range_end(list4) 67 | 68 | 69 | #@@range_begin(list5) 70 | def get_user_input(label, required=True): # <1> 71 | value = input(f'{label}: ') or None # <2> 72 | while required and not value: # <3> 73 | value = input(f'{label}: ') or None 74 | return value 75 | 76 | 77 | def get_new_bookmark_data(): # <4> 78 | return { 79 | 'title': get_user_input('タイトル'), 80 | 'url': get_user_input('URL'), 81 | 'notes': get_user_input('メモ', required=False), # <5> 82 | } 83 | 84 | 85 | def get_bookmark_id_for_deletion(): # <6> 86 | return get_user_input('削除するブックマークのIDを指定') 87 | #@@range_end(list5) 88 | 89 | 90 | def loop(): # <1> 91 | clear_screen() 92 | #@@range_begin(list3) 93 | options = OrderedDict({ 94 | 'A': Option('追加', commands.AddBookmarkCommand(), prep_call=get_new_bookmark_data), 95 | 'B': Option('登録順にリスト', commands.ListBookmarksCommand()), 96 | 'T': Option('タイトル順にリスト', commands.ListBookmarksCommand(order_by='title')), 97 | 'D': Option('削除', commands.DeleteBookmarkCommand(), prep_call=get_bookmark_id_for_deletion), 98 | 'Q': Option('終了', commands.QuitCommand()), 99 | }) 100 | print_options(options) 101 | #@@range_end(list3) 102 | 103 | chosen_option = get_option_choice(options) 104 | clear_screen() 105 | chosen_option.choose() 106 | 107 | _ = input('Enterを押すとメニューに戻ります') # <2> 108 | 109 | 110 | if __name__ == '__main__': 111 | print("ブックマーク管理アプリ Bark") 112 | commands.CreateBookmarksTableCommand().execute() 113 | 114 | while True: # <3> 115 | loop() 116 | -------------------------------------------------------------------------------- /ch06/bark1/commands.py: -------------------------------------------------------------------------------- 1 | # ch06/bark1/commands.py 2 | import sys 3 | from datetime import datetime 4 | 5 | from database import DatabaseManager 6 | 7 | #@@range_begin(list1) 8 | db = DatabaseManager('bookmarks.db') # <1> 9 | 10 | 11 | class CreateBookmarksTableCommand: 12 | def execute(self): # <2> 13 | db.create_table('bookmarks', { # <3> 14 | 'id': 'integer primary key autoincrement', 15 | 'title': 'text not null', 16 | 'url': 'text not null', 17 | 'notes': 'text', 18 | 'date_added': 'text not null', 19 | }) 20 | #@@range_end(list1) 21 | 22 | #@@range_begin(list2) 23 | class AddBookmarkCommand: 24 | def execute(self, data): 25 | data['date_added'] = datetime.utcnow().isoformat() # <1> 26 | db.add('bookmarks', data) # <2> 27 | return 'ブックマークを追加しました。' # <3> 28 | #@@range_end(list2) 29 | 30 | #@@range_begin(list3) 31 | class ListBookmarksCommand: 32 | def __init__(self, order_by='date_added'): # <1> 33 | self.order_by = order_by 34 | 35 | def execute(self): 36 | return db.select('bookmarks', order_by=self.order_by).fetchall() # <2> 37 | #@@range_end(list3) 38 | 39 | #@@range_begin(list4) 40 | class DeleteBookmarkCommand: 41 | def execute(self, data): 42 | db.delete('bookmarks', {'id': data}) # <1> 43 | return 'ブックマークを削除しました。' 44 | #@@range_end(list4) 45 | 46 | 47 | #@@range_begin(list5) 48 | class QuitCommand: 49 | def execute(self): 50 | sys.exit() # <1> 51 | #@@range_end(list5) 52 | -------------------------------------------------------------------------------- /ch06/bark1/database.py: -------------------------------------------------------------------------------- 1 | # ch06/bark1/database.py 2 | #@@range_begin(list1) 3 | import sqlite3 4 | 5 | class DatabaseManager: 6 | def __init__(self, database_filename): 7 | self.connection = sqlite3.connect(database_filename) # <1> 8 | 9 | def __del__(self): 10 | self.connection.close() # <2> 11 | #@@range_end(list1) 12 | #@@range_begin(list2) 13 | def _execute(self, statement, values=None): # <1> 14 | with self.connection: 15 | cursor = self.connection.cursor() 16 | cursor.execute(statement, values or []) # <2> 17 | return cursor 18 | #@@range_end(list2) 19 | 20 | #@@range_begin(list3) 21 | def create_table(self, table_name, columns): 22 | columns_with_types = [ # <1> 23 | f'{column_name} {data_type}' 24 | for column_name, data_type in columns.items() 25 | ] 26 | self._execute( # <2> 27 | f''' 28 | CREATE TABLE IF NOT EXISTS {table_name} 29 | ({', '.join(columns_with_types)}); 30 | ''' 31 | ) 32 | #@@range_end(list3) 33 | 34 | def drop_table(self, table_name): 35 | self._execute(f'DROP TABLE {table_name};') 36 | 37 | #@@range_begin(list4) 38 | def add(self, table_name, data): 39 | placeholders = ', '.join('?' * len(data)) 40 | column_names = ', '.join(data.keys()) # <1> 41 | column_values = tuple(data.values()) # <2> 42 | 43 | self._execute( 44 | f''' 45 | INSERT INTO {table_name} 46 | ({column_names}) 47 | VALUES ({placeholders}); 48 | ''', 49 | column_values, # <3> 50 | ) 51 | #@@range_end(list4) 52 | 53 | #@@range_begin(list5) 54 | def delete(self, table_name, criteria): # <1> 55 | placeholders = [f'{column} = ?' for column in criteria.keys()] 56 | delete_criteria = ' AND '.join(placeholders) 57 | self._execute( 58 | f''' 59 | DELETE FROM {table_name} 60 | WHERE {delete_criteria}; 61 | ''', 62 | tuple(criteria.values()), # <2> 63 | ) 64 | #@@range_end(list5) 65 | 66 | #@@range_begin(list6) 67 | def select(self, table_name, criteria=None, order_by=None): 68 | criteria = criteria or {} # <1> 69 | 70 | query = f'SELECT * FROM {table_name}' 71 | 72 | if criteria: # <2> 73 | placeholders = [f'{column} = ?' for column in criteria.keys()] 74 | select_criteria = ' AND '.join(placeholders) 75 | query += f' WHERE {select_criteria}' 76 | 77 | if order_by: # <3> 78 | query += f' ORDER BY {order_by}' 79 | 80 | return self._execute( # <4> 81 | query, 82 | tuple(criteria.values()), 83 | ) 84 | #@@range_end(list6) 85 | -------------------------------------------------------------------------------- /ch07/01bicycle1/bicycle.py: -------------------------------------------------------------------------------- 1 | class Tire: # <1> 2 | def __repr__(self): 3 | return 'ゴムのタイヤ' 4 | 5 | 6 | class Frame: 7 | def __repr__(self): 8 | return 'アルミのフレーム' 9 | 10 | 11 | class Bicycle: 12 | def __init__(self): # <2> 13 | self.front_tire = Tire() 14 | self.back_tire = Tire() 15 | self.frame = Frame() 16 | 17 | def print_specs(self): # <3> 18 | print(f'フレーム: {self.frame}') 19 | print(f'前のタイヤ: {self.front_tire}。後ろのタイヤ: {self.back_tire}') 20 | 21 | 22 | if __name__ == '__main__': # <4> 23 | bike = Bicycle() 24 | bike.print_specs() 25 | -------------------------------------------------------------------------------- /ch07/02bicycle2/bicycle.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | class Tire: # <1> 3 | def __repr__(self): 4 | return 'ゴムのタイヤ' 5 | 6 | 7 | class Frame: 8 | def __repr__(self): 9 | return 'アルミのフレーム' 10 | 11 | 12 | class Bicycle: 13 | def __init__(self, front_tire, back_tire, frame): # <1> 14 | self.front_tire = front_tire 15 | self.back_tire = back_tire 16 | self.frame = frame 17 | 18 | def print_specs(self): 19 | print(f'フレーム: {self.frame}') 20 | print(f'前のタイヤ: {self.front_tire}。後ろのタイヤ: {self.back_tire}') 21 | 22 | 23 | if __name__ == '__main__': 24 | bike = Bicycle(Tire(), Tire(), Frame()) # <2> 25 | bike.print_specs() 26 | #@@range_end(list1) 27 | 28 | -------------------------------------------------------------------------------- /ch07/03bicycle3/bicycle.py: -------------------------------------------------------------------------------- 1 | class Tire: # <1> 2 | def __repr__(self): 3 | return 'ゴムのタイヤ' 4 | 5 | 6 | class Frame: 7 | def __repr__(self): 8 | return 'アルミのフレーム' 9 | 10 | #@@range_begin(list1) 11 | class CarbonFiberFrame: 12 | def __repr__(self): 13 | return 'カーボンファイバーのフレーム' 14 | #@@range_end(list1) 15 | 16 | class Bicycle: 17 | def __init__(self, front_tire, back_tire, frame): # <1> 18 | self.front_tire = front_tire 19 | self.back_tire = back_tire 20 | self.frame = frame 21 | 22 | def print_specs(self): 23 | print(f'フレーム: {self.frame}') 24 | print(f'前のタイヤ: {self.front_tire}。後ろのタイヤ: {self.back_tire}') 25 | 26 | 27 | if __name__ == '__main__': 28 | bike = Bicycle(Tire(), Tire(), Frame()) # <2> 29 | bike.print_specs() 30 | 31 | print("----------") 32 | #@@range_begin(list2) 33 | bike = Bicycle(Tire(), Tire(), CarbonFiberFrame()) # <1> 34 | bike.print_specs() # <2> 35 | #@@range_end(list2) 36 | -------------------------------------------------------------------------------- /ch07/04bark2/bark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | from collections import OrderedDict 5 | 6 | import commands 7 | 8 | 9 | def print_bookmarks(bookmarks): 10 | for bookmark in bookmarks: 11 | print('\t'.join( 12 | str(field) if field else '' 13 | for field in bookmark 14 | )) 15 | 16 | 17 | class Option: 18 | def __init__(self, name, command, prep_call=None): 19 | self.name = name 20 | self.command = command 21 | self.prep_call = prep_call 22 | 23 | def _handle_message(self, message): 24 | if isinstance(message, list): 25 | print_bookmarks(message) 26 | else: 27 | print(message) 28 | 29 | def choose(self): 30 | data = self.prep_call() if self.prep_call else None 31 | message = self.command.execute(data) if data else self.command.execute() 32 | self._handle_message(message) 33 | 34 | def __str__(self): 35 | return self.name 36 | 37 | 38 | def clear_screen(): 39 | clear = 'cls' if os.name == 'nt' else 'clear' 40 | os.system(clear) 41 | 42 | 43 | def print_options(options): 44 | for shortcut, option in options.items(): 45 | print(f'({shortcut}) {option}') 46 | print() 47 | 48 | 49 | def option_choice_is_valid(choice, options): 50 | return choice in options or choice.upper() in options 51 | 52 | 53 | def get_option_choice(options): 54 | choice = input('操作を選択してください: ') 55 | while not option_choice_is_valid(choice, options): 56 | print('A, B, T, D, G, Qのいずれかを入力してください(小文字でもOK。ただし半角文字)') 57 | choice = input('操作を選択してください: ') 58 | return options[choice.upper()] 59 | 60 | 61 | def get_user_input(label, required=True): 62 | value = input(f'{label}: ') or None 63 | while required and not value: 64 | value = input(f'{label}: ') or None 65 | return value 66 | 67 | 68 | def get_new_bookmark_data(): 69 | return { 70 | 'タイトル': get_user_input('タイトル'), 71 | 'URL': get_user_input('URL'), 72 | 'メモ': get_user_input('メモ', required=False), 73 | } 74 | 75 | 76 | def get_bookmark_id_for_deletion(): 77 | return get_user_input('削除するブックマークのIDを指定') 78 | 79 | #@@range_begin(list1) 80 | def get_github_import_options(): # <1> 81 | return { 82 | 'github_username': get_user_input('GitHubのユーザー名'), 83 | 'preserve_timestamps': # <2> 84 | get_user_input( 85 | 'タイムスタンプを維持しますか [Y/n]', 86 | required=False 87 | ) in {'Y', 'y', None}, # <3> 88 | } 89 | #@@range_end(list1) 90 | 91 | 92 | def loop(): # <1> 93 | clear_screen() 94 | 95 | options = OrderedDict({ 96 | 'A': Option('追加', commands.AddBookmarkCommand(), prep_call=get_new_bookmark_data), 97 | 'B': Option('登録順にリスト', commands.ListBookmarksCommand()), 98 | 'T': Option('タイトル順にリスト', commands.ListBookmarksCommand(order_by='タイトル')), 99 | 'D': Option('削除', commands.DeleteBookmarkCommand(), prep_call=get_bookmark_id_for_deletion), 100 | #@@range_begin(list2) 101 | 'G': Option( # <4> 102 | 'GitHubのスターをインポート', 103 | commands.ImportGitHubStarsCommand(), 104 | prep_call=get_github_import_options 105 | ), 106 | 'Q': Option('終了', commands.QuitCommand()), 107 | }) 108 | #@@range_end(list2) 109 | print_options(options) 110 | 111 | chosen_option = get_option_choice(options) 112 | clear_screen() 113 | chosen_option.choose() 114 | 115 | _ = input('Enterを押すとメニューに戻ります') 116 | 117 | 118 | if __name__ == '__main__': 119 | print("ブックマーク管理アプリ Bark") 120 | commands.CreateBookmarksTableCommand().execute() 121 | 122 | while True: 123 | loop() 124 | -------------------------------------------------------------------------------- /ch07/04bark2/commands.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import datetime 3 | 4 | import requests 5 | 6 | from database import DatabaseManager 7 | 8 | db = DatabaseManager('bookmarks.db') 9 | 10 | 11 | class CreateBookmarksTableCommand: 12 | def execute(self): 13 | db.create_table('bookmarks', { 14 | 'id': 'integer primary key autoincrement', 15 | 'タイトル': 'text not null', 16 | 'URL': 'text not null', 17 | 'メモ': 'text', 18 | 'date_added': 'text not null', 19 | }) 20 | 21 | #@@range_begin(list1) 22 | class AddBookmarkCommand: 23 | def execute(self, data, timestamp=None): # <1> 24 | data['date_added'] = timestamp or datetime.utcnow().isoformat() # <2> 25 | db.add('bookmarks', data) 26 | return 'ブックマークを追加しました。' 27 | #@@range_end(list1) 28 | 29 | class ListBookmarksCommand: 30 | def __init__(self, order_by='date_added'): 31 | self.order_by = order_by 32 | 33 | def execute(self): 34 | return db.select('bookmarks', order_by=self.order_by).fetchall() 35 | 36 | 37 | class DeleteBookmarkCommand: 38 | def execute(self, data): 39 | db.delete('bookmarks', {'id': data}) 40 | return 'ブックマークを削除しました。' 41 | 42 | 43 | class QuitCommand: 44 | def execute(self): 45 | sys.exit() 46 | 47 | #@@range_begin(list2) 48 | class ImportGitHubStarsCommand: 49 | def _extract_bookmark_info(self, repo): # <1> 50 | return { 51 | 'タイトル': repo['name'], 52 | 'URL': repo['html_url'], 53 | 'メモ': repo['description'], 54 | } 55 | 56 | def execute(self, data): 57 | bookmarks_imported = 0 58 | 59 | github_username = data['github_username'] 60 | next_page_of_results = \ 61 | f'https://api.github.com/users/{github_username}/starred' # <2> 62 | 63 | while next_page_of_results: # <3> 64 | stars_response = requests.get( # <4> 65 | next_page_of_results, 66 | headers={'Accept': 'application/vnd.github.v3.star+json'}, 67 | ) 68 | next_page_of_results = stars_response.links.get('next', {}).get('url') # <5> 69 | 70 | for repo_info in stars_response.json(): # スターごとの繰り返し 71 | repo = repo_info['repo'] # <6> 72 | 73 | if data['preserve_timestamps']: 74 | timestamp = datetime.strptime( 75 | repo_info['starred_at'], # <7> 76 | '%Y-%m-%dT%H:%M:%SZ' # <8> 77 | ) 78 | else: 79 | timestamp = None 80 | 81 | bookmarks_imported += 1 82 | AddBookmarkCommand().execute( # <9> 83 | self._extract_bookmark_info(repo), 84 | timestamp=timestamp, 85 | ) 86 | 87 | return f'{bookmarks_imported}個のブックマークをインポートしました。' # <10> 88 | #@@range_end(list2) 89 | -------------------------------------------------------------------------------- /ch07/04bark2/database.py: -------------------------------------------------------------------------------- 1 | # ch06/bark1/database.py 2 | import sqlite3 3 | 4 | class DatabaseManager: 5 | def __init__(self, database_filename): 6 | self.connection = sqlite3.connect(database_filename) # <1> 7 | 8 | def __del__(self): 9 | self.connection.close() # <2> 10 | 11 | def _execute(self, statement, values=None): # <1> 12 | with self.connection: 13 | cursor = self.connection.cursor() 14 | cursor.execute(statement, values or []) # <2> 15 | return cursor 16 | 17 | def create_table(self, table_name, columns): 18 | columns_with_types = [ # <1> 19 | f'{column_name} {data_type}' 20 | for column_name, data_type in columns.items() 21 | ] 22 | self._execute( # <2> 23 | f''' 24 | CREATE TABLE IF NOT EXISTS {table_name} 25 | ({', '.join(columns_with_types)}); 26 | ''' 27 | ) 28 | 29 | def drop_table(self, table_name): 30 | self._execute(f'DROP TABLE {table_name};') 31 | 32 | def add(self, table_name, data): 33 | placeholders = ', '.join('?' * len(data)) 34 | column_names = ', '.join(data.keys()) # <1> 35 | column_values = tuple(data.values()) # <2> 36 | 37 | self._execute( 38 | f''' 39 | INSERT INTO {table_name} 40 | ({column_names}) 41 | VALUES ({placeholders}); 42 | ''', 43 | column_values, # <3> 44 | ) 45 | 46 | def delete(self, table_name, criteria): # <1> 47 | placeholders = [f'{column} = ?' for column in criteria.keys()] 48 | delete_criteria = ' AND '.join(placeholders) 49 | self._execute( 50 | f''' 51 | DELETE FROM {table_name} 52 | WHERE {delete_criteria}; 53 | ''', 54 | tuple(criteria.values()), # <2> 55 | ) 56 | 57 | def select(self, table_name, criteria=None, order_by=None): 58 | criteria = criteria or {} # <1> 59 | 60 | query = f'SELECT * FROM {table_name}' 61 | 62 | if criteria: # <2> 63 | placeholders = [f'{column} = ?' for column in criteria.keys()] 64 | select_criteria = ' AND '.join(placeholders) 65 | query += f' WHERE {select_criteria}' 66 | 67 | if order_by: # <3> 68 | query += f' ORDER BY {order_by}' 69 | 70 | return self._execute( # <4> 71 | query, 72 | tuple(criteria.values()), 73 | ) 74 | -------------------------------------------------------------------------------- /ch07/05bark3/bark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | from collections import OrderedDict 5 | 6 | import commands 7 | 8 | 9 | def print_bookmarks(bookmarks): 10 | for bookmark in bookmarks: 11 | print('\t'.join( 12 | str(field) if field else '' 13 | for field in bookmark 14 | )) 15 | 16 | 17 | class Option: 18 | def __init__(self, name, command, prep_call=None): 19 | self.name = name 20 | self.command = command 21 | self.prep_call = prep_call 22 | 23 | def _handle_message(self, message): 24 | if isinstance(message, list): 25 | print_bookmarks(message) 26 | else: 27 | print(message) 28 | 29 | def choose(self): 30 | data = self.prep_call() if self.prep_call else None 31 | message = self.command.execute(data) if data else self.command.execute() 32 | self._handle_message(message) 33 | 34 | def __str__(self): 35 | return self.name 36 | 37 | 38 | def clear_screen(): 39 | clear = 'cls' if os.name == 'nt' else 'clear' 40 | os.system(clear) 41 | 42 | 43 | def print_options(options): 44 | for shortcut, option in options.items(): 45 | print(f'({shortcut}) {option}') 46 | print() 47 | 48 | 49 | def option_choice_is_valid(choice, options): 50 | return choice in options or choice.upper() in options 51 | 52 | 53 | def get_option_choice(options): 54 | choice = input('操作を選択してください: ') 55 | while not option_choice_is_valid(choice, options): 56 | print('A, B, T, E, D, G, Qのいずれかを入力してください(小文字でもOK。ただし半角文字)') 57 | choice = input('操作を選択してください: ') 58 | return options[choice.upper()] 59 | 60 | 61 | def get_user_input(label, required=True): 62 | value = input(f'{label}: ') or None 63 | while required and not value: 64 | value = input(f'{label}: ') or None 65 | return value 66 | 67 | 68 | def get_new_bookmark_data(): 69 | return { 70 | 'タイトル': get_user_input('タイトル'), 71 | 'URL': get_user_input('URL'), 72 | 'メモ': get_user_input('メモ', required=False), 73 | } 74 | 75 | 76 | def get_bookmark_id_for_deletion(): 77 | return get_user_input('削除するブックマークのIDを指定') 78 | 79 | 80 | def get_github_import_options(): 81 | return { 82 | 'github_username': get_user_input('GitHubのユーザー名'), 83 | 'preserve_timestamps': 84 | get_user_input( 85 | 'タイムスタンプを維持しますか [Y/n]', 86 | required=False 87 | ) in {'Y', 'y', None}, 88 | } 89 | 90 | 91 | def get_new_bookmark_info(): 92 | bookmark_id = get_user_input('編集対象のIDを入力してください') 93 | field = get_user_input('編集項目を指定してください(「タイトル」「URL」「メモ」のいずれか)') 94 | new_value = get_user_input(f'{field}の新しい値') 95 | return { 96 | 'id': bookmark_id, 97 | 'update': {field: new_value}, 98 | } 99 | 100 | 101 | def loop(): 102 | clear_screen() 103 | 104 | options = OrderedDict({ 105 | 'A': Option('追加', commands.AddBookmarkCommand(), prep_call=get_new_bookmark_data), 106 | 'B': Option('登録順にリスト', commands.ListBookmarksCommand()), 107 | 'T': Option('タイトル順にリスト', commands.ListBookmarksCommand(order_by='タイトル')), 108 | 'E': Option('編集', commands.EditBookmarkCommand(), prep_call=get_new_bookmark_info), 109 | 'D': Option('削除', commands.DeleteBookmarkCommand(), prep_call=get_bookmark_id_for_deletion), 110 | 'G': Option( 111 | 'GitHubのスターをインポート', 112 | commands.ImportGitHubStarsCommand(), 113 | prep_call=get_github_import_options 114 | ), 115 | 'Q': Option('終了', commands.QuitCommand()), 116 | }) 117 | print_options(options) 118 | 119 | chosen_option = get_option_choice(options) 120 | clear_screen() 121 | chosen_option.choose() 122 | 123 | _ = input('Enterを押すとメニューに戻ります') 124 | 125 | 126 | if __name__ == '__main__': 127 | print("ブックマーク管理アプリ Bark") 128 | commands.CreateBookmarksTableCommand().execute() 129 | 130 | while True: 131 | loop() 132 | -------------------------------------------------------------------------------- /ch07/05bark3/commands.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from datetime import datetime 3 | 4 | import requests 5 | 6 | from database import DatabaseManager 7 | 8 | db = DatabaseManager('bookmarks.db') 9 | 10 | 11 | class CreateBookmarksTableCommand: 12 | def execute(self): 13 | db.create_table('bookmarks', { 14 | 'id': 'integer primary key autoincrement', 15 | 'タイトル': 'text not null', 16 | 'URL': 'text not null', 17 | 'メモ': 'text', 18 | '追加日時': 'text not null', 19 | }) 20 | 21 | 22 | class AddBookmarkCommand: 23 | def execute(self, data, timestamp=None): # <1> 24 | data['追加日時'] = timestamp or datetime.utcnow().isoformat() # <2> 25 | db.add('bookmarks', data) 26 | return 'ブックマークを追加しました。' 27 | 28 | 29 | class ListBookmarksCommand: 30 | def __init__(self, order_by='追加日時'): 31 | self.order_by = order_by 32 | 33 | def execute(self): 34 | return db.select('bookmarks', order_by=self.order_by).fetchall() 35 | 36 | 37 | class DeleteBookmarkCommand: 38 | def execute(self, data): 39 | db.delete('bookmarks', {'id': data}) 40 | return 'ブックマークを削除しました。' 41 | 42 | 43 | class QuitCommand: 44 | def execute(self): 45 | sys.exit() 46 | 47 | 48 | class ImportGitHubStarsCommand: 49 | def _extract_bookmark_info(self, repo): # <1> 50 | return { 51 | 'タイトル': repo['name'], 52 | 'URL': repo['html_url'], 53 | 'メモ': repo['description'], 54 | } 55 | 56 | def execute(self, data): 57 | bookmarks_imported = 0 58 | 59 | github_username = data['github_username'] 60 | next_page_of_results = f'https://api.github.com/users/{github_username}/starred' # <2> 61 | 62 | while next_page_of_results: # <3> 63 | stars_response = requests.get( # <4> 64 | next_page_of_results, 65 | headers={'Accept': 'application/vnd.github.v3.star+json'}, 66 | ) 67 | next_page_of_results = stars_response.links.get('next', {}).get('url') # <5> 68 | 69 | for repo_info in stars_response.json(): 70 | repo = repo_info['repo'] # <6> 71 | 72 | if data['preserve_timestamps']: 73 | timestamp = datetime.strptime( 74 | repo_info['starred_at'], # <7> 75 | '%Y-%m-%dT%H:%M:%SZ' # <8> 76 | ) 77 | else: 78 | timestamp = None 79 | 80 | bookmarks_imported += 1 81 | AddBookmarkCommand().execute( # <9> 82 | self._extract_bookmark_info(repo), 83 | timestamp=timestamp, 84 | ) 85 | 86 | return f'{bookmarks_imported}個のブックマークをインポートしました。' # <10> 87 | 88 | 89 | class EditBookmarkCommand: 90 | def execute(self, data): 91 | db.update( 92 | 'bookmarks', 93 | {'id': data['id']}, 94 | data['update'], 95 | ) 96 | return 'ブックマークを更新しました。' 97 | -------------------------------------------------------------------------------- /ch07/05bark3/database.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | class DatabaseManager: 4 | def __init__(self, database_filename): 5 | self.connection = sqlite3.connect(database_filename) 6 | 7 | def __del__(self): 8 | self.connection.close() 9 | 10 | def _execute(self, statement, values=None): 11 | with self.connection: 12 | cursor = self.connection.cursor() 13 | cursor.execute(statement, values or []) 14 | return cursor 15 | 16 | def create_table(self, table_name, columns): 17 | columns_with_types = [ 18 | f'{column_name} {data_type}' 19 | for column_name, data_type in columns.items() 20 | ] 21 | self._execute( 22 | f''' 23 | CREATE TABLE IF NOT EXISTS {table_name} 24 | ({', '.join(columns_with_types)}); 25 | ''' 26 | ) 27 | 28 | def drop_table(self, table_name): 29 | self._execute(f'DROP TABLE {table_name};') 30 | 31 | def add(self, table_name, data): 32 | placeholders = ', '.join('?' * len(data)) 33 | column_names = ', '.join(data.keys()) 34 | column_values = tuple(data.values()) 35 | 36 | self._execute( 37 | f''' 38 | INSERT INTO {table_name} 39 | ({column_names}) 40 | VALUES ({placeholders}); 41 | ''', 42 | column_values, 43 | ) 44 | 45 | def delete(self, table_name, criteria): 46 | placeholders = [f'{column} = ?' for column in criteria.keys()] 47 | delete_criteria = ' AND '.join(placeholders) 48 | self._execute( 49 | f''' 50 | DELETE FROM {table_name} 51 | WHERE {delete_criteria}; 52 | ''', 53 | tuple(criteria.values()), 54 | ) 55 | 56 | def select(self, table_name, criteria=None, order_by=None): 57 | criteria = criteria or {} 58 | 59 | query = f'SELECT * FROM {table_name}' 60 | 61 | if criteria: 62 | placeholders = [f'{column} = ?' for column in criteria.keys()] 63 | select_criteria = ' AND '.join(placeholders) 64 | query += f' WHERE {select_criteria}' 65 | 66 | if order_by: 67 | query += f' ORDER BY {order_by}' 68 | 69 | return self._execute( 70 | query, 71 | tuple(criteria.values()), 72 | ) 73 | 74 | def update(self, table_name, criteria, data): 75 | update_placeholders = [f'{column} = ?' for column in criteria.keys()] 76 | update_criteria = ' AND '.join(update_placeholders) 77 | 78 | data_placeholders = ', '.join(f'{key} = ?' for key in data.keys()) 79 | 80 | values = tuple(data.values()) + tuple(criteria.values()) 81 | 82 | self._execute( 83 | f''' 84 | UPDATE {table_name} 85 | SET {data_placeholders} 86 | WHERE {update_criteria}; 87 | ''', 88 | values, 89 | ) 90 | 91 | -------------------------------------------------------------------------------- /ch08/01gastropods1/gastropods.py: -------------------------------------------------------------------------------- 1 | class Slug: # ナメクジ 2 | def __init__(self, name): 3 | self.name = name 4 | 5 | def crawl(self): 6 | print(f'{self.name}の這い跡ができました') 7 | 8 | 9 | class Snail(Slug): # <1> # カタツムリ Slug(ナメクジ)を継承 10 | def __init__(self, name, shell_size): # <2> 11 | super().__init__(name) 12 | self.name = name 13 | self.shell_size = shell_size # 殻の大きさ 14 | 15 | 16 | def race(gastropod_one, gastropod_two): 17 | gastropod_one.crawl() 18 | gastropod_two.crawl() 19 | 20 | 21 | race(Slug('小次郎'), Slug('小雪')) # <3> 22 | race(Snail('小次郎'), Snail('小雪')) # <4> 23 | -------------------------------------------------------------------------------- /ch08/02birds/birds.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | class Bird: 3 | def fly(self): 4 | print('飛行中!') 5 | #@@range_end(list1) 6 | 7 | #@@range_begin(list2) 8 | class Hummingbird(Bird): 9 | def fly(self): 10 | print('高速に羽ばたき中!') 11 | #@@range_end(list2) 12 | 13 | #@@range_begin(list3) 14 | class Penguin(Bird): 15 | def fly(self): 16 | print('飛べません!') 17 | #@@range_end(list3) 18 | 19 | a = Bird(); 20 | a.fly() 21 | b = Hummingbird(); 22 | b.fly() 23 | c = Penguin(); 24 | c.fly() 25 | -------------------------------------------------------------------------------- /ch08/03bicycle/bicycle.py: -------------------------------------------------------------------------------- 1 | class Tire: # <1> 2 | def __repr__(self): 3 | return 'ゴムのタイヤ' 4 | 5 | 6 | class Frame: 7 | def __repr__(self): 8 | return 'アルミのフレーム' 9 | 10 | #@@range_begin(list1) 11 | class CarbonFiberFrame: 12 | def __repr__(self): 13 | return 'カーボンファイバーのフレーム' 14 | #@@range_end(list1) 15 | 16 | class Bicycle: 17 | def __init__(self, front_tire, back_tire, frame): # <1> 18 | self.front_tire = front_tire 19 | self.back_tire = back_tire 20 | self.frame = frame 21 | 22 | def print_specs(self): 23 | print(f'フレーム: {self.frame}') 24 | print(f'前のタイヤ: {self.front_tire}。後ろのタイヤ: {self.back_tire}') 25 | 26 | 27 | if __name__ == '__main__': 28 | bike = Bicycle(Tire(), Tire(), Frame()) # <2> 29 | bike.print_specs() 30 | 31 | print("----------") 32 | #@@range_begin(list2) 33 | bike = Bicycle(Tire(), Tire(), CarbonFiberFrame()) # <1> 34 | bike.print_specs() # <2> 35 | #@@range_end(list2) 36 | 37 | print(isinstance(CarbonFiberFrame(), Frame)) 38 | -------------------------------------------------------------------------------- /ch08/04bicycle2/bicycle.py: -------------------------------------------------------------------------------- 1 | class Tire: # <1> 2 | def __repr__(self): 3 | return 'ゴムのタイヤ' 4 | 5 | 6 | class FancyTire(Tire): 7 | def __repr__(self): 8 | return '素敵なタイヤ' 9 | 10 | 11 | class Frame: 12 | def __repr__(self): 13 | return 'アルミのフレーム' 14 | 15 | #@@range_begin(list1) 16 | class CarbonFiberFrame: 17 | def __repr__(self): 18 | return 'カーボンファイバーのフレーム' 19 | #@@range_end(list1) 20 | 21 | class Bicycle: 22 | def __init__(self, front_tire, back_tire, frame): # <1> 23 | self.front_tire = front_tire 24 | self.back_tire = back_tire 25 | self.frame = frame 26 | 27 | def print_specs(self): 28 | print(f'フレーム: {self.frame}') 29 | print(f'前のタイヤ: {self.front_tire}。後ろのタイヤ: {self.back_tire}') 30 | 31 | 32 | if __name__ == '__main__': 33 | bike = Bicycle(Tire(), Tire(), Frame()) # <2> 34 | bike.print_specs() 35 | 36 | print("----------") 37 | #@@range_begin(list2) 38 | bike = Bicycle(Tire(), Tire(), CarbonFiberFrame()) # <1> 39 | bike.print_specs() # <2> 40 | #@@range_end(list2) 41 | 42 | print(isinstance(FancyTire(), Tire)) 43 | print(issubclass(int, int)) 44 | print(issubclass(FancyTire, Tire)) 45 | print(issubclass(dict, float)) 46 | -------------------------------------------------------------------------------- /ch08/05banking/banking.py: -------------------------------------------------------------------------------- 1 | class Teller: # 窓口係 2 | def deposit(self, amount, account): # 預け入れ 3 | account.deposit(amount) 4 | 5 | 6 | class CorruptTeller(Teller): # <1> # 邪悪な窓口係 7 | def __init__(self): 8 | self.coffers = 0 # 金庫 9 | 10 | def deposit(self, amount, account): # <2> 11 | self.coffers += amount * 0.01 # <3> # 少し自分の金庫に入れる 12 | super().deposit(amount * 0.99, account) # <4> 13 | -------------------------------------------------------------------------------- /ch08/06cats1/cats.py: -------------------------------------------------------------------------------- 1 | class BigCat: # 大型猫科動物 2 | def eats(self): # 食べる 3 | return ['齧歯動物'] 4 | 5 | 6 | class Lion(BigCat): # <1> 7 | def eats(self): 8 | return ['ヌー'] 9 | 10 | 11 | class Tiger(BigCat): # <2> 12 | def eats(self): 13 | return ['水牛'] 14 | 15 | 16 | class Liger(Lion, Tiger): # <3> 17 | def eats(self): 18 | return super().eats() + ['兎', '牛', '豚', '鶏'] 19 | 20 | 21 | if __name__ == '__main__': 22 | lion = Lion() 23 | print('ライオンが食べるもの:', lion.eats()) 24 | tiger = Tiger() 25 | print('トラが食べるもの:', tiger.eats()) 26 | liger = Liger() 27 | print('ライガーが食べるもの:', liger.eats()) 28 | -------------------------------------------------------------------------------- /ch08/07cats2/cats.py: -------------------------------------------------------------------------------- 1 | class BigCat: # 大型猫科動物 2 | def eats(self): # 食べる 3 | return ['齧歯動物'] 4 | 5 | 6 | class Lion(BigCat): # <1> 7 | def eats(self): 8 | return super().eats() + ['ヌー'] 9 | 10 | 11 | class Tiger(BigCat): # <2> 12 | def eats(self): 13 | return super().eats() + ['水牛'] 14 | 15 | 16 | class Liger(Lion, Tiger): # <3> 17 | def eats(self): 18 | return super().eats() + ['兎', '牛', '豚', '鶏'] 19 | 20 | 21 | if __name__ == '__main__': 22 | lion = Lion() 23 | print('ライオンが食べるもの:', lion.eats()) 24 | tiger = Tiger() 25 | print('トラが食べるもの:', tiger.eats()) 26 | liger = Liger() 27 | print('ライガーが食べるもの:', liger.eats()) 28 | -------------------------------------------------------------------------------- /ch08/08predators1/predators.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Predator(ABC): # <1> 5 | @abstractmethod # <2> 6 | def eat(self, prey): # <3> 7 | pass # <4> 8 | 9 | 10 | class Bear(Predator): # <5> 11 | def eat(self, prey): # <6> 12 | print(f'熊が{prey}を一撃!') 13 | 14 | 15 | class Owl(Predator): 16 | def eat(self, prey): 17 | print(f'フクロウが{prey}めがけて急降下!') 18 | 19 | 20 | class Chameleon(Predator): 21 | def eat(self, prey): 22 | print(f'カメレオンが舌を伸ばして{prey}をペロリ!') 23 | 24 | 25 | if __name__ == '__main__': 26 | bear = Bear() 27 | bear.eat('シカ') 28 | owl = Owl() 29 | owl.eat('ネズミ') 30 | chameleon = Chameleon() 31 | chameleon.eat('ハエ') 32 | -------------------------------------------------------------------------------- /ch08/09predators2/predators.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Predator(ABC): # <1> 5 | @abstractmethod # <2> 6 | def eat(self, prey): # <3> 7 | pass # <4> 8 | 9 | 10 | class Bear(Predator): # <5> 11 | def eat(self, prey): # <6> 12 | print(f'熊が{prey}を一撃!') 13 | 14 | 15 | class Owl(Predator): 16 | def eat(self, prey): 17 | print(f'フクロウが{prey}めがけて急降下!') 18 | 19 | 20 | class Chameleon(Predator): 21 | def eat(self, prey): 22 | print(f'カメレオンが舌を伸ばして{prey}をペロリ!') 23 | 24 | class Human(Predator): 25 | def talk(self): 26 | print(f'人間が話をした') 27 | 28 | if __name__ == '__main__': 29 | bear = Bear() 30 | bear.eat('シカ') 31 | owl = Owl() 32 | owl.eat('ネズミ') 33 | chameleon = Chameleon() 34 | chameleon.eat('ハエ') 35 | 36 | human = Human() 37 | human.talk() 38 | -------------------------------------------------------------------------------- /ch08/10predators3/predators.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Predator(ABC): # <1> 5 | @abstractmethod # <2> 6 | def eat(self, prey): # <3> 7 | pass # <4> 8 | 9 | 10 | class Bear(Predator): # <5> 11 | def eat(self, prey): # <6> 12 | print(f'熊が{prey}を一撃!') 13 | 14 | def roar(self): 15 | print(f'熊が吠えた!') 16 | 17 | class Owl(Predator): 18 | def eat(self, prey): 19 | print(f'フクロウが{prey}めがけて急降下!') 20 | 21 | 22 | class Chameleon(Predator): 23 | def eat(self, prey): 24 | print(f'カメレオンが舌を伸ばして{prey}をペロリ!') 25 | 26 | 27 | if __name__ == '__main__': 28 | bear = Bear() 29 | bear.eat('シカ') 30 | owl = Owl() 31 | owl.eat('ネズミ') 32 | chameleon = Chameleon() 33 | chameleon.eat('ハエ') 34 | 35 | bear.roar() 36 | -------------------------------------------------------------------------------- /ch08/11predators4/predators.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Predator(ABC): # <1> 5 | @abstractmethod # <2> 6 | def eat(self, prey): # <3> 7 | pass # <4> 8 | def roar(self): 9 | print(f'吠えた') 10 | 11 | 12 | class Bear(Predator): # <5> 13 | def eat(self, prey): # <6> 14 | print(f'熊が{prey}を一撃!') 15 | 16 | # def roar(self): 17 | # print(f'熊が吠えた!') 18 | 19 | class Owl(Predator): 20 | def eat(self, prey): 21 | print(f'フクロウが{prey}めがけて急降下!') 22 | 23 | 24 | class Chameleon(Predator): 25 | def eat(self, prey): 26 | print(f'カメレオンが舌を伸ばして{prey}をペロリ!') 27 | 28 | 29 | if __name__ == '__main__': 30 | bear = Bear() 31 | bear.eat('シカ') 32 | owl = Owl() 33 | owl.eat('ネズミ') 34 | chameleon = Chameleon() 35 | chameleon.eat('ハエ') 36 | 37 | bear.roar() 38 | -------------------------------------------------------------------------------- /ch08/12bark4/bark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | from collections import OrderedDict 5 | 6 | import commands 7 | 8 | 9 | def print_bookmarks(bookmarks): 10 | for bookmark in bookmarks: 11 | print('\t'.join( 12 | str(field) if field else '' 13 | for field in bookmark 14 | )) 15 | 16 | 17 | class Option: 18 | def __init__(self, name, command, prep_call=None): 19 | self.name = name 20 | self.command = command 21 | self.prep_call = prep_call 22 | 23 | def _handle_message(self, message): 24 | if isinstance(message, list): 25 | print_bookmarks(message) 26 | else: 27 | print(message) 28 | 29 | def choose(self): 30 | data = self.prep_call() if self.prep_call else None 31 | message = self.command.execute(data) # <1> 32 | self._handle_message(message) 33 | 34 | def __str__(self): 35 | return self.name 36 | 37 | 38 | def clear_screen(): 39 | clear = 'cls' if os.name == 'nt' else 'clear' 40 | os.system(clear) 41 | 42 | 43 | def print_options(options): 44 | for shortcut, option in options.items(): 45 | print(f'({shortcut}) {option}') 46 | print() 47 | 48 | 49 | def option_choice_is_valid(choice, options): 50 | return choice in options or choice.upper() in options 51 | 52 | 53 | def get_option_choice(options): 54 | choice = input('操作を選択してください: ') 55 | while not option_choice_is_valid(choice, options): 56 | print('A, B, T, E, D, G, Qのいずれかを入力してください(小文字でもOK。ただし半角文字)') 57 | choice = input('操作を選択してください: ') 58 | return options[choice.upper()] 59 | 60 | 61 | def get_user_input(label, required=True): 62 | value = input(f'{label}: ') or None 63 | while required and not value: 64 | value = input(f'{label}: ') or None 65 | return value 66 | 67 | 68 | def get_new_bookmark_data(): 69 | return { 70 | 'タイトル': get_user_input('タイトル'), 71 | 'URL': get_user_input('URL'), 72 | 'メモ': get_user_input('メモ', required=False), 73 | } 74 | 75 | 76 | def get_bookmark_id_for_deletion(): 77 | return get_user_input('削除するブックマークのIDを指定') 78 | 79 | 80 | def get_github_import_options(): 81 | return { 82 | 'github_username': get_user_input('GitHubのユーザー名'), 83 | 'preserve_timestamps': 84 | get_user_input( 85 | 'タイムスタンプを維持しますか [Y/n]', 86 | required=False 87 | ) in {'Y', 'y', None}, 88 | } 89 | 90 | 91 | def get_new_bookmark_info(): 92 | bookmark_id = get_user_input('編集対象のIDを入力してください') 93 | field = get_user_input('編集項目を指定してください(「タイトル」「URL」「メモ」のいずれか)') 94 | new_value = get_user_input(f'{field}の新しい値') 95 | return { 96 | 'id': bookmark_id, 97 | 'update': {field: new_value}, 98 | } 99 | 100 | 101 | def loop(): 102 | clear_screen() 103 | 104 | options = OrderedDict({ 105 | 'A': Option('追加', commands.AddBookmarkCommand(), prep_call=get_new_bookmark_data), 106 | 'B': Option('登録順にリスト', commands.ListBookmarksCommand()), 107 | 'T': Option('タイトル順にリスト', commands.ListBookmarksCommand(order_by='タイトル')), 108 | 'E': Option('編集', commands.EditBookmarkCommand(), prep_call=get_new_bookmark_info), 109 | 'D': Option('削除', commands.DeleteBookmarkCommand(), prep_call=get_bookmark_id_for_deletion), 110 | 'G': Option( 111 | 'GitHubのスターをインポート', 112 | commands.ImportGitHubStarsCommand(), 113 | prep_call=get_github_import_options 114 | ), 115 | 'Q': Option('終了', commands.QuitCommand()), 116 | }) 117 | print_options(options) 118 | 119 | chosen_option = get_option_choice(options) 120 | clear_screen() 121 | chosen_option.choose() 122 | 123 | _ = input('Enterを押すとメニューに戻ります') 124 | 125 | 126 | if __name__ == '__main__': 127 | print("ブックマーク管理アプリ Bark") 128 | commands.CreateBookmarksTableCommand().execute() 129 | 130 | while True: 131 | loop() 132 | -------------------------------------------------------------------------------- /ch08/12bark4/commands.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from abc import ABC, abstractmethod # <1> 3 | from datetime import datetime 4 | 5 | import requests 6 | 7 | from database import DatabaseManager 8 | 9 | db = DatabaseManager('bookmarks.db') 10 | 11 | 12 | class Command(ABC): # <2> 13 | @abstractmethod 14 | def execute(self, data): # <3> 15 | raise NotImplementedError('コマンドは必ずメソッドexecuteを実装してください') 16 | 17 | 18 | class CreateBookmarksTableCommand(Command): # <4> 19 | def execute(self, data=None): # <5> 20 | db.create_table('bookmarks', { 21 | 'id': 'integer primary key autoincrement', 22 | 'タイトル': 'text not null', 23 | 'URL': 'text not null', 24 | 'メモ': 'text', 25 | '追加日時': 'text not null', 26 | }) 27 | 28 | 29 | class AddBookmarkCommand(Command): # <6> 30 | def execute(self, data, timestamp=None): 31 | data['追加日時'] = timestamp or datetime.utcnow().isoformat() 32 | db.add('bookmarks', data) 33 | return 'ブックマークを追加しました。' 34 | 35 | 36 | class ListBookmarksCommand(Command): 37 | def __init__(self, order_by='追加日時'): 38 | self.order_by = order_by 39 | 40 | def execute(self, data=None): 41 | return db.select('bookmarks', order_by=self.order_by).fetchall() 42 | 43 | 44 | class DeleteBookmarkCommand(Command): 45 | def execute(self, data): 46 | db.delete('bookmarks', {'id': data}) 47 | return 'ブックマークを削除しました。' 48 | 49 | 50 | class QuitCommand(Command): 51 | def execute(self, data=None): 52 | sys.exit() 53 | 54 | 55 | class ImportGitHubStarsCommand(Command): 56 | def _extract_bookmark_info(self, repo): 57 | return { 58 | 'タイトル': repo['name'], 59 | 'URL': repo['html_url'], 60 | 'メモ': repo['description'], 61 | } 62 | 63 | #@@range_begin(list1) 64 | def execute(self, data): 65 | bookmarks_imported = 0 66 | 67 | github_username = data['github_username'] 68 | next_page_of_results = f'https://api.github.com/users/{github_username}/starred' 69 | 70 | while next_page_of_results: 71 | stars_response = requests.get( 72 | next_page_of_results, 73 | headers={'Accept': 'application/vnd.github.v3.star+json'}, 74 | ) 75 | next_page_of_results = stars_response.links.get('next', {}).get('url') 76 | 77 | for repo_info in stars_response.json(): 78 | repo = repo_info['repo'] 79 | 80 | if data['preserve_timestamps']: 81 | timestamp = datetime.strptime( 82 | repo_info['starred_at'], 83 | '%Y-%m-%dT%H:%M:%SZ' 84 | ) 85 | else: 86 | timestamp = None 87 | 88 | bookmarks_imported += 1 89 | AddBookmarkCommand().execute( 90 | self._extract_bookmark_info(repo), 91 | timestamp=timestamp, 92 | ) 93 | 94 | return f'{bookmarks_imported}個のブックマークをインポートしました。' 95 | #@@range_end(list1) 96 | 97 | class EditBookmarkCommand(Command): 98 | def execute(self, data): 99 | db.update( 100 | 'bookmarks', 101 | {'id': data['id']}, 102 | data['update'], 103 | ) 104 | return 'ブックマークを更新しました。' 105 | -------------------------------------------------------------------------------- /ch08/12bark4/database.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | class DatabaseManager: 4 | def __init__(self, database_filename): 5 | self.connection = sqlite3.connect(database_filename) 6 | 7 | def __del__(self): 8 | self.connection.close() 9 | 10 | def _execute(self, statement, values=None): 11 | with self.connection: 12 | cursor = self.connection.cursor() 13 | cursor.execute(statement, values or []) 14 | return cursor 15 | 16 | def create_table(self, table_name, columns): 17 | columns_with_types = [ 18 | f'{column_name} {data_type}' 19 | for column_name, data_type in columns.items() 20 | ] 21 | self._execute( 22 | f''' 23 | CREATE TABLE IF NOT EXISTS {table_name} 24 | ({', '.join(columns_with_types)}); 25 | ''' 26 | ) 27 | 28 | def drop_table(self, table_name): 29 | self._execute(f'DROP TABLE {table_name};') 30 | 31 | def add(self, table_name, data): 32 | placeholders = ', '.join('?' * len(data)) 33 | column_names = ', '.join(data.keys()) 34 | column_values = tuple(data.values()) 35 | 36 | self._execute( 37 | f''' 38 | INSERT INTO {table_name} 39 | ({column_names}) 40 | VALUES ({placeholders}); 41 | ''', 42 | column_values, 43 | ) 44 | 45 | def delete(self, table_name, criteria): 46 | placeholders = [f'{column} = ?' for column in criteria.keys()] 47 | delete_criteria = ' AND '.join(placeholders) 48 | self._execute( 49 | f''' 50 | DELETE FROM {table_name} 51 | WHERE {delete_criteria}; 52 | ''', 53 | tuple(criteria.values()), 54 | ) 55 | 56 | def select(self, table_name, criteria=None, order_by=None): 57 | criteria = criteria or {} 58 | 59 | query = f'SELECT * FROM {table_name}' 60 | 61 | if criteria: 62 | placeholders = [f'{column} = ?' for column in criteria.keys()] 63 | select_criteria = ' AND '.join(placeholders) 64 | query += f' WHERE {select_criteria}' 65 | 66 | if order_by: 67 | query += f' ORDER BY {order_by}' 68 | 69 | return self._execute( 70 | query, 71 | tuple(criteria.values()), 72 | ) 73 | 74 | def update(self, table_name, criteria, data): 75 | update_placeholders = [f'{column} = ?' for column in criteria.keys()] 76 | update_criteria = ' AND '.join(update_placeholders) 77 | 78 | data_placeholders = ', '.join(f'{key} = ?' for key in data.keys()) 79 | 80 | values = tuple(data.values()) + tuple(criteria.values()) 81 | 82 | self._execute( 83 | f''' 84 | UPDATE {table_name} 85 | SET {data_placeholders} 86 | WHERE {update_criteria}; 87 | ''', 88 | values, 89 | ) 90 | 91 | -------------------------------------------------------------------------------- /ch09/01complexity/complexity.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | def has_long_words(sentence): 3 | if isinstance(sentence, str): # <1> 4 | sentence = sentence.split(' ') 5 | 6 | for word in sentence: # <2> 7 | if len(word) > 10: # <3> 8 | return True 9 | 10 | return False # <4> 11 | #@@range_end(list1) 12 | 13 | print(has_long_words("This is a nice program.")) 14 | print(has_long_words("I love hippopotamuses very much.")) 15 | print(has_long_words(["I", "love", "hippopotamuses", "very", "much", "."])) 16 | print(has_long_words(["I", "love", "cats", "very", "much", "."])) 17 | -------------------------------------------------------------------------------- /ch09/02configuration1/configuration.py: -------------------------------------------------------------------------------- 1 | import json 2 | #@@range_begin(list1) 3 | import random 4 | 5 | FOODS = [ # <1> 6 | 'ピザ', 7 | 'ハンバーガー', 8 | 'サラダ', 9 | 'スープ', 10 | ] 11 | 12 | 13 | def random_food(request): # <2> 14 | return random.choice(FOODS) # <3> 15 | #@@range_end(list1) 16 | 17 | 18 | print(random_food(None)) 19 | -------------------------------------------------------------------------------- /ch09/03configuration2/configuration.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | 4 | FOODS = [ 5 | 'ピザ', 6 | 'ハンバーガー', 7 | 'サラダ', 8 | 'スープ', 9 | ] 10 | 11 | #@@range_begin(list1) 12 | def random_food(request): 13 | food = random.choice(FOODS) # <1> 14 | 15 | if request.headers.get('Accept') == 'application/json': # <2> 16 | return json.dumps({'food': food}) 17 | else: 18 | return food # <3> 19 | #@@range_end(list1) 20 | -------------------------------------------------------------------------------- /ch09/04configuration3/configuration.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | 4 | FOODS = [ 5 | 'ピザ', 6 | 'ハンバーガー', 7 | 'サラダ', 8 | 'スープ', 9 | ] 10 | 11 | #@@range_begin(list1) 12 | def random_food(request): 13 | food = random.choice(FOODS) 14 | 15 | if request.headers.get('Accept') == 'application/json': 16 | return json.dumps({'food': food}) 17 | elif request.headers.get('Accept') == 'application/xml': # <1> 18 | return f'{food}' 19 | else: 20 | return food 21 | #@@range_end(list1) 22 | -------------------------------------------------------------------------------- /ch09/05configuration4/configuration.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | 4 | FOODS = [ 5 | 'ピザ', 6 | 'ハンバーガー', 7 | 'サラダ', 8 | 'スープ', 9 | ] 10 | 11 | 12 | #@@range_begin(list1) 13 | def random_food(request): 14 | food = random.choice(FOODS) 15 | 16 | formats = { # <1> 17 | 'application/json': json.dumps({'food': food}), 18 | 'application/xml': f'{food}', 19 | } 20 | 21 | return formats.get(request.headers.get('Accept'), food) # <2> 22 | #@@range_end(list1) 23 | -------------------------------------------------------------------------------- /ch09/06configuration5/configuration.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | 4 | FOODS = [ 5 | 'ピザ', 6 | 'ハンバーガー', 7 | 'サラダ', 8 | 'スープ', 9 | ] 10 | 11 | 12 | #@@range_begin(list1) 13 | def to_json(food): # <1> 14 | return json.dumps({'food': food}) 15 | 16 | 17 | def to_xml(food): 18 | return f'{food}' 19 | 20 | 21 | def random_food(request): 22 | food = random.choice(FOODS) 23 | 24 | formats = { # <2> 25 | 'application/json': to_json, 26 | 'application/xml': to_xml, 27 | } 28 | 29 | format_function = formats.get( # <3> 30 | request.headers.get('Accept'), 31 | lambda val: val # <4> 32 | ) 33 | return format_function(food) # <5> 34 | #@@range_end(list1) 35 | -------------------------------------------------------------------------------- /ch09/07configuration6/configuration.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | 4 | FOODS = [ 5 | 'ピザ', 6 | 'ハンバーガー', 7 | 'サラダ', 8 | 'スープ', 9 | ] 10 | 11 | 12 | 13 | def to_json(food): # <1> 14 | return json.dumps({'food': food}) 15 | 16 | 17 | def to_xml(food): 18 | return f'{food}' 19 | 20 | 21 | #@@range_begin(list1) 22 | def get_format_function(accept=None): # <1> 23 | formats = { 24 | 'application/json': to_json, 25 | 'application/xml': to_xml, 26 | } 27 | 28 | return formats.get(accept, lambda val: val) 29 | 30 | 31 | def random_food(request): # <2> 32 | food = random.choice(FOODS) 33 | format_function = get_format_function(request.headers.get('Accept')) # <3> 34 | return format_function(food) 35 | #@@range_end(list1) 36 | -------------------------------------------------------------------------------- /ch09/08book1/book.py: -------------------------------------------------------------------------------- 1 | class Book: 2 | def __init__(self, data): 3 | self.title = data['title'] # <1> 4 | self.subtitle = data['subtitle'] 5 | 6 | # 表示用タイトル(display_title)の決定 7 | if self.title and self.subtitle: # <2> 8 | # サブタイトルが指定されているときは、メインのタイトルの後ろに付加する 9 | self.display_title = f'{self.title}: {self.subtitle}' 10 | elif self.title: 11 | # タイトルのみが指定されているときはそれを表示用タイトルにする 12 | self.display_title = self.title 13 | else: 14 | # どちらも指定されていない場合はUntitled(無題)とする 15 | self.display_title = 'Untitled' 16 | -------------------------------------------------------------------------------- /ch09/09book2/book.py: -------------------------------------------------------------------------------- 1 | class Book: 2 | def __init__(self, data): 3 | self.title = data['title'] 4 | self.subtitle = data['subtitle'] 5 | self.set_display_title() # <1> 6 | 7 | def set_display_title(self): # <2> 8 | if self.title and self.subtitle: 9 | self.display_title = f'{self.title}: {self.subtitle}' 10 | elif self.title: 11 | self.display_title = self.title 12 | else: 13 | self.display_title = 'Untitled' 14 | -------------------------------------------------------------------------------- /ch09/10book3/book.py: -------------------------------------------------------------------------------- 1 | class Book: 2 | def __init__(self, data): 3 | self.title = data['title'] 4 | self.subtitle = data['subtitle'] 5 | 6 | @property 7 | def display_title(self): # <1> 8 | if self.title and self.subtitle: 9 | return f'{self.title}: {self.subtitle}' 10 | elif self.title: 11 | return self.title 12 | else: 13 | return 'Untitled' 14 | -------------------------------------------------------------------------------- /ch09/11book4/book.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | class Book: 3 | def __init__(self, data): 4 | self.title = data['title'] 5 | self.subtitle = data['subtitle'] 6 | self.author_data = data['author'] # <1> 7 | 8 | @property 9 | def author_for_display(self): # <2> 10 | return f'{self.author_data["first_name"]} {self.author_data["last_name"]}' 11 | 12 | @property 13 | def author_for_citation(self): # <3> 14 | return f'{self.author_data["last_name"]}, {self.author_data["first_name"][0]}.' 15 | #@@range_end(list1) 16 | 17 | 18 | #@@range_begin(list2) 19 | book = Book({ 20 | 'title': 'Brillo-iant', 21 | 'subtitle': 'The pad that changed everything', 22 | 'author': { 23 | 'first_name': 'Rusty', 24 | 'last_name': 'Potts', 25 | } 26 | }) 27 | 28 | 29 | print(book.author_for_display) # ウェブ表示用 30 | print(book.author_for_citation) # 論文参考文献用 31 | #@@range_end(list2) 32 | -------------------------------------------------------------------------------- /ch09/12book5/book.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | class Author: 3 | def __init__(self, author_data): # <1> 4 | self.first_name = author_data['first_name'] 5 | self.last_name = author_data['last_name'] 6 | 7 | @property 8 | def for_display(self): # <2> 9 | return f'{self.first_name} {self.last_name}' 10 | 11 | @property 12 | def for_citation(self): 13 | return f'{self.last_name}, {self.first_name[0]}.' 14 | 15 | 16 | class Book: 17 | def __init__(self, data): 18 | self.title = data['title'] 19 | self.subtitle = data['subtitle'] 20 | self.author_data = data['author'] # <3> 21 | self.author = Author(self.author_data) # <4> 22 | 23 | @property 24 | def author_for_display(self): # <5> 25 | return self.author.for_display 26 | 27 | @property 28 | def author_for_citation(self): 29 | return self.author.for_citation 30 | #@@range_end(list1) 31 | 32 | 33 | #@@range_begin(list2) 34 | book = Book({ 35 | 'title': 'Brillo-iant', 36 | 'subtitle': 'The pad that changed everything', 37 | 'author': { 38 | 'first_name': 'Rusty', 39 | 'last_name': 'Potts', 40 | } 41 | }) 42 | 43 | 44 | print(book.author_for_display) 45 | print(book.author_for_citation) 46 | #@@range_end(list2) 47 | -------------------------------------------------------------------------------- /ch09/13book6/book.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | #@@range_begin(list1) 4 | class Author: 5 | def __init__(self, author_data): # <1> 6 | self.first_name = author_data['first_name'] 7 | self.last_name = author_data['last_name'] 8 | 9 | @property 10 | def for_display(self): # <2> 11 | return f'{self.first_name} {self.last_name}' 12 | 13 | @property 14 | def for_citation(self): 15 | return f'{self.last_name}, {self.first_name[0]}.' 16 | 17 | 18 | class Book: 19 | def __init__(self, data): 20 | self.title = data['title'] 21 | self.subtitle = data['subtitle'] 22 | self.author_data = data['author'] # <3> 23 | self.author = Author(self.author_data) # <4> 24 | 25 | @property 26 | def author_for_display(self): # <5> 27 | warnings.warn('book.author.for_displayを利用してください', DeprecationWarning) 28 | return self.author.for_display 29 | 30 | @property 31 | def author_for_citation(self): 32 | warnings.warn('book.author.for_citationを利用してください', DeprecationWarning) 33 | return self.author.for_citation 34 | #@@range_end(list1) 35 | 36 | 37 | #@@range_begin(list2) 38 | book = Book({ 39 | 'title': 'Brillo-iant', 40 | 'subtitle': 'The pad that changed everything', 41 | 'author': { 42 | 'first_name': 'Rusty', 43 | 'last_name': 'Potts', 44 | } 45 | }) 46 | 47 | 48 | print(book.author_for_display) 49 | print(book.author_for_citation) 50 | #@@range_end(list2) 51 | -------------------------------------------------------------------------------- /ch10/01book1/book.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | class Book: 3 | def __init__(self, title, subtitle, author): # <1> 4 | self.title = title 5 | self.subtitle = subtitle 6 | self.author = author 7 | 8 | 9 | def display_book_info(book): 10 | print(f'{book.author}著『{book.title} ── {book.subtitle}』') # <2> 11 | #@@range_end(list1) 12 | 13 | 14 | book = Book(author='デイン・ディラード', title='Pythonプロフェッショナル・プログラミング', subtitle='一流のPythonプログラマーへのパスポート') 15 | display_book_info(book) 16 | 17 | -------------------------------------------------------------------------------- /ch10/02book2/book.py: -------------------------------------------------------------------------------- 1 | #@@range_begin(list1) 2 | class Book: 3 | def __init__(self, title, subtitle, author): 4 | self.title = title 5 | self.subtitle = subtitle 6 | self.author = author 7 | 8 | def display_info(self): # <1> 9 | print(f'{self.author}著『{self.title} ── {self.subtitle}』') # <2> 10 | #@@range_end(list1) 11 | 12 | 13 | book = Book(author='デイン・ディラード', title='Pythonプロフェッショナル・プログラミング', subtitle='一流のPythonプログラマーへのパスポート') 14 | book.display_info() 15 | 16 | -------------------------------------------------------------------------------- /ch10/03search1/search.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def remove_spaces(query): # <1> 5 | query = query.strip() 6 | query = re.sub(r'\s+', ' ', query) 7 | return query 8 | 9 | 10 | def normalize(query): # <2> 11 | query = query.casefold() 12 | return query 13 | 14 | 15 | if __name__ == '__main__': 16 | search_query = input('検索文字列を入れてください:') # <3> 17 | search_query = remove_spaces(search_query) # <4> 18 | search_query = normalize(search_query) 19 | print(f'次の文字列を検索します:"{search_query}"') # <5> 20 | -------------------------------------------------------------------------------- /ch10/04search2/search.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def remove_spaces(query): 5 | query = query.strip() 6 | query = re.sub(r'\s+', ' ', query) 7 | return query 8 | 9 | 10 | def normalize(query): 11 | query = query.casefold() 12 | return query 13 | 14 | #@@range_begin(list1) 15 | def remove_quotes(query): # <1> 16 | query = re.sub(r'"', '', query) 17 | return query 18 | 19 | 20 | #@@range_begin(list2) 21 | if __name__ == '__main__': 22 | search_query = input('検索文字列を入れてください:') 23 | search_query = remove_spaces(search_query) 24 | search_query = remove_quotes(search_query) # <2> 25 | search_query = normalize(search_query) 26 | print(f'次の文字列を検索します:"{search_query}"') 27 | #@@range_end(list2) 28 | #@@range_end(list1) 29 | -------------------------------------------------------------------------------- /ch10/05search3/search.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def remove_spaces(query): 5 | query = query.strip() 6 | query = re.sub(r'\s+', ' ', query) 7 | return query 8 | 9 | 10 | def normalize(query): 11 | query = query.casefold() 12 | return query 13 | 14 | #@@range_begin(list1) 15 | def remove_quotes(query): 16 | query = re.sub(r'"', '', query) 17 | return query 18 | 19 | 20 | if __name__ == '__main__': 21 | search_query = input('検索文字列を入れてください:') 22 | search_query = remove_spaces(search_query) # <1> 23 | search_query = remove_quotes(search_query) # <2> 24 | search_query = normalize(search_query) # <3> 25 | print(f'次の文字列を検索します:"{search_query}"') 26 | 27 | #@@range_end(list1) 28 | -------------------------------------------------------------------------------- /ch10/06search4/search.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def _remove_spaces(query): # <1> 5 | query = query.strip() 6 | query = re.sub(r'\s+', ' ', query) 7 | return query 8 | 9 | 10 | def _normalize(query): 11 | query = query.casefold() 12 | return query 13 | 14 | 15 | def _remove_quotes(query): 16 | query = re.sub(r'"', '', query) 17 | return query 18 | 19 | 20 | def clean_query(query): # <2> 21 | query = _remove_spaces(query) 22 | query = _remove_quotes(query) 23 | query = _normalize(query) 24 | return query 25 | 26 | 27 | if __name__ == '__main__': 28 | search_query = input('検索文字列を入れてください:') 29 | search_query = clean_query(search_query) # <3> 30 | print(f'次の文字列を検索します:"{search_query}"') 31 | -------------------------------------------------------------------------------- /ch10/07bark5/bark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | from collections import OrderedDict 5 | 6 | import commands 7 | 8 | 9 | def print_bookmarks(bookmarks): 10 | for bookmark in bookmarks: 11 | print('\t'.join( 12 | str(field) if field else '' 13 | for field in bookmark 14 | )) 15 | 16 | 17 | class Option: 18 | def __init__(self, name, command, prep_call=None): 19 | self.name = name 20 | self.command = command 21 | self.prep_call = prep_call 22 | 23 | def _handle_message(self, message): 24 | if isinstance(message, list): 25 | print_bookmarks(message) 26 | else: 27 | print(message) 28 | 29 | def choose(self): 30 | data = self.prep_call() if self.prep_call else None 31 | message = self.command.execute(data) # <1> 32 | self._handle_message(message) 33 | 34 | def __str__(self): 35 | return self.name 36 | 37 | 38 | def clear_screen(): 39 | clear = 'cls' if os.name == 'nt' else 'clear' 40 | os.system(clear) 41 | 42 | 43 | def print_options(options): 44 | for shortcut, option in options.items(): 45 | print(f'({shortcut}) {option}') 46 | print() 47 | 48 | 49 | def option_choice_is_valid(choice, options): 50 | return choice in options or choice.upper() in options 51 | 52 | 53 | def get_option_choice(options): 54 | choice = input('操作を選択してください: ') 55 | while not option_choice_is_valid(choice, options): 56 | print('A, B, T, E, D, G, Qのいずれかを入力してください(小文字でもOK。ただし半角文字)') 57 | choice = input('操作を選択してください: ') 58 | return options[choice.upper()] 59 | 60 | 61 | def get_user_input(label, required=True): 62 | value = input(f'{label}: ') or None 63 | while required and not value: 64 | value = input(f'{label}: ') or None 65 | return value 66 | 67 | 68 | def get_new_bookmark_data(): 69 | return { 70 | 'タイトル': get_user_input('タイトル'), 71 | 'URL': get_user_input('URL'), 72 | 'メモ': get_user_input('メモ', required=False), 73 | } 74 | 75 | 76 | def get_bookmark_id_for_deletion(): 77 | return get_user_input('削除するブックマークのIDを指定') 78 | 79 | 80 | def get_github_import_options(): 81 | return { 82 | 'github_username': get_user_input('GitHubのユーザー名'), 83 | 'preserve_timestamps': 84 | get_user_input( 85 | 'タイムスタンプを維持しますか [Y/n]', 86 | required=False 87 | ) in {'Y', 'y', None}, 88 | } 89 | 90 | 91 | def get_new_bookmark_info(): 92 | bookmark_id = get_user_input('編集対象のIDを入力してください') 93 | field = get_user_input('編集項目を指定してください(「タイトル」「URL」「メモ」のいずれか)') 94 | new_value = get_user_input(f'{field}の新しい値') 95 | return { 96 | 'id': bookmark_id, 97 | 'update': {field: new_value}, 98 | } 99 | 100 | 101 | def loop(): 102 | clear_screen() 103 | 104 | options = OrderedDict({ 105 | 'A': Option('追加', commands.AddBookmarkCommand(), prep_call=get_new_bookmark_data), 106 | 'B': Option('登録順にリスト', commands.ListBookmarksCommand()), 107 | 'T': Option('タイトル順にリスト', commands.ListBookmarksCommand(order_by='タイトル')), 108 | 'E': Option('編集', commands.EditBookmarkCommand(), prep_call=get_new_bookmark_info), 109 | 'D': Option('削除', commands.DeleteBookmarkCommand(), prep_call=get_bookmark_id_for_deletion), 110 | 'G': Option( 111 | 'GitHubのスターをインポート', 112 | commands.ImportGitHubStarsCommand(), 113 | prep_call=get_github_import_options 114 | ), 115 | 'Q': Option('終了', commands.QuitCommand()), 116 | }) 117 | print_options(options) 118 | 119 | chosen_option = get_option_choice(options) 120 | clear_screen() 121 | chosen_option.choose() 122 | 123 | _ = input('Enterを押すとメニューに戻ります') 124 | 125 | 126 | if __name__ == '__main__': 127 | print("ブックマーク管理アプリ Bark") 128 | commands.CreateBookmarksTableCommand().execute() 129 | 130 | while True: 131 | loop() 132 | -------------------------------------------------------------------------------- /ch10/07bark5/commands.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from abc import ABC, abstractmethod 3 | from datetime import datetime 4 | 5 | import requests 6 | 7 | from database import DatabaseManager 8 | 9 | db = DatabaseManager('bookmarks.db') 10 | 11 | 12 | class Command(ABC): 13 | @abstractmethod 14 | def execute(self, data): 15 | raise NotImplementedError('コマンドは必ずメソッドexecuteを実装してください') 16 | 17 | 18 | class CreateBookmarksTableCommand(Command): 19 | def execute(self, data=None): 20 | db.create_table('bookmarks', { 21 | 'id': 'integer primary key autoincrement', 22 | 'タイトル': 'text not null', 23 | 'URL': 'text not null', 24 | 'メモ': 'text', 25 | '追加日時': 'text not null', 26 | }) 27 | 28 | 29 | #@@range_begin(list1) 30 | class AddBookmarkCommand(Command): 31 | def execute(self, data, timestamp=None): # <1> 32 | data['追加日時'] = timestamp or datetime.utcnow().isoformat() # <2> 33 | db.add('bookmarks', data) # <3> 34 | return 'ブックマークを追加しました。' # <4> 35 | #@@range_end(list1) 36 | 37 | 38 | class ListBookmarksCommand(Command): 39 | def __init__(self, order_by='追加日時'): 40 | self.order_by = order_by 41 | 42 | def execute(self, data=None): 43 | return db.select('bookmarks', order_by=self.order_by).fetchall() 44 | 45 | 46 | class DeleteBookmarkCommand(Command): 47 | def execute(self, data): 48 | db.delete('bookmarks', {'id': data}) 49 | return 'ブックマークを削除しました。' 50 | 51 | 52 | class QuitCommand(Command): 53 | def execute(self, data=None): 54 | sys.exit() 55 | 56 | 57 | class ImportGitHubStarsCommand(Command): 58 | def _extract_bookmark_info(self, repo): 59 | return { 60 | 'タイトル': repo['name'], 61 | 'URL': repo['html_url'], 62 | 'メモ': repo['description'], 63 | } 64 | 65 | def execute(self, data): 66 | bookmarks_imported = 0 67 | 68 | github_username = data['github_username'] 69 | next_page_of_results = f'https://api.github.com/users/{github_username}/starred' 70 | 71 | while next_page_of_results: 72 | stars_response = requests.get( 73 | next_page_of_results, 74 | headers={'Accept': 'application/vnd.github.v3.star+json'}, 75 | ) 76 | next_page_of_results = stars_response.links.get('next', {}).get('url') 77 | 78 | for repo_info in stars_response.json(): 79 | repo = repo_info['repo'] 80 | 81 | if data['preserve_timestamps']: 82 | timestamp = datetime.strptime( 83 | repo_info['starred_at'], 84 | '%Y-%m-%dT%H:%M:%SZ' 85 | ) 86 | else: 87 | timestamp = None 88 | 89 | bookmarks_imported += 1 90 | AddBookmarkCommand().execute( 91 | self._extract_bookmark_info(repo), 92 | timestamp=timestamp, 93 | ) 94 | 95 | return f'{bookmarks_imported}個のブックマークをインポートしました。' 96 | 97 | 98 | class EditBookmarkCommand(Command): 99 | def execute(self, data): 100 | db.update( 101 | 'bookmarks', 102 | {'id': data['id']}, 103 | data['update'], 104 | ) 105 | return 'ブックマークを更新しました。' 106 | -------------------------------------------------------------------------------- /ch10/07bark5/database.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | class DatabaseManager: 4 | def __init__(self, database_filename): 5 | self.connection = sqlite3.connect(database_filename) 6 | 7 | def __del__(self): 8 | self.connection.close() 9 | 10 | def _execute(self, statement, values=None): 11 | with self.connection: 12 | cursor = self.connection.cursor() 13 | cursor.execute(statement, values or []) 14 | return cursor 15 | 16 | def create_table(self, table_name, columns): 17 | columns_with_types = [ 18 | f'{column_name} {data_type}' 19 | for column_name, data_type in columns.items() 20 | ] 21 | self._execute( 22 | f''' 23 | CREATE TABLE IF NOT EXISTS {table_name} 24 | ({', '.join(columns_with_types)}); 25 | ''' 26 | ) 27 | 28 | def drop_table(self, table_name): 29 | self._execute(f'DROP TABLE {table_name};') 30 | 31 | def add(self, table_name, data): 32 | placeholders = ', '.join('?' * len(data)) 33 | column_names = ', '.join(data.keys()) 34 | column_values = tuple(data.values()) 35 | 36 | self._execute( 37 | f''' 38 | INSERT INTO {table_name} 39 | ({column_names}) 40 | VALUES ({placeholders}); 41 | ''', 42 | column_values, 43 | ) 44 | 45 | def delete(self, table_name, criteria): 46 | placeholders = [f'{column} = ?' for column in criteria.keys()] 47 | delete_criteria = ' AND '.join(placeholders) 48 | self._execute( 49 | f''' 50 | DELETE FROM {table_name} 51 | WHERE {delete_criteria}; 52 | ''', 53 | tuple(criteria.values()), 54 | ) 55 | 56 | def select(self, table_name, criteria=None, order_by=None): 57 | criteria = criteria or {} 58 | 59 | query = f'SELECT * FROM {table_name}' 60 | 61 | if criteria: 62 | placeholders = [f'{column} = ?' for column in criteria.keys()] 63 | select_criteria = ' AND '.join(placeholders) 64 | query += f' WHERE {select_criteria}' 65 | 66 | if order_by: 67 | query += f' ORDER BY {order_by}' 68 | 69 | return self._execute( 70 | query, 71 | tuple(criteria.values()), 72 | ) 73 | 74 | def update(self, table_name, criteria, data): 75 | update_placeholders = [f'{column} = ?' for column in criteria.keys()] 76 | update_criteria = ' AND '.join(update_placeholders) 77 | 78 | data_placeholders = ', '.join(f'{key} = ?' for key in data.keys()) 79 | 80 | values = tuple(data.values()) + tuple(criteria.values()) 81 | 82 | self._execute( 83 | f''' 84 | UPDATE {table_name} 85 | SET {data_placeholders} 86 | WHERE {update_criteria}; 87 | ''', 88 | values, 89 | ) 90 | 91 | -------------------------------------------------------------------------------- /ch10/08bark6/bark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | from collections import OrderedDict 5 | 6 | import commands 7 | 8 | #@@range_begin(list1) 9 | def format_bookmark(bookmark): 10 | return '\t'.join( 11 | str(field) if field else '' 12 | for field in bookmark 13 | ) 14 | 15 | 16 | class Option: 17 | def __init__(self, name, command, prep_call=None, success_message='{result}'): # <1> 18 | self.name = name 19 | self.command = command 20 | self.prep_call = prep_call 21 | self.success_message = success_message # <2> 22 | 23 | def _handle_message(self, message): 24 | if isinstance(message, list): 25 | print_bookmarks(message) 26 | else: 27 | print(message) 28 | 29 | def choose(self): 30 | data = self.prep_call() if self.prep_call else None 31 | success, result = self.command.execute(data) # <3> 32 | 33 | formatted_result = '' 34 | 35 | if isinstance(result, list): # <4> 36 | for bookmark in result: 37 | formatted_result += '\n' + format_bookmark(bookmark) 38 | else: 39 | formatted_result = result 40 | 41 | if success: 42 | print(self.success_message.format(result=formatted_result)) # <5> 43 | 44 | def __str__(self): 45 | return self.name 46 | #@@range_end(list1) 47 | 48 | 49 | def clear_screen(): 50 | clear = 'cls' if os.name == 'nt' else 'clear' 51 | os.system(clear) 52 | 53 | 54 | def print_options(options): 55 | for shortcut, option in options.items(): 56 | print(f'({shortcut}) {option}') 57 | print() 58 | 59 | 60 | def option_choice_is_valid(choice, options): 61 | return choice in options or choice.upper() in options 62 | 63 | 64 | def get_option_choice(options): 65 | choice = input('操作を選択してください: ') 66 | while not option_choice_is_valid(choice, options): 67 | print('A, B, T, E, D, G, Qのいずれかを入力してください(小文字でもOK。ただし半角文字)') 68 | choice = input('操作を選択してください: ') 69 | return options[choice.upper()] 70 | 71 | 72 | def get_user_input(label, required=True): 73 | value = input(f'{label}: ') or None 74 | while required and not value: 75 | value = input(f'{label}: ') or None 76 | return value 77 | 78 | 79 | def get_new_bookmark_data(): 80 | return { 81 | 'タイトル': get_user_input('タイトル'), 82 | 'URL': get_user_input('URL'), 83 | 'メモ': get_user_input('メモ', required=False), 84 | } 85 | 86 | 87 | def get_bookmark_id_for_deletion(): 88 | return get_user_input('削除するブックマークのIDを指定') 89 | 90 | 91 | def get_github_import_options(): 92 | return { 93 | 'github_username': get_user_input('GitHubのユーザー名'), 94 | 'preserve_timestamps': 95 | get_user_input( 96 | 'タイムスタンプを維持しますか [Y/n]', 97 | required=False 98 | ) in {'Y', 'y', None}, 99 | } 100 | 101 | 102 | def get_new_bookmark_info(): 103 | bookmark_id = get_user_input('編集対象のIDを入力してください') 104 | field = get_user_input('編集項目を指定してください(「タイトル」「URL」「メモ」のいずれか)') 105 | new_value = get_user_input(f'{field}の新しい値') 106 | return { 107 | 'id': bookmark_id, 108 | 'update': {field: new_value}, 109 | } 110 | 111 | 112 | 113 | def loop(): 114 | clear_screen() 115 | 116 | #@@range_begin(list2) 117 | options = OrderedDict({ 118 | 'A': Option( 119 | '追加', 120 | commands.AddBookmarkCommand(), 121 | prep_call=get_new_bookmark_data, 122 | success_message='ブックマークを追加しました。', # <6> 123 | ), 124 | 'B': Option( 125 | '登録順にリスト', 126 | commands.ListBookmarksCommand(), # <7> 127 | ), 128 | 'T': Option( 129 | 'タイトル順にリスト', 130 | commands.ListBookmarksCommand(order_by='タイトル'), 131 | ), 132 | 'E': Option( 133 | '編集', 134 | commands.EditBookmarkCommand(), 135 | prep_call=get_new_bookmark_info, 136 | success_message='ブックマークを更新しました。' 137 | ), 138 | 'D': Option( 139 | '削除', 140 | commands.DeleteBookmarkCommand(), 141 | prep_call=get_bookmark_id_for_deletion, 142 | success_message='ブックマークを削除しました。', 143 | ), 144 | 'G': Option( 145 | 'GitHubのスターをインポート', 146 | commands.ImportGitHubStarsCommand(), 147 | prep_call=get_github_import_options, 148 | success_message='{result}個のブックマークをインポートしました。', # <8> 149 | ), 150 | 'Q': Option( 151 | '終了', 152 | commands.QuitCommand() 153 | ), 154 | }) 155 | #@@range_end(list2) 156 | print_options(options) 157 | 158 | chosen_option = get_option_choice(options) 159 | clear_screen() 160 | chosen_option.choose() 161 | 162 | _ = input('Enterを押すとメニューに戻ります') 163 | 164 | 165 | if __name__ == '__main__': 166 | print("ブックマーク管理アプリ Bark") 167 | commands.CreateBookmarksTableCommand().execute() 168 | 169 | while True: 170 | loop() 171 | -------------------------------------------------------------------------------- /ch10/08bark6/commands.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from abc import ABC, abstractmethod 3 | from datetime import datetime 4 | 5 | import requests 6 | 7 | from database import DatabaseManager 8 | 9 | db = DatabaseManager('bookmarks.db') 10 | 11 | 12 | class Command(ABC): 13 | @abstractmethod 14 | def execute(self, data): 15 | raise NotImplementedError('コマンドは必ずメソッドexecuteを実装してください') 16 | 17 | 18 | class CreateBookmarksTableCommand(Command): 19 | def execute(self, data=None): 20 | db.create_table('bookmarks', { 21 | 'id': 'integer primary key autoincrement', 22 | 'タイトル': 'text not null', 23 | 'URL': 'text not null', 24 | 'メモ': 'text', 25 | '追加日時': 'text not null', 26 | }) 27 | 28 | 29 | #@@range_begin(list1) 30 | class AddBookmarkCommand(Command): # <1> 31 | def execute(self, data, timestamp=None): 32 | data['追加日時'] = timestamp or datetime.utcnow().isoformat() 33 | db.add('bookmarks', data) 34 | return True, None # <2> 35 | 36 | 37 | class ListBookmarksCommand(Command): # <3> 38 | def __init__(self, order_by='追加日時'): 39 | self.order_by = order_by 40 | 41 | def execute(self, data=None): 42 | return True, db.select('bookmarks', order_by=self.order_by).fetchall() # <4> 43 | #@@range_end(list1) 44 | 45 | 46 | class DeleteBookmarkCommand(Command): 47 | def execute(self, data): 48 | db.delete('bookmarks', {'id': data}) 49 | return True, None 50 | 51 | 52 | class QuitCommand(Command): 53 | def execute(self, data=None): 54 | sys.exit() 55 | 56 | 57 | class ImportGitHubStarsCommand(Command): 58 | def _extract_bookmark_info(self, repo): 59 | return { 60 | 'タイトル': repo['name'], 61 | 'URL': repo['html_url'], 62 | 'メモ': repo['description'], 63 | } 64 | 65 | def execute(self, data): 66 | bookmarks_imported = 0 67 | 68 | github_username = data['github_username'] 69 | next_page_of_results = f'https://api.github.com/users/{github_username}/starred' 70 | 71 | while next_page_of_results: 72 | stars_response = requests.get( 73 | next_page_of_results, 74 | headers={'Accept': 'application/vnd.github.v3.star+json'}, 75 | ) 76 | next_page_of_results = stars_response.links.get('next', {}).get('url') 77 | 78 | for repo_info in stars_response.json(): 79 | repo = repo_info['repo'] 80 | 81 | if data['preserve_timestamps']: 82 | timestamp = datetime.strptime( 83 | repo_info['starred_at'], 84 | '%Y-%m-%dT%H:%M:%SZ' 85 | ) 86 | else: 87 | timestamp = None 88 | 89 | bookmarks_imported += 1 90 | AddBookmarkCommand().execute( 91 | self._extract_bookmark_info(repo), 92 | timestamp=timestamp, 93 | ) 94 | 95 | return True, bookmarks_imported 96 | 97 | 98 | class EditBookmarkCommand(Command): 99 | def execute(self, data): 100 | db.update( 101 | 'bookmarks', 102 | {'id': data['id']}, 103 | data['update'], 104 | ) 105 | return True, None 106 | -------------------------------------------------------------------------------- /ch10/08bark6/database.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | class DatabaseManager: 4 | def __init__(self, database_filename): 5 | self.connection = sqlite3.connect(database_filename) 6 | 7 | def __del__(self): 8 | self.connection.close() 9 | 10 | def _execute(self, statement, values=None): 11 | with self.connection: 12 | cursor = self.connection.cursor() 13 | cursor.execute(statement, values or []) 14 | return cursor 15 | 16 | def create_table(self, table_name, columns): 17 | columns_with_types = [ 18 | f'{column_name} {data_type}' 19 | for column_name, data_type in columns.items() 20 | ] 21 | self._execute( 22 | f''' 23 | CREATE TABLE IF NOT EXISTS {table_name} 24 | ({', '.join(columns_with_types)}); 25 | ''' 26 | ) 27 | 28 | def drop_table(self, table_name): 29 | self._execute(f'DROP TABLE {table_name};') 30 | 31 | def add(self, table_name, data): 32 | placeholders = ', '.join('?' * len(data)) 33 | column_names = ', '.join(data.keys()) 34 | column_values = tuple(data.values()) 35 | 36 | self._execute( 37 | f''' 38 | INSERT INTO {table_name} 39 | ({column_names}) 40 | VALUES ({placeholders}); 41 | ''', 42 | column_values, 43 | ) 44 | 45 | def delete(self, table_name, criteria): 46 | placeholders = [f'{column} = ?' for column in criteria.keys()] 47 | delete_criteria = ' AND '.join(placeholders) 48 | self._execute( 49 | f''' 50 | DELETE FROM {table_name} 51 | WHERE {delete_criteria}; 52 | ''', 53 | tuple(criteria.values()), 54 | ) 55 | 56 | def select(self, table_name, criteria=None, order_by=None): 57 | criteria = criteria or {} 58 | 59 | query = f'SELECT * FROM {table_name}' 60 | 61 | if criteria: 62 | placeholders = [f'{column} = ?' for column in criteria.keys()] 63 | select_criteria = ' AND '.join(placeholders) 64 | query += f' WHERE {select_criteria}' 65 | 66 | if order_by: 67 | query += f' ORDER BY {order_by}' 68 | 69 | return self._execute( 70 | query, 71 | tuple(criteria.values()), 72 | ) 73 | 74 | def update(self, table_name, criteria, data): 75 | update_placeholders = [f'{column} = ?' for column in criteria.keys()] 76 | update_criteria = ' AND '.join(update_placeholders) 77 | 78 | data_placeholders = ', '.join(f'{key} = ?' for key in data.keys()) 79 | 80 | values = tuple(data.values()) + tuple(criteria.values()) 81 | 82 | self._execute( 83 | f''' 84 | UPDATE {table_name} 85 | SET {data_placeholders} 86 | WHERE {update_criteria}; 87 | ''', 88 | values, 89 | ) 90 | 91 | -------------------------------------------------------------------------------- /ch10/09bark7/bark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | from collections import OrderedDict 5 | 6 | import commands 7 | 8 | #@@range_begin(list1) 9 | def format_bookmark(bookmark): 10 | return '\t'.join( 11 | str(field) if field else '' 12 | for field in bookmark 13 | ) 14 | 15 | 16 | class Option: 17 | def __init__(self, name, command, prep_call=None, success_message='{result}'): # <1> 18 | self.name = name 19 | self.command = command 20 | self.prep_call = prep_call 21 | self.success_message = success_message # <2> 22 | 23 | def _handle_message(self, message): 24 | if isinstance(message, list): 25 | print_bookmarks(message) 26 | else: 27 | print(message) 28 | 29 | def choose(self): 30 | data = self.prep_call() if self.prep_call else None 31 | success, result = self.command.execute(data) # <3> 32 | 33 | formatted_result = '' 34 | 35 | if isinstance(result, list): # <4> 36 | for bookmark in result: 37 | formatted_result += '\n' + format_bookmark(bookmark) 38 | else: 39 | formatted_result = result 40 | 41 | if success: 42 | print(self.success_message.format(result=formatted_result)) # <5> 43 | 44 | def __str__(self): 45 | return self.name 46 | #@@range_end(list1) 47 | 48 | 49 | def clear_screen(): 50 | clear = 'cls' if os.name == 'nt' else 'clear' 51 | os.system(clear) 52 | 53 | 54 | def print_options(options): 55 | for shortcut, option in options.items(): 56 | print(f'({shortcut}) {option}') 57 | print() 58 | 59 | 60 | def option_choice_is_valid(choice, options): 61 | return choice in options or choice.upper() in options 62 | 63 | 64 | def get_option_choice(options): 65 | choice = input('操作を選択してください: ') 66 | while not option_choice_is_valid(choice, options): 67 | print('A, B, T, E, D, G, Qのいずれかを入力してください(小文字でもOK。ただし半角文字)') 68 | choice = input('操作を選択してください: ') 69 | return options[choice.upper()] 70 | 71 | 72 | def get_user_input(label, required=True): 73 | value = input(f'{label}: ') or None 74 | while required and not value: 75 | value = input(f'{label}: ') or None 76 | return value 77 | 78 | 79 | def get_new_bookmark_data(): 80 | return { 81 | 'タイトル': get_user_input('タイトル'), 82 | 'URL': get_user_input('URL'), 83 | 'メモ': get_user_input('メモ', required=False), 84 | } 85 | 86 | 87 | def get_bookmark_id_for_deletion(): 88 | return get_user_input('削除するブックマークのIDを指定') 89 | 90 | 91 | def get_github_import_options(): 92 | return { 93 | 'github_username': get_user_input('GitHubのユーザー名'), 94 | 'preserve_timestamps': 95 | get_user_input( 96 | 'タイムスタンプを維持しますか [Y/n]', 97 | required=False 98 | ) in {'Y', 'y', None}, 99 | } 100 | 101 | 102 | def get_new_bookmark_info(): 103 | bookmark_id = get_user_input('編集対象のIDを入力してください') 104 | field = get_user_input('編集項目を指定してください(「タイトル」「URL」「メモ」のいずれか)') 105 | new_value = get_user_input(f'{field}の新しい値') 106 | return { 107 | 'id': bookmark_id, 108 | 'update': {field: new_value}, 109 | } 110 | 111 | 112 | 113 | def loop(): 114 | clear_screen() 115 | 116 | #@@range_begin(list2) 117 | options = OrderedDict({ 118 | 'A': Option( 119 | '追加', 120 | commands.AddBookmarkCommand(), 121 | prep_call=get_new_bookmark_data, 122 | success_message='ブックマークを追加しました。', # <6> 123 | ), 124 | 'B': Option( 125 | '登録順にリスト', 126 | commands.ListBookmarksCommand(), # <7> 127 | ), 128 | 'T': Option( 129 | 'タイトル順にリスト', 130 | commands.ListBookmarksCommand(order_by='タイトル'), 131 | ), 132 | 'E': Option( 133 | '編集', 134 | commands.EditBookmarkCommand(), 135 | prep_call=get_new_bookmark_info, 136 | success_message='ブックマークを更新しました。' 137 | ), 138 | 'D': Option( 139 | '削除', 140 | commands.DeleteBookmarkCommand(), 141 | prep_call=get_bookmark_id_for_deletion, 142 | success_message='ブックマークを削除しました。', 143 | ), 144 | 'G': Option( 145 | 'GitHubのスターをインポート', 146 | commands.ImportGitHubStarsCommand(), 147 | prep_call=get_github_import_options, 148 | success_message='{result}個のブックマークをインポートしました。', # <8> 149 | ), 150 | 'Q': Option( 151 | '終了', 152 | commands.QuitCommand() 153 | ), 154 | }) 155 | #@@range_end(list2) 156 | print_options(options) 157 | 158 | chosen_option = get_option_choice(options) 159 | clear_screen() 160 | chosen_option.choose() 161 | 162 | _ = input('Enterを押すとメニューに戻ります') 163 | 164 | 165 | if __name__ == '__main__': 166 | print("ブックマーク管理アプリ Bark") 167 | while True: 168 | loop() 169 | -------------------------------------------------------------------------------- /ch10/09bark7/commands.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from abc import ABC, abstractmethod 3 | from datetime import datetime 4 | 5 | import requests 6 | 7 | from persistence import BookmarkDatabase # <1> 8 | 9 | persistence = BookmarkDatabase() # <2> 10 | 11 | 12 | class Command(ABC): 13 | @abstractmethod 14 | def execute(self, data): 15 | raise NotImplementedError('コマンドは必ずメソッドexecuteを実装してください') 16 | 17 | 18 | class AddBookmarkCommand(Command): 19 | def execute(self, data, timestamp=None): 20 | data['追加日時'] = timestamp or datetime.utcnow().isoformat() 21 | persistence.create(data) # <3> 22 | return True, None 23 | 24 | 25 | 26 | class ListBookmarksCommand(Command): 27 | def __init__(self, order_by='追加日時'): 28 | self.order_by = order_by 29 | 30 | def execute(self, data=None): 31 | return True, persistence.list(order_by=self.order_by) # <4> 32 | 33 | 34 | class DeleteBookmarkCommand(Command): 35 | def execute(self, data): 36 | persistence.delete(data) # <5> 37 | return True, None 38 | 39 | 40 | class QuitCommand(Command): 41 | def execute(self, data=None): 42 | sys.exit() 43 | 44 | 45 | class ImportGitHubStarsCommand(Command): 46 | def _extract_bookmark_info(self, repo): 47 | return { 48 | 'タイトル': repo['name'], 49 | 'URL': repo['html_url'], 50 | 'メモ': repo['description'], 51 | } 52 | 53 | def execute(self, data): 54 | bookmarks_imported = 0 55 | 56 | github_username = data['github_username'] 57 | next_page_of_results = f'https://api.github.com/users/{github_username}/starred' 58 | 59 | while next_page_of_results: 60 | stars_response = requests.get( 61 | next_page_of_results, 62 | headers={'Accept': 'application/vnd.github.v3.star+json'}, 63 | ) 64 | next_page_of_results = stars_response.links.get('next', {}).get('url') 65 | 66 | for repo_info in stars_response.json(): 67 | repo = repo_info['repo'] 68 | 69 | if data['preserve_timestamps']: 70 | timestamp = datetime.strptime( 71 | repo_info['starred_at'], 72 | '%Y-%m-%dT%H:%M:%SZ' 73 | ) 74 | else: 75 | timestamp = None 76 | 77 | bookmarks_imported += 1 78 | AddBookmarkCommand().execute( 79 | self._extract_bookmark_info(repo), 80 | timestamp=timestamp, 81 | ) 82 | 83 | return True, bookmarks_imported 84 | 85 | 86 | class EditBookmarkCommand(Command): 87 | def execute(self, data): 88 | persistence.edit(data['id'], data['update']) # <6> 89 | return True, None 90 | -------------------------------------------------------------------------------- /ch10/09bark7/database.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | class DatabaseManager: 4 | def __init__(self, database_filename): 5 | self.connection = sqlite3.connect(database_filename) 6 | 7 | def __del__(self): 8 | self.connection.close() 9 | 10 | def _execute(self, statement, values=None): 11 | with self.connection: 12 | cursor = self.connection.cursor() 13 | cursor.execute(statement, values or []) 14 | return cursor 15 | 16 | def create_table(self, table_name, columns): 17 | columns_with_types = [ 18 | f'{column_name} {data_type}' 19 | for column_name, data_type in columns.items() 20 | ] 21 | self._execute( 22 | f''' 23 | CREATE TABLE IF NOT EXISTS {table_name} 24 | ({', '.join(columns_with_types)}); 25 | ''' 26 | ) 27 | 28 | def drop_table(self, table_name): 29 | self._execute(f'DROP TABLE {table_name};') 30 | 31 | def add(self, table_name, data): 32 | placeholders = ', '.join('?' * len(data)) 33 | column_names = ', '.join(data.keys()) 34 | column_values = tuple(data.values()) 35 | 36 | self._execute( 37 | f''' 38 | INSERT INTO {table_name} 39 | ({column_names}) 40 | VALUES ({placeholders}); 41 | ''', 42 | column_values, 43 | ) 44 | 45 | def delete(self, table_name, criteria): 46 | placeholders = [f'{column} = ?' for column in criteria.keys()] 47 | delete_criteria = ' AND '.join(placeholders) 48 | self._execute( 49 | f''' 50 | DELETE FROM {table_name} 51 | WHERE {delete_criteria}; 52 | ''', 53 | tuple(criteria.values()), 54 | ) 55 | 56 | def select(self, table_name, criteria=None, order_by=None): 57 | criteria = criteria or {} 58 | 59 | query = f'SELECT * FROM {table_name}' 60 | 61 | if criteria: 62 | placeholders = [f'{column} = ?' for column in criteria.keys()] 63 | select_criteria = ' AND '.join(placeholders) 64 | query += f' WHERE {select_criteria}' 65 | 66 | if order_by: 67 | query += f' ORDER BY {order_by}' 68 | 69 | return self._execute( 70 | query, 71 | tuple(criteria.values()), 72 | ) 73 | 74 | def update(self, table_name, criteria, data): 75 | update_placeholders = [f'{column} = ?' for column in criteria.keys()] 76 | update_criteria = ' AND '.join(update_placeholders) 77 | 78 | data_placeholders = ', '.join(f'{key} = ?' for key in data.keys()) 79 | 80 | values = tuple(data.values()) + tuple(criteria.values()) 81 | 82 | self._execute( 83 | f''' 84 | UPDATE {table_name} 85 | SET {data_placeholders} 86 | WHERE {update_criteria}; 87 | ''', 88 | values, 89 | ) 90 | 91 | -------------------------------------------------------------------------------- /ch10/09bark7/persistence.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from database import DatabaseManager 4 | 5 | 6 | class PersistenceLayer(ABC): # <1> 7 | @abstractmethod 8 | def create(self, data): # <2> 9 | raise NotImplementedError('パーシスタンス層では必ずメソッドcreateを実装してください') 10 | 11 | @abstractmethod 12 | def list(self, order_by=None): 13 | raise NotImplementedError('パーシスタンス層では必ずメソッドlistを実装してください') 14 | 15 | @abstractmethod 16 | def edit(self, bookmark_id, bookmark_data): 17 | raise NotImplementedError('パーシスタンス層では必ずメソッドeditを実装してください') 18 | 19 | @abstractmethod 20 | def delete(self, bookmark_id): 21 | raise NotImplementedError('パーシスタンス層では必ずメソッドdeleteを実装してください') 22 | 23 | 24 | class BookmarkDatabase(PersistenceLayer): # <3> 25 | def __init__(self): 26 | self.table_name = 'bookmarks' # <4> 27 | self.db = DatabaseManager('bookmarks.db') 28 | 29 | self.db.create_table(self.table_name, { 30 | 'id': 'integer primary key autoincrement', 31 | 'タイトル': 'text not null', 32 | 'URL': 'text not null', 33 | 'メモ': 'text', 34 | '追加日時': 'text not null', 35 | }) 36 | 37 | def create(self, bookmark_data): # <5> 38 | self.db.add(self.table_name, bookmark_data) 39 | 40 | def list(self, order_by=None): 41 | return self.db.select(self.table_name, order_by=order_by).fetchall() 42 | 43 | def edit(self, bookmark_id, bookmark_data): 44 | self.db.update(self.table_name, {'id': bookmark_id}, bookmark_data) 45 | 46 | def delete(self, bookmark_id): 47 | self.db.delete(self.table_name, {'id': bookmark_id}) 48 | --------------------------------------------------------------------------------