├── Chapter01 └── ch1 │ ├── bike.py │ ├── example │ ├── core.py │ ├── run.py │ └── util │ │ ├── __init__.py │ │ ├── db.py │ │ ├── math.py │ │ └── network.py │ ├── factorial.py │ ├── files_only │ ├── core.py │ ├── db.py │ ├── math.py │ ├── network.py │ └── run.py │ ├── names.py │ ├── scopes1.py │ ├── scopes2.py │ ├── scopes3.py │ └── virtualenv.creation.txt ├── Chapter02 └── ch2 │ ├── README.md │ ├── bytearray.py │ ├── chainmap.py │ ├── defaultdict.py │ ├── dicts.py │ ├── enum.py │ ├── final_considerations.py │ ├── lists.py │ ├── namedtuple.py │ ├── numbers.py │ ├── objects.py │ ├── sequences.py │ ├── sets.py │ └── tuples.py ├── Chapter03 └── ch3 │ ├── any.py │ ├── binary.2.py │ ├── binary.py │ ├── compress.py │ ├── conditional.1.py │ ├── conditional.2.py │ ├── coupons.dict.py │ ├── coupons.py │ ├── discount.py │ ├── errorsalert.py │ ├── for.else.py │ ├── for.no.else.py │ ├── infinite.py │ ├── multiple.sequences.enumerate.py │ ├── multiple.sequences.explicit.py │ ├── multiple.sequences.implicit.py │ ├── multiple.sequences.py │ ├── multiple.sequences.while.py │ ├── multiple.sequences.zip.py │ ├── permutations.py │ ├── primes.else.py │ ├── primes.py │ ├── range.py │ ├── simple.for.2.py │ ├── simple.for.3.py │ ├── simple.for.4.py │ ├── simple.for.py │ ├── switch.js │ ├── switch.py │ ├── taxes.py │ └── ternary.py ├── Chapter04 └── ch4 │ ├── additional.unpacking.py │ ├── arguments.all.kwonly.py │ ├── arguments.all.py │ ├── arguments.default.error.py │ ├── arguments.default.py │ ├── arguments.defaults.mutable.intermediate.call.py │ ├── arguments.defaults.mutable.no.trap.py │ ├── arguments.defaults.mutable.py │ ├── arguments.keyword.only.py │ ├── arguments.keyword.py │ ├── arguments.positional.py │ ├── arguments.variable.db.py │ ├── arguments.variable.keyword.py │ ├── arguments.variable.positional.py │ ├── arguments.variable.positional.unpacking.py │ ├── data.science.example.py │ ├── docstrings.py │ ├── filter.lambda.py │ ├── filter.regular.py │ ├── func.attributes.py │ ├── func_from.py │ ├── func_import.py │ ├── key.points.argument.passing.py │ ├── key.points.assignment.py │ ├── key.points.mutable.assignment.py │ ├── key.points.mutable.py │ ├── lambda.explained.py │ ├── lib │ ├── __init__.py │ └── funcdef.py │ ├── matrix.multiplication.func.py │ ├── matrix.multiplication.nofunc.py │ ├── no.side.effects.py │ ├── primes.py │ ├── recursive.factorial.py │ ├── return.multiple.py │ ├── return.none.py │ ├── return.single.value.2.py │ ├── return.single.value.py │ ├── scoping.level.1.py │ ├── scoping.level.2.global.py │ ├── scoping.level.2.nonlocal.py │ ├── scoping.level.2.py │ ├── vat.function.py │ └── vat.py ├── Chapter05 └── ch5 │ ├── alias.py │ ├── decorate.sort.undecorate.py │ ├── dictionary.comprehensions.duplicates.py │ ├── dictionary.comprehensions.positions.py │ ├── dictionary.comprehensions.py │ ├── even.squares.py │ ├── fibonacci.elegant.py │ ├── fibonacci.first.py │ ├── fibonacci.second.py │ ├── filter.py │ ├── first.n.squares.manual.method.py │ ├── first.n.squares.manual.py │ ├── first.n.squares.py │ ├── functions.py │ ├── gen.filter.py │ ├── gen.map.filter.py │ ├── gen.map.py │ ├── gen.send.preparation.py │ ├── gen.send.preparation.stop.py │ ├── gen.send.py │ ├── gen.yield.for.py │ ├── gen.yield.from.py │ ├── gen.yield.return.py │ ├── generator.expressions.py │ ├── map.example.py │ ├── maxims.py │ ├── pairs.for.loop.py │ ├── pairs.list.comprehension.py │ ├── performances.map.py │ ├── performances.py │ ├── pythagorean.triple.comprehension.py │ ├── pythagorean.triple.generation.for.py │ ├── pythagorean.triple.generation.py │ ├── pythagorean.triple.int.py │ ├── pythagorean.triple.py │ ├── scopes.for.py │ ├── scopes.noglobal.py │ ├── scopes.py │ ├── set.comprehensions.py │ ├── squares.comprehension.py │ ├── squares.map.py │ ├── squares.py │ ├── sum.example.2.py │ ├── sum.example.py │ └── zip.grades.py ├── Chapter06 └── ch6 │ ├── decorators │ ├── decorators.factory.py │ ├── syntax.py │ ├── time.measure.arguments.py │ ├── time.measure.deco1.py │ ├── time.measure.deco2.py │ ├── time.measure.dry.py │ ├── time.measure.start.py │ └── two.decorators.py │ ├── iterators │ └── iterator.py │ └── oop │ ├── class.attribute.shadowing.py │ ├── class.init.py │ ├── class.issubclass.isinstance.py │ ├── class.methods.factory.py │ ├── class.methods.split.py │ ├── class.namespaces.py │ ├── class.price.py │ ├── class.self.py │ ├── class_inheritance.py │ ├── dataclass.py │ ├── mro.py │ ├── mro.simple.py │ ├── multiple.inheritance.py │ ├── operator.overloading.py │ ├── private.attrs.fixed.py │ ├── private.attrs.py │ ├── property.py │ ├── simplest.class.py │ ├── static.methods.py │ ├── super.duplication.py │ ├── super.explicit.py │ └── super.implicit.py ├── Chapter07 └── ch7 │ ├── files │ ├── buffer.py │ ├── compression │ │ ├── content1.txt │ │ ├── content2.txt │ │ ├── subfolder │ │ │ ├── content3.txt │ │ │ └── content4.txt │ │ ├── tar.py │ │ └── zip.py │ ├── existence.py │ ├── fear.txt │ ├── fixed_amount.py │ ├── listing.py │ ├── manipulation.py │ ├── open_try.py │ ├── open_with.py │ ├── ops_create.py │ ├── paths.py │ ├── print_file.py │ ├── read_write.py │ ├── read_write_bin.py │ ├── tmp.py │ ├── walking.py │ └── write_not_exists.py │ ├── io_examples │ ├── reqs.py │ ├── reqs_post.py │ └── string_io.py │ ├── json_examples │ ├── json_basic.py │ ├── json_cplx.py │ ├── json_datetime.py │ └── json_tuple.py │ └── persistence │ ├── alchemy.py │ ├── alchemy_models.py │ ├── pickler.py │ └── shelf.py ├── Chapter08 └── ch8 │ ├── __init__.py │ ├── api.py │ ├── data.py │ ├── exceptions │ ├── first.example.py │ ├── for.loop.py │ ├── json.example.py │ ├── multiple.except.py │ └── try.syntax.py │ ├── profiling │ ├── triples.py │ ├── triples_v2.py │ └── triples_v3.py │ └── tests │ ├── __init__.py │ └── test_api.py ├── Chapter09 └── ch9 │ ├── claims_auth.py │ ├── claims_time.py │ ├── hlib.py │ ├── hmc.py │ ├── rsa │ ├── key │ ├── key.pub │ ├── keypwd │ └── keypwd.pub │ ├── secrs │ ├── secr_gen.py │ ├── secr_rand.py │ └── secr_reset.py │ ├── tok.py │ └── token_rsa.py ├── Chapter10 └── ch10 │ ├── aio │ ├── pics │ │ ├── 53a51667.jpg │ │ └── 81846f84.jpg │ ├── randompix_corout.py │ ├── randompix_proc.py │ └── randompix_serial.py │ ├── comm_queue.py │ ├── comm_queue_proc.py │ ├── evt.py │ ├── hostres │ ├── README.md │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ └── test_util.py │ └── util.py │ ├── local.py │ ├── ms │ ├── README.md │ ├── __init__.py │ ├── algo │ │ ├── __init__.py │ │ ├── mergesort.py │ │ ├── mergesort_proc.py │ │ ├── mergesort_thread.py │ │ └── multi_mergesort.py │ ├── performance.py │ └── tests │ │ ├── __init__.py │ │ ├── test_mergesort.py │ │ ├── test_mergesort_concurrent.py │ │ └── test_multi_mergesort.py │ ├── pool.py │ ├── pool_proc.py │ ├── race.py │ ├── race_with_lock.py │ ├── start.py │ ├── start_proc.py │ ├── start_with_info.py │ ├── starwars.py │ ├── stop.py │ └── sudoku │ ├── LICENSE │ ├── README.md │ ├── __init__.py │ ├── algo │ ├── __init__.py │ └── solver.py │ ├── norvig │ ├── LICENSE │ └── sudoku.py │ ├── process_solver.py │ ├── puzzles │ ├── resources.txt │ ├── sudoku-subig20.txt │ ├── sudoku-top2365.txt │ ├── sudoku-top95.txt │ ├── sudoku-topn1465.txt │ └── sudoku-topn234.txt │ └── tests │ ├── __init__.py │ └── test_solver.py ├── Chapter11 └── ch11 │ ├── assertions.py │ ├── custom.py │ ├── custom_timestamp.py │ ├── log.py │ ├── pdebugger.py │ ├── pdebugger_pdb.py │ ├── traceback_simple.py │ └── traceback_validator.py ├── Chapter12 └── ch12 │ ├── guiscrape.py │ ├── scrape.py │ └── simple_server │ ├── img │ ├── owl-alcohol.png │ ├── owl-book.png │ ├── owl-books.png │ ├── owl-ebook.jpg │ └── owl-rose.jpeg │ ├── index.html │ └── serve.sh ├── Chapter13 └── ch13 │ ├── ch13-dataprep.ipynb │ ├── ch13.ipynb │ ├── data.json │ └── example.ipynb ├── Chapter14 └── ch14 │ ├── falcon │ ├── main.py │ ├── quotes.py │ └── stress.py │ ├── flask │ ├── main.py │ └── templates │ │ └── main.html │ └── regex │ ├── db.sqlite3 │ ├── entries │ ├── __init__.py │ ├── admin.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── static │ │ └── entries │ │ │ └── css │ │ │ └── main.css │ ├── templates │ │ └── entries │ │ │ ├── base.html │ │ │ ├── footer.html │ │ │ ├── home.html │ │ │ ├── insert.html │ │ │ └── list.html │ └── views.py │ ├── manage.py │ └── regex │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── LICENSE ├── README.md └── requirements ├── all.txt ├── requirements.data.science.in ├── requirements.data.science.txt ├── requirements.in └── requirements.txt /Chapter01/ch1/bike.py: -------------------------------------------------------------------------------- 1 | # let's define the class Bike 2 | class Bike: 3 | 4 | def __init__(self, colour, frame_material): 5 | self.colour = colour 6 | self.frame_material = frame_material 7 | 8 | def brake(self): 9 | print("Braking!") 10 | 11 | # let's create a couple of instances 12 | red_bike = Bike('Red', 'Carbon fiber') 13 | blue_bike = Bike('Blue', 'Steel') 14 | 15 | # let's inspect the objects we have, instances of the Bike class. 16 | print(red_bike.colour) # prints: Red 17 | print(red_bike.frame_material) # prints: Carbon fiber 18 | print(blue_bike.colour) # prints: Blue 19 | print(blue_bike.frame_material) # prints: Steel 20 | 21 | # let's brake! 22 | red_bike.brake() # prints: Braking! 23 | -------------------------------------------------------------------------------- /Chapter01/ch1/example/core.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter01/ch1/example/core.py -------------------------------------------------------------------------------- /Chapter01/ch1/example/run.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter01/ch1/example/run.py -------------------------------------------------------------------------------- /Chapter01/ch1/example/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter01/ch1/example/util/__init__.py -------------------------------------------------------------------------------- /Chapter01/ch1/example/util/db.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter01/ch1/example/util/db.py -------------------------------------------------------------------------------- /Chapter01/ch1/example/util/math.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter01/ch1/example/util/math.py -------------------------------------------------------------------------------- /Chapter01/ch1/example/util/network.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter01/ch1/example/util/network.py -------------------------------------------------------------------------------- /Chapter01/ch1/factorial.py: -------------------------------------------------------------------------------- 1 | >>> from math import factorial 2 | >>> factorial(5) 3 | 120 -------------------------------------------------------------------------------- /Chapter01/ch1/files_only/core.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter01/ch1/files_only/core.py -------------------------------------------------------------------------------- /Chapter01/ch1/files_only/db.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter01/ch1/files_only/db.py -------------------------------------------------------------------------------- /Chapter01/ch1/files_only/math.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter01/ch1/files_only/math.py -------------------------------------------------------------------------------- /Chapter01/ch1/files_only/network.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter01/ch1/files_only/network.py -------------------------------------------------------------------------------- /Chapter01/ch1/files_only/run.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter01/ch1/files_only/run.py -------------------------------------------------------------------------------- /Chapter01/ch1/names.py: -------------------------------------------------------------------------------- 1 | >>> n = 3 # integer number 2 | >>> address = "221b Baker Street, NW1 6XE, London" # Sherlock Holmes' address 3 | >>> employee = { 4 | ... 'age': 45, 5 | ... 'role': 'CTO', 6 | ... 'SSN': 'AB1234567', 7 | ... } 8 | >>> # let's print them 9 | >>> n 10 | 3 11 | >>> address 12 | '221b Baker Street, NW1 6XE, London' 13 | >>> employee 14 | {'age': 45, 'role': 'CTO', 'SSN': 'AB1234567'} 15 | >>> other_name 16 | Traceback (most recent call last): 17 | File "", line 1, in 18 | NameError: name 'other_name' is not defined 19 | >>> -------------------------------------------------------------------------------- /Chapter01/ch1/scopes1.py: -------------------------------------------------------------------------------- 1 | # Local versus Global 2 | 3 | # we define a function, called local 4 | def local(): 5 | m = 7 6 | print(m) 7 | 8 | m = 5 9 | print(m) 10 | 11 | # we call, or `execute` the function local 12 | local() 13 | -------------------------------------------------------------------------------- /Chapter01/ch1/scopes2.py: -------------------------------------------------------------------------------- 1 | # Local versus Global 2 | 3 | def local(): 4 | # m doesn't belong to the scope defined by the local function 5 | # so Python will keep looking into the next enclosing scope. 6 | # m is finally found in the global scope 7 | print(m, 'printing from the local scope') 8 | 9 | m = 5 10 | print(m, 'printing from the global scope') 11 | 12 | local() 13 | -------------------------------------------------------------------------------- /Chapter01/ch1/scopes3.py: -------------------------------------------------------------------------------- 1 | # Local, Enclosing and Global 2 | 3 | 4 | def enclosing_func(): 5 | m = 13 6 | 7 | def local(): 8 | # m doesn't belong to the scope defined by the local 9 | # function so Python will keep looking into the next 10 | # enclosing scope. This time m is found in the enclosing 11 | # scope 12 | print(m, 'printing from the local scope') 13 | 14 | # calling the function local 15 | local() 16 | 17 | m = 5 18 | print(m, 'printing from the global scope') 19 | 20 | enclosing_func() 21 | -------------------------------------------------------------------------------- /Chapter01/ch1/virtualenv.creation.txt: -------------------------------------------------------------------------------- 1 | fabmp:srv fab$ # step 1 - create folder 2 | fabmp:srv fab$ mkdir learn.pp 3 | fabmp:srv fab$ cd learn.pp 4 | 5 | fabmp:learn.pp fab$ # step 2 - create virtual environment 6 | fabmp:learn.pp fab$ which python3.7 7 | /Users/fab/.pyenv/shims/python3.7 8 | fabmp:learn.pp fab$ virtualenv -p 9 | ⇢ /Users/fab/.pyenv/shims/python3.7 learnpp 10 | Running virtualenv with interpreter /Users/fab/.pyenv/shims/python3.7 11 | Using base prefix '/Users/fab/.pyenv/versions/3.7.0a3' 12 | New python executable in /Users/fab/srv/learn.pp/learnpp/bin/python3.7 13 | Also creating executable in /Users/fab/srv/learn.pp/learnpp/bin/python 14 | Installing setuptools, pip, wheel...done. 15 | 16 | fabmp:learn.pp fab$ # step 3 - activate virtual environment 17 | fabmp:learn.pp fab$ source learnpp/bin/activate 18 | 19 | (learnpp) fabmp:learn.pp fab$ # step 4 - verify which python 20 | (learnpp) fabmp:learn.pp fab$ which python 21 | /Users/fab/srv/learn.pp/learnpp/bin/python 22 | 23 | (learnpp) fabmp:learn.pp fab$ python 24 | Python 3.7.0a3 (default, Jan 27 2018, 00:46:45) 25 | [Clang 9.0.0 (clang-900.0.39.2)] on darwin 26 | Type "help", "copyright", "credits" or "license" for more information. 27 | >>> exit() 28 | 29 | (learnpp) fabmp:learn.pp fab$ # step 5 - deactivate 30 | (learnpp) fabmp:learn.pp fab$ deactivate 31 | fabmp:learn.pp fab$ 32 | -------------------------------------------------------------------------------- /Chapter02/ch2/README.md: -------------------------------------------------------------------------------- 1 | Chapter 2 data files 2 | ==================== 3 | 4 | The files in this folder are not supposed to work if run. 5 | They serve as source for the book chapters, and to provide a 6 | quick copy/paste tool for whoever would need their content. 7 | -------------------------------------------------------------------------------- /Chapter02/ch2/bytearray.py: -------------------------------------------------------------------------------- 1 | # bytearray.py 2 | 3 | 4 | >>> bytearray() # empty bytearray object 5 | bytearray(b'') 6 | >>> bytearray(10) # zero-filled instance with given length 7 | bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') 8 | >>> bytearray(range(5)) # bytearray from iterable of integers 9 | bytearray(b'\x00\x01\x02\x03\x04') 10 | >>> name = bytearray(b'Lina') #A - bytearray from bytes 11 | >>> name.replace(b'L', b'l') 12 | bytearray(b'lina') 13 | >>> name.endswith(b'na') 14 | True 15 | >>> name.upper() 16 | bytearray(b'LINA') 17 | >>> name.count(b'L') 18 | 1 19 | -------------------------------------------------------------------------------- /Chapter02/ch2/chainmap.py: -------------------------------------------------------------------------------- 1 | # chainmap.py 2 | 3 | 4 | >>> from collections import ChainMap 5 | >>> default_connection = {'host': 'localhost', 'port': 4567} 6 | >>> connection = {'port': 5678} 7 | >>> conn = ChainMap(connection, default_connection) # map creation 8 | >>> conn['port'] # port is found in the first dictionary 9 | 5678 10 | >>> conn['host'] # host is fetched from the second dictionary 11 | 'localhost' 12 | >>> conn.maps # we can see the mapping objects 13 | [{'port': 5678}, {'host': 'localhost', 'port': 4567}] 14 | >>> conn['host'] = 'packtpub.com' # let's add host 15 | >>> conn.maps 16 | [{'port': 5678, 'host': 'packtpub.com'}, 17 | {'host': 'localhost', 'port': 4567}] 18 | >>> del conn['port'] # let's remove the port information 19 | >>> conn.maps 20 | [{'host': 'packtpub.com'}, {'host': 'localhost', 'port': 4567}] 21 | >>> conn['port'] # now port is fetched from the second dictionary 22 | 4567 23 | >>> dict(conn) # easy to merge and convert to regular dictionary 24 | {'host': 'packtpub.com', 'port': 4567} -------------------------------------------------------------------------------- /Chapter02/ch2/defaultdict.py: -------------------------------------------------------------------------------- 1 | # defaultdict.py 2 | 3 | 4 | >>> d = {} 5 | >>> d['age'] = d.get('age', 0) + 1 # age not there, we get 0 + 1 6 | >>> d 7 | {'age': 1} 8 | >>> d = {'age': 39} 9 | >>> d['age'] = d.get('age', 0) + 1 # age is there, we get 40 10 | >>> d 11 | {'age': 40} 12 | 13 | 14 | >>> from collections import defaultdict 15 | >>> dd = defaultdict(int) # int is the default type (0 the value) 16 | >>> dd['age'] += 1 # short for dd['age'] = dd['age'] + 1 17 | >>> dd 18 | defaultdict(, {'age': 1}) # 1, as expected 19 | -------------------------------------------------------------------------------- /Chapter02/ch2/enum.py: -------------------------------------------------------------------------------- 1 | >>> GREEN = 1 2 | >>> YELLOW = 2 3 | >>> RED = 4 4 | >>> TRAFFIC_LIGHTS = (GREEN, YELLOW, RED) 5 | >>> # or with a dict 6 | >>> traffic_lights = {'GREEN': 1, 'YELLOW': 2, 'RED': 4} 7 | 8 | 9 | 10 | # using enum 11 | >>> from enum import Enum 12 | >>> class TrafficLight(Enum): 13 | ... GREEN = 1 14 | ... YELLOW = 2 15 | ... RED = 4 16 | ... 17 | >>> TrafficLight.GREEN 18 | 19 | >>> TrafficLight.GREEN.name 20 | 'GREEN' 21 | >>> TrafficLight.GREEN.value 22 | 1 23 | >>> TrafficLight(1) 24 | 25 | >>> TrafficLight(4) 26 | 27 | -------------------------------------------------------------------------------- /Chapter02/ch2/final_considerations.py: -------------------------------------------------------------------------------- 1 | # final_considerations.py 2 | 3 | 4 | >>> a = 1000000 5 | >>> b = 1000000 6 | >>> id(a) == id(b) 7 | False 8 | 9 | 10 | >>> a = 5 11 | >>> b = 5 12 | >>> id(a) == id(b) 13 | True 14 | 15 | 16 | # how to choose data structures 17 | # example customer objects 18 | customer1 = {'id': 'abc123', 'full_name': 'Master Yoda'} 19 | customer2 = {'id': 'def456', 'full_name': 'Obi-Wan Kenobi'} 20 | customer3 = {'id': 'ghi789', 'full_name': 'Anakin Skywalker'} 21 | # collect them in a tuple 22 | customers = (customer1, customer2, customer3) 23 | # or collect them in a list 24 | customers = [customer1, customer2, customer3] 25 | # or maybe within a dictionary, they have a unique id after all 26 | customers = { 27 | 'abc123': customer1, 28 | 'def456': customer2, 29 | 'ghi789': customer3, 30 | } 31 | 32 | 33 | # negative indexing 34 | >>> a = list(range(10)) # `a` has 10 elements. Last one is 9. 35 | >>> a 36 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 37 | >>> len(a) # its length is 10 elements 38 | 10 39 | >>> a[len(a) - 1] # position of last one is len(a) - 1 40 | 9 41 | >>> a[-1] # but we don't need len(a)! Python rocks! 42 | 9 43 | >>> a[-2] # equivalent to len(a) - 2 44 | 8 45 | >>> a[-3] # equivalent to len(a) - 3 46 | 7 47 | -------------------------------------------------------------------------------- /Chapter02/ch2/lists.py: -------------------------------------------------------------------------------- 1 | # lists.py 2 | 3 | 4 | # creation 5 | >>> [] # empty list 6 | [] 7 | >>> list() # same as [] 8 | [] 9 | >>> [1, 2, 3] # as with tuples, items are comma separated 10 | [1, 2, 3] 11 | >>> [x + 5 for x in [2, 3, 4]] # Python is magic 12 | [7, 8, 9] 13 | >>> list((1, 3, 5, 7, 9)) # list from a tuple 14 | [1, 3, 5, 7, 9] 15 | >>> list('hello') # list from a string 16 | ['h', 'e', 'l', 'l', 'o'] 17 | 18 | 19 | # main methods 20 | >>> a = [1, 2, 1, 3] 21 | >>> a.append(13) # we can append anything at the end 22 | >>> a 23 | [1, 2, 1, 3, 13] 24 | >>> a.count(1) # how many `1` are there in the list? 25 | 2 26 | >>> a.extend([5, 7]) # extend the list by another (or sequence) 27 | >>> a 28 | [1, 2, 1, 3, 13, 5, 7] 29 | >>> a.index(13) # position of `13` in the list (0-based indexing) 30 | 4 31 | >>> a.insert(0, 17) # insert `17` at position 0 32 | >>> a 33 | [17, 1, 2, 1, 3, 13, 5, 7] 34 | >>> a.pop() # pop (remove and return) last element 35 | 7 36 | >>> a.pop(3) # pop element at position 3 37 | 1 38 | >>> a 39 | [17, 1, 2, 3, 13, 5] 40 | >>> a.remove(17) # remove `17` from the list 41 | >>> a 42 | [1, 2, 3, 13, 5] 43 | >>> a.reverse() # reverse the order of the elements in the list 44 | >>> a 45 | [5, 13, 3, 2, 1] 46 | >>> a.sort() # sort the list 47 | >>> a 48 | [1, 2, 3, 5, 13] 49 | >>> a.clear() # remove all elements from the list 50 | >>> a 51 | [] 52 | 53 | 54 | # extending 55 | >>> a = list('hello') # makes a list from a string 56 | >>> a 57 | ['h', 'e', 'l', 'l', 'o'] 58 | >>> a.append(100) # append 100, heterogeneous type 59 | >>> a 60 | ['h', 'e', 'l', 'l', 'o', 100] 61 | >>> a.extend((1, 2, 3)) # extend using tuple 62 | >>> a 63 | ['h', 'e', 'l', 'l', 'o', 100, 1, 2, 3] 64 | >>> a.extend('...') # extend using string 65 | >>> a 66 | ['h', 'e', 'l', 'l', 'o', 100, 1, 2, 3, '.', '.', '.'] 67 | 68 | 69 | # most common operations 70 | >>> a = [1, 3, 5, 7] 71 | >>> min(a) # minimum value in the list 72 | 1 73 | >>> max(a) # maximum value in the list 74 | 7 75 | >>> sum(a) # sum of all values in the list 76 | 16 77 | >>> len(a) # number of elements in the list 78 | 4 79 | >>> b = [6, 7, 8] 80 | >>> a + b # `+` with list means concatenation 81 | [1, 3, 5, 7, 6, 7, 8] 82 | >>> a * 2 # `*` has also a special meaning 83 | [1, 3, 5, 7, 1, 3, 5, 7] 84 | 85 | 86 | # cool sorting 87 | >>> from operator import itemgetter 88 | >>> a = [(5, 3), (1, 3), (1, 2), (2, -1), (4, 9)] 89 | >>> sorted(a) 90 | [(1, 2), (1, 3), (2, -1), (4, 9), (5, 3)] 91 | >>> sorted(a, key=itemgetter(0)) 92 | [(1, 3), (1, 2), (2, -1), (4, 9), (5, 3)] 93 | >>> sorted(a, key=itemgetter(0, 1)) 94 | [(1, 2), (1, 3), (2, -1), (4, 9), (5, 3)] 95 | >>> sorted(a, key=itemgetter(1)) 96 | [(2, -1), (1, 2), (5, 3), (1, 3), (4, 9)] 97 | >>> sorted(a, key=itemgetter(1), reverse=True) 98 | [(4, 9), (5, 3), (1, 3), (1, 2), (2, -1)] 99 | -------------------------------------------------------------------------------- /Chapter02/ch2/namedtuple.py: -------------------------------------------------------------------------------- 1 | # namedtuple.py 2 | 3 | 4 | # the problem 5 | >>> vision = (9.5, 8.8) 6 | >>> vision 7 | (9.5, 8.8) 8 | >>> vision[0] # left eye (implicit positional reference) 9 | 9.5 10 | >>> vision[1] # right eye (implicit positional reference) 11 | 8.8 12 | 13 | 14 | # the solution 15 | >>> from collections import namedtuple 16 | >>> Vision = namedtuple('Vision', ['left', 'right']) 17 | >>> vision = Vision(9.5, 8.8) 18 | >>> vision[0] 19 | 9.5 20 | >>> vision.left # same as vision[0], but explicit 21 | 9.5 22 | >>> vision.right # same as vision[1], but explicit 23 | 8.8 24 | 25 | 26 | # the change 27 | >>> Vision = namedtuple('Vision', ['left', 'combined', 'right']) 28 | >>> vision = Vision(9.5, 9.2, 8.8) 29 | >>> vision.left # still correct 30 | 9.5 31 | >>> vision.right # still correct (though now is vision[2]) 32 | 8.8 33 | >>> vision.combined # the new vision[1] 34 | 9.2 35 | -------------------------------------------------------------------------------- /Chapter02/ch2/objects.py: -------------------------------------------------------------------------------- 1 | # objects.py 2 | 3 | # code block # 1 4 | >>> age = 42 5 | >>> age 6 | 42 7 | >>> age = 43 #A 8 | >>> age 9 | 43 10 | 11 | 12 | # code block # 2 13 | >>> age = 42 14 | >>> id(age) 15 | 4377553168 16 | >>> age = 43 17 | >>> id(age) 18 | 4377553200 19 | 20 | 21 | # code block # 3 22 | >>> class Person(): 23 | ... def __init__(self, age): 24 | ... self.age = age 25 | ... 26 | >>> fab = Person(age=42) 27 | >>> fab.age 28 | 42 29 | >>> id(fab) 30 | 4380878496 31 | >>> id(fab.age) 32 | 4377553168 33 | >>> fab.age = 25 # I wish! 34 | >>> id(fab) # will be the same 35 | 4380878496 36 | >>> id(fab.age) # will be different 37 | 4377552624 -------------------------------------------------------------------------------- /Chapter02/ch2/sequences.py: -------------------------------------------------------------------------------- 1 | # sequences.py 2 | 3 | 4 | # strings 5 | >>> # 4 ways to make a string 6 | >>> str1 = 'This is a string. We built it with single quotes.' 7 | >>> str2 = "This is also a string, but built with double quotes." 8 | >>> str3 = '''This is built using triple quotes, 9 | ... so it can span multiple lines.''' 10 | >>> str4 = """This too 11 | ... is a multiline one 12 | ... built with triple double-quotes.""" 13 | >>> str4 #A 14 | 'This too\nis a multiline one\nbuilt with triple double-quotes.' 15 | >>> print(str4) #B 16 | This too 17 | is a multiline one 18 | built with triple double-quotes. 19 | >>> 20 | 21 | 22 | # encode / decode 23 | >>> s = "This is üŋíc0de" # unicode string: code points 24 | >>> type(s) 25 | 26 | >>> encoded_s = s.encode('utf-8') # utf-8 encoded version of s 27 | >>> encoded_s 28 | b'This is \xc3\xbc\xc5\x8b\xc3\xadc0de' # result: bytes object 29 | >>> type(encoded_s) # another way to verify it 30 | 31 | >>> encoded_s.decode('utf-8') # let's revert to the original 32 | 'This is üŋíc0de' 33 | >>> bytes_obj = b"A bytes object" # a bytes object 34 | >>> type(bytes_obj) 35 | 36 | 37 | 38 | # length 39 | >>> len(str1) 40 | 49 41 | 42 | 43 | # indexing and slicing 44 | >>> s = "The trouble is you think you have time." 45 | >>> s[0] # indexing at position 0, which is the first char 46 | 'T' 47 | >>> s[5] # indexing at position 5, which is the sixth char 48 | 'r' 49 | >>> s[:4] # slicing, we specify only the stop position 50 | 'The ' 51 | >>> s[4:] # slicing, we specify only the start position 52 | 'trouble is you think you have time.' 53 | >>> s[2:14] # slicing, both start and stop positions 54 | 'e trouble is' 55 | >>> s[2:14:3] # slicing, start, stop and step (every 3 chars) 56 | 'erb ' 57 | >>> s[:] # quick way of making a copy 58 | 'The trouble is you think you have time.' 59 | 60 | 61 | # formatting 62 | >>> greet_old = 'Hello %s!' 63 | >>> greet_old % 'Fabrizio' 64 | 'Hello Fabrizio!' 65 | >>> greet_positional = 'Hello {}!' 66 | >>> greet_positional.format('Fabrizio') 67 | 'Hello Fabrizio!' 68 | >>> greet_positional = 'Hello {} {}!' 69 | >>> greet_positional.format('Fabrizio', 'Romano') 70 | 'Hello Fabrizio Romano!' 71 | >>> greet_positional_idx = 'This is {0}! {1} loves {0}!' 72 | >>> greet_positional_idx.format('Python', 'Fabrizio') 73 | 'This is Python! Fabrizio loves Python!' 74 | >>> greet_positional_idx.format('Coffee', 'Fab') 75 | 'This is Coffee! Fab loves Coffee!' 76 | >>> keyword = 'Hello, my name is {name} {last_name}' 77 | >>> keyword.format(name='Fabrizio', last_name='Romano') 78 | 'Hello, my name is Fabrizio Romano' 79 | 80 | 81 | # formatted string literals 82 | >>> name = 'Fab' 83 | >>> age = 42 84 | >>> f"Hello! My name is {name} and I'm {age}" 85 | "Hello! My name is Fab and I'm 42" 86 | >>> from math import pi 87 | >>> f"No arguing with {pi}, it's irrational..." 88 | "No arguing with 3.141592653589793, it's irrational..." 89 | -------------------------------------------------------------------------------- /Chapter02/ch2/sets.py: -------------------------------------------------------------------------------- 1 | # sets.py 2 | 3 | 4 | >>> small_primes = set() # empty set 5 | >>> small_primes.add(2) # adding one element at a time 6 | >>> small_primes.add(3) 7 | >>> small_primes.add(5) 8 | >>> small_primes 9 | {2, 3, 5} 10 | >>> small_primes.add(1) # Look what I've done, 1 is not a prime! 11 | >>> small_primes 12 | {1, 2, 3, 5} 13 | >>> small_primes.remove(1) # so let's remove it 14 | >>> 3 in small_primes # membership test 15 | True 16 | >>> 4 in small_primes 17 | False 18 | >>> 4 not in small_primes # negated membership test 19 | True 20 | >>> small_primes.add(3) # trying to add 3 again 21 | >>> small_primes 22 | {2, 3, 5} # no change, duplication is not allowed 23 | >>> bigger_primes = set([5, 7, 11, 13]) # faster creation 24 | >>> small_primes | bigger_primes # union operator `|` 25 | {2, 3, 5, 7, 11, 13} 26 | >>> small_primes & bigger_primes # intersection operator `&` 27 | {5} 28 | >>> small_primes - bigger_primes # difference operator `-` 29 | {2, 3} 30 | 31 | 32 | >>> small_primes = {2, 3, 5, 5, 3} 33 | >>> small_primes 34 | {2, 3, 5} 35 | 36 | 37 | 38 | 39 | # frozenset 40 | >>> small_primes = frozenset([2, 3, 5, 7]) 41 | >>> bigger_primes = frozenset([5, 7, 11]) 42 | >>> small_primes.add(11) # we cannot add to a frozenset 43 | Traceback (most recent call last): 44 | File "", line 1, in 45 | AttributeError: 'frozenset' object has no attribute 'add' 46 | >>> small_primes.remove(2) # neither we can remove 47 | Traceback (most recent call last): 48 | File "", line 1, in 49 | AttributeError: 'frozenset' object has no attribute 'remove' 50 | >>> small_primes & bigger_primes # intersect, union, etc. allowed 51 | frozenset({5, 7}) 52 | -------------------------------------------------------------------------------- /Chapter02/ch2/tuples.py: -------------------------------------------------------------------------------- 1 | # tuples.py 2 | 3 | 4 | >>> t = () # empty tuple 5 | >>> type(t) 6 | 7 | >>> one_element_tuple = (42, ) # you need the comma! 8 | >>> three_elements_tuple = (1, 3, 5) # braces are optional here 9 | >>> a, b, c = 1, 2, 3 # tuple for multiple assignment 10 | >>> a, b, c # implicit tuple to print with one instruction 11 | (1, 2, 3) 12 | >>> 3 in three_elements_tuple # membership test 13 | True 14 | 15 | 16 | # swap 17 | >>> a, b = 1, 2 18 | >>> c = a # we need three lines and a temporary var c 19 | >>> a = b 20 | >>> b = c 21 | >>> a, b # a and b have been swapped 22 | (2, 1) 23 | 24 | # swap pythonic 25 | >>> a, b = 0, 1 26 | >>> a, b = b, a # this is the Pythonic way to do it 27 | >>> a, b 28 | (1, 0) -------------------------------------------------------------------------------- /Chapter03/ch3/any.py: -------------------------------------------------------------------------------- 1 | items = [0, None, 0.0, True, 0, 7] # True and 7 evaluate to True 2 | 3 | found = False # this is called "flag" 4 | for item in items: 5 | print('scanning item', item) 6 | if item: 7 | found = True # we update the flag 8 | break 9 | 10 | if found: # we inspect the flag 11 | print('At least one item evaluates to True') 12 | else: 13 | print('All items evaluate to False') 14 | -------------------------------------------------------------------------------- /Chapter03/ch3/binary.2.py: -------------------------------------------------------------------------------- 1 | n = 39 2 | remainders = [] 3 | while n > 0: 4 | n, remainder = divmod(n, 2) 5 | remainders.insert(0, remainder) 6 | 7 | print(remainders) 8 | -------------------------------------------------------------------------------- /Chapter03/ch3/binary.py: -------------------------------------------------------------------------------- 1 | """ 2 | 6 / 2 = 3 (remainder: 0) 3 | 3 / 2 = 1 (remainder: 1) 4 | 1 / 2 = 0 (remainder: 1) 5 | List of remainders: 0, 1, 1. 6 | Inverse is 1, 1, 0, which is also the binary repres. of 6: 110 7 | """ 8 | 9 | n = 39 10 | remainders = [] 11 | while n > 0: 12 | remainder = n % 2 # remainder of division by 2 13 | remainders.insert(0, remainder) # we keep track of remainders 14 | n //= 2 # we divide n by 2 15 | 16 | print(remainders) 17 | -------------------------------------------------------------------------------- /Chapter03/ch3/compress.py: -------------------------------------------------------------------------------- 1 | from itertools import compress 2 | data = range(10) 3 | even_selector = [1, 0] * 10 4 | odd_selector = [0, 1] * 10 5 | 6 | even_numbers = list(compress(data, even_selector)) 7 | odd_numbers = list(compress(data, odd_selector)) 8 | 9 | print(odd_selector) 10 | print(list(data)) 11 | print(even_numbers) 12 | print(odd_numbers) 13 | -------------------------------------------------------------------------------- /Chapter03/ch3/conditional.1.py: -------------------------------------------------------------------------------- 1 | late = True 2 | if late: 3 | print('I need to call my manager!') 4 | -------------------------------------------------------------------------------- /Chapter03/ch3/conditional.2.py: -------------------------------------------------------------------------------- 1 | late = False 2 | if late: 3 | print('I need to call my manager!') #1 4 | else: 5 | print('no need to call my manager...') #2 6 | -------------------------------------------------------------------------------- /Chapter03/ch3/coupons.dict.py: -------------------------------------------------------------------------------- 1 | customers = [ 2 | dict(id=1, total=200, coupon_code='F20'), # F20: fixed, £20 3 | dict(id=2, total=150, coupon_code='P30'), # P30: percent, 30% 4 | dict(id=3, total=100, coupon_code='P50'), # P50: percent, 50% 5 | dict(id=4, total=110, coupon_code='F15'), # F15: fixed, £15 6 | ] 7 | discounts = { 8 | 'F20': (0.0, 20.0), # each value is (percent, fixed) 9 | 'P30': (0.3, 0.0), 10 | 'P50': (0.5, 0.0), 11 | 'F15': (0.0, 15.0), 12 | } 13 | for customer in customers: 14 | code = customer['coupon_code'] 15 | percent, fixed = discounts.get(code, (0.0, 0.0)) 16 | customer['discount'] = percent * customer['total'] + fixed 17 | 18 | for customer in customers: 19 | print(customer['id'], customer['total'], customer['discount']) 20 | -------------------------------------------------------------------------------- /Chapter03/ch3/coupons.py: -------------------------------------------------------------------------------- 1 | customers = [ 2 | dict(id=1, total=200, coupon_code='F20'), # F20: fixed, £20 3 | dict(id=2, total=150, coupon_code='P30'), # P30: percent, 30% 4 | dict(id=3, total=100, coupon_code='P50'), # P50: percent, 50% 5 | dict(id=4, total=110, coupon_code='F15'), # F15: fixed, £15 6 | ] 7 | for customer in customers: 8 | code = customer['coupon_code'] 9 | if code == 'F20': 10 | customer['discount'] = 20.0 11 | elif code == 'F15': 12 | customer['discount'] = 15.0 13 | elif code == 'P30': 14 | customer['discount'] = customer['total'] * 0.3 15 | elif code == 'P50': 16 | customer['discount'] = customer['total'] * 0.5 17 | else: 18 | customer['discount'] = 0.0 19 | 20 | for customer in customers: 21 | print(customer['id'], customer['total'], customer['discount']) 22 | -------------------------------------------------------------------------------- /Chapter03/ch3/discount.py: -------------------------------------------------------------------------------- 1 | from datetime import date, timedelta 2 | 3 | today = date.today() 4 | tomorrow = today + timedelta(days=1) # today + 1 day is tomorrow 5 | products = [ 6 | {'sku': '1', 'expiration_date': today, 'price': 100.0}, 7 | {'sku': '2', 'expiration_date': tomorrow, 'price': 50}, 8 | {'sku': '3', 'expiration_date': today, 'price': 20}, 9 | ] 10 | 11 | for product in products: 12 | if product['expiration_date'] != today: 13 | continue 14 | product['price'] *= 0.8 # equivalent to applying 20% discount 15 | print( 16 | 'Price for sku', product['sku'], 17 | 'is now', product['price']) 18 | -------------------------------------------------------------------------------- /Chapter03/ch3/errorsalert.py: -------------------------------------------------------------------------------- 1 | # The send_email function is defined here to enable you to play with 2 | # code, but of course it doesn't send an actual email. 3 | def send_email(*a): 4 | print (*a) 5 | 6 | alert_system = 'console' # other value can be 'email' 7 | error_severity = 'critical' # other values: 'medium' or 'low' 8 | error_message = 'OMG! Something terrible happened!' 9 | 10 | if alert_system == 'console': 11 | print(error_message) #1 12 | elif alert_system == 'email': 13 | if error_severity == 'critical': 14 | send_email('admin@example.com', error_message) #2 15 | elif error_severity == 'medium': 16 | send_email('support.1@example.com', error_message) #3 17 | else: 18 | send_email('support.2@example.com', error_message) #4 19 | -------------------------------------------------------------------------------- /Chapter03/ch3/for.else.py: -------------------------------------------------------------------------------- 1 | class DriverException(Exception): 2 | pass 3 | 4 | 5 | people = [('James', 17), ('Kirk', 9), ('Lars', 13), ('Robert', 8)] 6 | for person, age in people: 7 | if age >= 18: 8 | driver = (person, age) 9 | break 10 | else: 11 | raise DriverException('Driver not found.') 12 | -------------------------------------------------------------------------------- /Chapter03/ch3/for.no.else.py: -------------------------------------------------------------------------------- 1 | class DriverException(Exception): 2 | pass 3 | 4 | 5 | people = [('James', 17), ('Kirk', 9), ('Lars', 13), ('Robert', 8)] 6 | driver = None 7 | for person, age in people: 8 | if age >= 18: 9 | driver = (person, age) 10 | break 11 | 12 | if driver is None: 13 | raise DriverException('Driver not found.') 14 | -------------------------------------------------------------------------------- /Chapter03/ch3/infinite.py: -------------------------------------------------------------------------------- 1 | from itertools import count 2 | 3 | for n in count(5, 3): 4 | if n > 20: 5 | break 6 | print(n, end=', ') # instead of newline, comma and space 7 | 8 | print() 9 | -------------------------------------------------------------------------------- /Chapter03/ch3/multiple.sequences.enumerate.py: -------------------------------------------------------------------------------- 1 | people = ['Conrad', 'Deepak', 'Heinrich', 'Tom'] 2 | ages = [29, 30, 34, 36] 3 | for position, person in enumerate(people): 4 | age = ages[position] 5 | print(person, age) 6 | -------------------------------------------------------------------------------- /Chapter03/ch3/multiple.sequences.explicit.py: -------------------------------------------------------------------------------- 1 | people = ['Conrad', 'Deepak', 'Heinrich', 'Tom'] 2 | ages = [29, 30, 34, 36] 3 | nationalities = ['Poland', 'India', 'South Africa', 'England'] 4 | for person, age, nationality in zip(people, ages, nationalities): 5 | print(person, age, nationality) 6 | -------------------------------------------------------------------------------- /Chapter03/ch3/multiple.sequences.implicit.py: -------------------------------------------------------------------------------- 1 | people = ['Conrad', 'Deepak', 'Heinrich', 'Tom'] 2 | ages = [29, 30, 34, 36] 3 | nationalities = ['Poland', 'India', 'South Africa', 'England'] 4 | for data in zip(people, ages, nationalities): 5 | person, age, nationality = data 6 | print(person, age, nationality) 7 | -------------------------------------------------------------------------------- /Chapter03/ch3/multiple.sequences.py: -------------------------------------------------------------------------------- 1 | people = ['Conrad', 'Deepak', 'Heinrich', 'Tom'] 2 | ages = [29, 30, 34, 36] 3 | for position in range(len(people)): 4 | person = people[position] 5 | age = ages[position] 6 | print(person, age) 7 | -------------------------------------------------------------------------------- /Chapter03/ch3/multiple.sequences.while.py: -------------------------------------------------------------------------------- 1 | people = ['Conrad', 'Deepak', 'Heinrich', 'Tom'] 2 | ages = [29, 30, 34, 36] 3 | position = 0 4 | while position < len(people): 5 | person = people[position] 6 | age = ages[position] 7 | print(person, age) 8 | position += 1 9 | -------------------------------------------------------------------------------- /Chapter03/ch3/multiple.sequences.zip.py: -------------------------------------------------------------------------------- 1 | people = ['Conrad', 'Deepak', 'Heinrich', 'Tom'] 2 | ages = [29, 30, 34, 36] 3 | for person, age in zip(people, ages): 4 | print(person, age) 5 | -------------------------------------------------------------------------------- /Chapter03/ch3/permutations.py: -------------------------------------------------------------------------------- 1 | from itertools import permutations 2 | print(list(permutations('ABC'))) 3 | -------------------------------------------------------------------------------- /Chapter03/ch3/primes.else.py: -------------------------------------------------------------------------------- 1 | primes = [] 2 | upto = 100 3 | for n in range(2, upto + 1): 4 | for divisor in range(2, n): 5 | if n % divisor == 0: 6 | break 7 | else: 8 | primes.append(n) 9 | 10 | print(primes) 11 | -------------------------------------------------------------------------------- /Chapter03/ch3/primes.py: -------------------------------------------------------------------------------- 1 | primes = [] # this will contain the primes in the end 2 | upto = 100 # the limit, inclusive 3 | for n in range(2, upto + 1): 4 | is_prime = True # flag, new at each iteration of outer for 5 | for divisor in range(2, n): 6 | if n % divisor == 0: 7 | is_prime = False 8 | break 9 | if is_prime: # check on flag 10 | primes.append(n) 11 | 12 | print(primes) 13 | -------------------------------------------------------------------------------- /Chapter03/ch3/range.py: -------------------------------------------------------------------------------- 1 | >>> list(range(10)) # one value: from 0 to value (excluded) 2 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 3 | >>> list(range(3, 8)) # two values: from start to stop (excluded) 4 | [3, 4, 5, 6, 7] 5 | >>> list(range(-10, 10, 4)) # three values: step is added 6 | [-10, -6, -2, 2, 6] 7 | -------------------------------------------------------------------------------- /Chapter03/ch3/simple.for.2.py: -------------------------------------------------------------------------------- 1 | surnames = ['Rivest', 'Shamir', 'Adleman'] 2 | for position in range(len(surnames)): 3 | print(position, surnames[position]) 4 | # print(surnames[position][0], end='') # try swapping prints 5 | -------------------------------------------------------------------------------- /Chapter03/ch3/simple.for.3.py: -------------------------------------------------------------------------------- 1 | surnames = ['Rivest', 'Shamir', 'Adleman'] 2 | for surname in surnames: 3 | print(surname) 4 | -------------------------------------------------------------------------------- /Chapter03/ch3/simple.for.4.py: -------------------------------------------------------------------------------- 1 | surnames = ['Rivest', 'Shamir', 'Adleman'] 2 | for position, surname in enumerate(surnames): 3 | print(position, surname) 4 | -------------------------------------------------------------------------------- /Chapter03/ch3/simple.for.py: -------------------------------------------------------------------------------- 1 | for number in [0, 1, 2, 3, 4]: 2 | print(number) 3 | 4 | 5 | # equivalent using range 6 | for number in range(5): 7 | print(number) 8 | -------------------------------------------------------------------------------- /Chapter03/ch3/switch.js: -------------------------------------------------------------------------------- 1 | switch (day_number) { 2 | case 1: 3 | case 2: 4 | case 3: 5 | case 4: 6 | case 5: 7 | day = "Weekday"; 8 | break; 9 | case 6: 10 | day = "Saturday"; 11 | break; 12 | case 0: 13 | day = "Sunday"; 14 | break; 15 | default: 16 | day = ""; 17 | alert(day_number + ' is not a valid day number.') 18 | } 19 | -------------------------------------------------------------------------------- /Chapter03/ch3/switch.py: -------------------------------------------------------------------------------- 1 | # this code is not supposed to be run 2 | if 1 <= day_number <= 5: 3 | day = 'Weekday' 4 | elif day_number == 6: 5 | day = 'Saturday' 6 | elif day_number == 0: 7 | day = 'Sunday' 8 | else: 9 | day = '' 10 | raise ValueError( 11 | str(day_number) + ' is not a valid day number.') 12 | -------------------------------------------------------------------------------- /Chapter03/ch3/taxes.py: -------------------------------------------------------------------------------- 1 | income = 15000 2 | if income < 10000: 3 | tax_coefficient = 0.0 #1 4 | elif income < 30000: 5 | tax_coefficient = 0.2 #2 6 | elif income < 100000: 7 | tax_coefficient = 0.35 #3 8 | else: 9 | tax_coefficient = 0.45 #4 10 | 11 | print('I will pay:', income * tax_coefficient, 'in taxes') 12 | -------------------------------------------------------------------------------- /Chapter03/ch3/ternary.py: -------------------------------------------------------------------------------- 1 | order_total = 247 # GBP 2 | 3 | # classic if/else form 4 | if order_total > 100: 5 | discount = 25 # GBP 6 | else: 7 | discount = 0 # GBP 8 | print(order_total, discount) 9 | 10 | # ternary operator 11 | discount = 25 if order_total > 100 else 0 12 | print(order_total, discount) 13 | -------------------------------------------------------------------------------- /Chapter04/ch4/additional.unpacking.py: -------------------------------------------------------------------------------- 1 | def additional(*args, **kwargs): 2 | print(args) 3 | print(kwargs) 4 | 5 | args1 = (1, 2, 3) 6 | args2 = [4, 5] 7 | kwargs1 = dict(option1=10, option2=20) 8 | kwargs2 = {'option3': 30} 9 | 10 | additional(*args1, *args2, **kwargs1, **kwargs2) 11 | -------------------------------------------------------------------------------- /Chapter04/ch4/arguments.all.kwonly.py: -------------------------------------------------------------------------------- 1 | def func_with_kwonly(a, b=42, *args, c, d=256, **kwargs): 2 | print('a, b:', a, b) 3 | print('c, d:', c, d) 4 | print('args:', args) 5 | print('kwargs:', kwargs) 6 | 7 | # both calls equivalent 8 | func_with_kwonly(3, 42, c=0, d=1, *(7, 9, 11), e='E', f='F') 9 | func_with_kwonly(3, 42, *(7, 9, 11), c=0, d=1, e='E', f='F') 10 | -------------------------------------------------------------------------------- /Chapter04/ch4/arguments.all.py: -------------------------------------------------------------------------------- 1 | def func(a, b, c=7, *args, **kwargs): 2 | print('a, b, c:', a, b, c) 3 | print('args:', args) 4 | print('kwargs:', kwargs) 5 | 6 | func(1, 2, 3, *(5, 7, 9), **{'A': 'a', 'B': 'b'}) 7 | func(1, 2, 3, 5, 7, 9, A='a', B='b') # same as previous one 8 | -------------------------------------------------------------------------------- /Chapter04/ch4/arguments.default.error.py: -------------------------------------------------------------------------------- 1 | def func(a, b=4, c=88): 2 | print(a, b, c) 3 | 4 | func(b=1, c=2, 42) # positional argument after keyword one 5 | 6 | """ 7 | File "arguments.default.error.py", line 4 8 | func(b=1, c=2, 42) 9 | ^ 10 | SyntaxError: non-keyword arg after keyword arg 11 | """ 12 | -------------------------------------------------------------------------------- /Chapter04/ch4/arguments.default.py: -------------------------------------------------------------------------------- 1 | def func(a, b=4, c=88): 2 | print(a, b, c) 3 | 4 | func(1) # prints: 1 4 88 5 | func(b=5, a=7, c=9) # prints: 7 5 9 6 | func(42, c=9) # prints: 42 4 9 7 | func(42, 43, 44) # prints: 42, 43, 44 8 | -------------------------------------------------------------------------------- /Chapter04/ch4/arguments.defaults.mutable.intermediate.call.py: -------------------------------------------------------------------------------- 1 | def func(a=[], b={}): 2 | print(a) 3 | print(b) 4 | print('#' * 12) 5 | a.append(len(a)) # this will affect a's default value 6 | b[len(a)] = len(a) # and this will affect b's one 7 | 8 | func() 9 | func(a=[1, 2, 3], b={'B': 1}) 10 | func() 11 | -------------------------------------------------------------------------------- /Chapter04/ch4/arguments.defaults.mutable.no.trap.py: -------------------------------------------------------------------------------- 1 | def func(a=None): 2 | if a is None: 3 | a = [] 4 | # do whatever you want with `a` ... 5 | -------------------------------------------------------------------------------- /Chapter04/ch4/arguments.defaults.mutable.py: -------------------------------------------------------------------------------- 1 | def func(a=[], b={}): 2 | print(a) 3 | print(b) 4 | print('#' * 12) 5 | a.append(len(a)) # this will affect a's default value 6 | b[len(a)] = len(a) # and this will affect b's one 7 | 8 | func() 9 | func() 10 | func() 11 | -------------------------------------------------------------------------------- /Chapter04/ch4/arguments.keyword.only.py: -------------------------------------------------------------------------------- 1 | def kwo(*a, c): 2 | print(a, c) 3 | 4 | kwo(1, 2, 3, c=7) # prints: (1, 2, 3) 7 5 | kwo(c=4) # prints: () 4 6 | # kwo(1, 2) # breaks, invalid syntax, with the following error 7 | # TypeError: kwo() missing 1 required keyword-only argument: 'c' 8 | 9 | def kwo2(a, b=42, *, c): 10 | print(a, b, c) 11 | 12 | kwo2(3, b=7, c=99) # prints: 3 7 99 13 | kwo2(3, c=13) # prints: 3 42 13 14 | # kwo2(3, 23) # breaks, invalid syntax, with the following error 15 | # TypeError: kwo2() missing 1 required keyword-only argument: 'c' 16 | -------------------------------------------------------------------------------- /Chapter04/ch4/arguments.keyword.py: -------------------------------------------------------------------------------- 1 | def func(a, b, c): 2 | print(a, b, c) 3 | 4 | func(a=1, c=2, b=3) # prints: 1 3 2 5 | -------------------------------------------------------------------------------- /Chapter04/ch4/arguments.positional.py: -------------------------------------------------------------------------------- 1 | def func(a, b, c): 2 | print(a, b, c) 3 | 4 | func(1, 2, 3) # prints: 1 2 3 5 | -------------------------------------------------------------------------------- /Chapter04/ch4/arguments.variable.db.py: -------------------------------------------------------------------------------- 1 | def connect(**options): 2 | conn_params = { 3 | 'host': options.get('host', '127.0.0.1'), 4 | 'port': options.get('port', 5432), 5 | 'user': options.get('user', ''), 6 | 'pwd': options.get('pwd', ''), 7 | } 8 | print(conn_params) 9 | # we then connect to the db (commented out) 10 | # db.connect(**conn_params) 11 | 12 | connect() 13 | connect(host='127.0.0.42', port=5433) 14 | connect(port=5431, user='fab', pwd='gandalf') 15 | -------------------------------------------------------------------------------- /Chapter04/ch4/arguments.variable.keyword.py: -------------------------------------------------------------------------------- 1 | def func(**kwargs): 2 | print(kwargs) 3 | 4 | # All calls equivalent. They print: {'a': 1, 'b': 42} 5 | func(a=1, b=42) 6 | func(**{'a': 1, 'b': 42}) 7 | func(**dict(a=1, b=42)) 8 | -------------------------------------------------------------------------------- /Chapter04/ch4/arguments.variable.positional.py: -------------------------------------------------------------------------------- 1 | def minimum(*n): 2 | # print(type(n)) # n is a tuple 3 | if n: # explained after the code 4 | mn = n[0] 5 | for value in n[1:]: 6 | if value < mn: 7 | mn = value 8 | print(mn) 9 | 10 | minimum(1, 3, -7, 9) # n = (1, 3, -7, 9) - prints: -7 11 | minimum() # n = () - prints: nothing 12 | -------------------------------------------------------------------------------- /Chapter04/ch4/arguments.variable.positional.unpacking.py: -------------------------------------------------------------------------------- 1 | def func(*args): 2 | print(args) 3 | 4 | values = (1, 3, -7, 9) 5 | func(values) # equivalent to: func((1, 3, -7, 9)) 6 | func(*values) # equivalent to: func(1, 3, -7, 9) 7 | -------------------------------------------------------------------------------- /Chapter04/ch4/data.science.example.py: -------------------------------------------------------------------------------- 1 | def do_report(data_source): 2 | # fetch and prepare data 3 | data = fetch_data(data_source) 4 | parsed_data = parse_data(data) 5 | filtered_data = filter_data(parsed_data) 6 | polished_data = polish_data(filtered_data) 7 | 8 | # run algorithms on data 9 | final_data = analyse(polished_data) 10 | 11 | # create and return report 12 | report = Report(final_data) 13 | return report 14 | 15 | 16 | if __name__ == "__main__": 17 | 18 | print( 19 | "Please don't call the `do_report` function. " 20 | "It's just and example." 21 | ) 22 | -------------------------------------------------------------------------------- /Chapter04/ch4/docstrings.py: -------------------------------------------------------------------------------- 1 | def square(n): 2 | """Return the square of a number n. """ 3 | return n ** 2 4 | 5 | def get_username(userid): 6 | """Return the username of a user given their id. """ 7 | return db.get(user_id=userid).username 8 | 9 | 10 | def connect(host, port, user, password): 11 | """Connect to a database. 12 | 13 | Connect to a PostgreSQL database directly, using the given 14 | parameters. 15 | 16 | :param host: The host IP. 17 | :param port: The desired port. 18 | :param user: The connection username. 19 | :param password: The connection password. 20 | :return: The connection object. 21 | """ 22 | # body of the function here... 23 | return connection 24 | -------------------------------------------------------------------------------- /Chapter04/ch4/filter.lambda.py: -------------------------------------------------------------------------------- 1 | def get_multiples_of_five(n): 2 | return list(filter(lambda k: not k % 5, range(n))) 3 | 4 | 5 | print(get_multiples_of_five(50)) 6 | -------------------------------------------------------------------------------- /Chapter04/ch4/filter.regular.py: -------------------------------------------------------------------------------- 1 | def is_multiple_of_five(n): 2 | return not n % 5 3 | 4 | 5 | def get_multiples_of_five(n): 6 | return list(filter(is_multiple_of_five, range(n))) 7 | 8 | 9 | print(get_multiples_of_five(50)) 10 | -------------------------------------------------------------------------------- /Chapter04/ch4/func.attributes.py: -------------------------------------------------------------------------------- 1 | def multiplication(a, b=1): 2 | """Return a multiplied by b. """ 3 | return a * b 4 | 5 | 6 | if __name__ == "__main__": 7 | 8 | special_attributes = [ 9 | "__doc__", "__name__", "__qualname__", "__module__", 10 | "__defaults__", "__code__", "__globals__", "__dict__", 11 | "__closure__", "__annotations__", "__kwdefaults__", 12 | ] 13 | 14 | for attribute in special_attributes: 15 | print(attribute, '->', getattr(multiplication, attribute)) 16 | -------------------------------------------------------------------------------- /Chapter04/ch4/func_from.py: -------------------------------------------------------------------------------- 1 | from lib.funcdef import square, cube 2 | 3 | print(square(10)) 4 | print(cube(10)) 5 | -------------------------------------------------------------------------------- /Chapter04/ch4/func_import.py: -------------------------------------------------------------------------------- 1 | import lib.funcdef 2 | 3 | 4 | print(lib.funcdef.square(10)) 5 | print(lib.funcdef.cube(10)) 6 | -------------------------------------------------------------------------------- /Chapter04/ch4/key.points.argument.passing.py: -------------------------------------------------------------------------------- 1 | x = 3 2 | def func(y): 3 | print(y) 4 | 5 | func(x) # prints: 3 6 | -------------------------------------------------------------------------------- /Chapter04/ch4/key.points.assignment.py: -------------------------------------------------------------------------------- 1 | x = 3 2 | def func(x): 3 | x = 7 # defining a local x, not changing the global one 4 | 5 | func(x) 6 | print(x) # prints: 3 7 | -------------------------------------------------------------------------------- /Chapter04/ch4/key.points.mutable.assignment.py: -------------------------------------------------------------------------------- 1 | x = [1, 2, 3] 2 | def func(x): 3 | x[1] = 42 # this changes the caller! 4 | x = 'something else' # this points x to a new string object 5 | 6 | func(x) 7 | print(x) # still prints: [1, 42, 3] 8 | -------------------------------------------------------------------------------- /Chapter04/ch4/key.points.mutable.py: -------------------------------------------------------------------------------- 1 | x = [1, 2, 3] 2 | def func(x): 3 | x[1] = 42 # this affects the caller! 4 | 5 | func(x) 6 | print(x) # prints: [1, 42, 3] 7 | -------------------------------------------------------------------------------- /Chapter04/ch4/lambda.explained.py: -------------------------------------------------------------------------------- 1 | """ 2 | myfunc = lambda [parameter_list]: expression 3 | 4 | def myfunc([parameter_list]): 5 | return expression 6 | """ 7 | 8 | # example 1: adder 9 | def adder(a, b): 10 | return a + b 11 | 12 | # is equivalent to: 13 | adder_lambda = lambda a, b: a + b 14 | 15 | # example 2: to uppercase 16 | def to_upper(s): 17 | return s.upper() 18 | 19 | # is equivalent to: 20 | to_upper_lambda = lambda s: s.upper() 21 | 22 | 23 | if __name__ == "__main__": 24 | 25 | print(adder(3, 4)) 26 | print(adder_lambda(3, 4)) 27 | 28 | print(to_upper("Hello")) 29 | print(to_upper_lambda("Hello")) 30 | -------------------------------------------------------------------------------- /Chapter04/ch4/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter04/ch4/lib/__init__.py -------------------------------------------------------------------------------- /Chapter04/ch4/lib/funcdef.py: -------------------------------------------------------------------------------- 1 | def square(n): 2 | return n ** 2 3 | 4 | 5 | def cube(n): 6 | return n ** 3 7 | -------------------------------------------------------------------------------- /Chapter04/ch4/matrix.multiplication.func.py: -------------------------------------------------------------------------------- 1 | # this function could also be defined in another module 2 | def matrix_mul(a, b): 3 | return [[sum(i * j for i, j in zip(r, c)) for c in zip(*b)] 4 | for r in a] 5 | 6 | 7 | a = [[1, 2], [3, 4]] 8 | b = [[5, 1], [2, 1]] 9 | 10 | c = matrix_mul(a, b) 11 | print(c) 12 | -------------------------------------------------------------------------------- /Chapter04/ch4/matrix.multiplication.nofunc.py: -------------------------------------------------------------------------------- 1 | a = [[1, 2], [3, 4]] 2 | b = [[5, 1], [2, 1]] 3 | 4 | c = [[sum(i * j for i, j in zip(r, c)) for c in zip(*b)] 5 | for r in a] 6 | print(c) 7 | -------------------------------------------------------------------------------- /Chapter04/ch4/no.side.effects.py: -------------------------------------------------------------------------------- 1 | # This file is not meant to be run 2 | >>> numbers = [4, 1, 7, 5] 3 | >>> sorted(numbers) # won't sort the original `numbers` list 4 | [1, 4, 5, 7] 5 | >>> numbers # let's verify 6 | [4, 1, 7, 5] # good, untouched 7 | >>> numbers.sort() # this will act on the list 8 | >>> numbers 9 | [1, 4, 5, 7] 10 | -------------------------------------------------------------------------------- /Chapter04/ch4/primes.py: -------------------------------------------------------------------------------- 1 | from math import sqrt, ceil 2 | 3 | 4 | def get_primes(n): 5 | """Calculate a list of primes up to n (included). """ 6 | primelist = [] 7 | for candidate in range(2, n + 1): 8 | is_prime = True 9 | root = ceil(sqrt(candidate)) # division limit 10 | for prime in primelist: # we try only the primes 11 | if prime > root: # no need to check any further 12 | break 13 | if candidate % prime == 0: 14 | is_prime = False 15 | break 16 | if is_prime: 17 | primelist.append(candidate) 18 | return primelist 19 | 20 | 21 | if __name__ == "__main__": 22 | 23 | def test(): 24 | primes = get_primes(10**3) 25 | primes2 = [ 26 | 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 27 | 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 28 | 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 29 | 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 30 | 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 31 | 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 32 | 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 33 | 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 34 | 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 35 | 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 36 | 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 37 | 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 38 | 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 39 | 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 40 | 941, 947, 953, 967, 971, 977, 983, 991, 997 41 | ] 42 | return primes == primes2 43 | 44 | print(test()) 45 | 46 | print(get_primes(100)) 47 | -------------------------------------------------------------------------------- /Chapter04/ch4/recursive.factorial.py: -------------------------------------------------------------------------------- 1 | def factorial(n): 2 | if n in (0, 1): # base case 3 | return 1 4 | return factorial(n - 1) * n # recursive case 5 | 6 | 7 | print([factorial(n) for n in range(10)]) 8 | -------------------------------------------------------------------------------- /Chapter04/ch4/return.multiple.py: -------------------------------------------------------------------------------- 1 | def moddiv(a, b): 2 | return a // b, a % b 3 | 4 | 5 | def test(n=10**4): 6 | from random import choice, random, randint 7 | m = 10 ** 6 8 | for x in range(n): 9 | a = choice((randint(0, m), m * random())) 10 | b = 0 11 | while not b: 12 | b = choice((randint(1, m), m * random())) 13 | 14 | r = divmod(a, b) 15 | r2 = moddiv(a, b) 16 | if r != r2: 17 | print('Difference: ', a, b, r, r2) 18 | 19 | 20 | if __name__ == "__main__": 21 | 22 | test(10 ** 6) 23 | print('Done') 24 | 25 | print(moddiv(20, 7)) # prints (2, 6) 26 | -------------------------------------------------------------------------------- /Chapter04/ch4/return.none.py: -------------------------------------------------------------------------------- 1 | def func(): 2 | pass 3 | 4 | func() # the return of this call won't be collected. It's lost. 5 | a = func() # the return of this one instead is collected into `a` 6 | print(a) # prints: None 7 | -------------------------------------------------------------------------------- /Chapter04/ch4/return.single.value.2.py: -------------------------------------------------------------------------------- 1 | from functools import reduce 2 | from operator import mul 3 | 4 | 5 | def factorial(n): 6 | return reduce(mul, range(1, n + 1), 1) 7 | 8 | 9 | f5 = factorial(5) # f5 = 120 10 | print(f5) 11 | print([factorial(k) for k in range(10)]) 12 | -------------------------------------------------------------------------------- /Chapter04/ch4/return.single.value.py: -------------------------------------------------------------------------------- 1 | def factorial(n): 2 | if n in (0, 1): 3 | return 1 4 | result = n 5 | for k in range(2, n): 6 | result *= k 7 | return result 8 | 9 | f5 = factorial(5) # f5 = 120 10 | print(f5) 11 | -------------------------------------------------------------------------------- /Chapter04/ch4/scoping.level.1.py: -------------------------------------------------------------------------------- 1 | def my_function(): 2 | test = 1 # this is defined in the local scope of the function 3 | print('my_function:', test) 4 | 5 | 6 | test = 0 # this is defined in the global scope 7 | my_function() 8 | print('global:', test) 9 | -------------------------------------------------------------------------------- /Chapter04/ch4/scoping.level.2.global.py: -------------------------------------------------------------------------------- 1 | def outer(): 2 | test = 1 # outer scope 3 | 4 | def inner(): 5 | global test 6 | test = 2 # global scope 7 | print('inner:', test) 8 | 9 | inner() 10 | print('outer:', test) 11 | 12 | 13 | test = 0 # global scope 14 | outer() 15 | print('global:', test) 16 | -------------------------------------------------------------------------------- /Chapter04/ch4/scoping.level.2.nonlocal.py: -------------------------------------------------------------------------------- 1 | def outer(): 2 | test = 1 # outer scope 3 | 4 | def inner(): 5 | nonlocal test 6 | test = 2 # nearest enclosing scope (which is 'outer') 7 | print('inner:', test) 8 | 9 | inner() 10 | print('outer:', test) 11 | 12 | 13 | test = 0 # global scope 14 | outer() 15 | print('global:', test) 16 | -------------------------------------------------------------------------------- /Chapter04/ch4/scoping.level.2.py: -------------------------------------------------------------------------------- 1 | def outer(): 2 | test = 1 # outer scope 3 | 4 | def inner(): 5 | test = 2 # inner scope 6 | print('inner:', test) 7 | 8 | inner() 9 | print('outer:', test) 10 | 11 | 12 | test = 0 # global scope 13 | outer() 14 | print('global:', test) 15 | -------------------------------------------------------------------------------- /Chapter04/ch4/vat.function.py: -------------------------------------------------------------------------------- 1 | def calculate_price_with_vat(price, vat): 2 | return price * (100 + vat) / 100 3 | 4 | 5 | if __name__ == "__main__": 6 | print(calculate_price_with_vat(100, 20)) 7 | -------------------------------------------------------------------------------- /Chapter04/ch4/vat.py: -------------------------------------------------------------------------------- 1 | price = 100 # GBP, no VAT 2 | final_price1 = price * 1.2 3 | final_price2 = price + price / 5.0 4 | final_price3 = price * (100 + 20) / 100.0 5 | final_price4 = price + price * 0.2 6 | 7 | 8 | if __name__ == "__main__": 9 | for var, value in sorted(vars().items()): 10 | if 'price' in var: 11 | print(var, value) 12 | -------------------------------------------------------------------------------- /Chapter05/ch5/alias.py: -------------------------------------------------------------------------------- 1 | # this code won't run 2 | 3 | >>> range(7) 4 | range(0, 7) 5 | >>> list(range(7)) # put all elements in a list to view them 6 | [0, 1, 2, 3, 4, 5, 6] 7 | >>> _ = list # create an "alias" to list 8 | >>> _(range(7)) # same as list(range(7)) 9 | [0, 1, 2, 3, 4, 5, 6] 10 | -------------------------------------------------------------------------------- /Chapter05/ch5/decorate.sort.undecorate.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | 3 | _ = list 4 | 5 | 6 | students = [ 7 | dict(id=0, credits=dict(math=9, physics=6, history=7)), 8 | dict(id=1, credits=dict(math=6, physics=7, latin=10)), 9 | dict(id=2, credits=dict(history=8, physics=9, chemistry=10)), 10 | dict(id=3, credits=dict(math=5, physics=5, geography=7)), 11 | ] 12 | 13 | 14 | def decorate(student): 15 | # create a 2-tuple (sum of credits, student) from student dict 16 | return (sum(student['credits'].values()), student) 17 | pprint(decorate(students[0])) 18 | 19 | 20 | def undecorate(decorated_student): 21 | # discard sum of credits, return original student dict 22 | return decorated_student[1] 23 | 24 | 25 | students = sorted(map(decorate, students), reverse=True) 26 | students = _(map(undecorate, students)) 27 | pprint(students) 28 | -------------------------------------------------------------------------------- /Chapter05/ch5/dictionary.comprehensions.duplicates.py: -------------------------------------------------------------------------------- 1 | word = 'Hello' 2 | 3 | swaps = {c: c.swapcase() for c in word} 4 | 5 | print(swaps) # prints: {'H': 'h', 'e': 'E', 'l': 'L', 'o': 'O'} 6 | -------------------------------------------------------------------------------- /Chapter05/ch5/dictionary.comprehensions.positions.py: -------------------------------------------------------------------------------- 1 | word = 'Hello' 2 | 3 | positions = {c: k for k, c in enumerate(word)} 4 | 5 | print(positions) # prints: {'H': 0, 'e': 1, 'l': 3, 'o': 4} 6 | -------------------------------------------------------------------------------- /Chapter05/ch5/dictionary.comprehensions.py: -------------------------------------------------------------------------------- 1 | from string import ascii_lowercase 2 | 3 | 4 | lettermap = dict((c, k) for k, c in enumerate(ascii_lowercase, 1)) 5 | # lettermap = {c: k for k, c in enumerate(ascii_lowercase, 1)} 6 | 7 | 8 | from pprint import pprint 9 | pprint(lettermap) 10 | -------------------------------------------------------------------------------- /Chapter05/ch5/even.squares.py: -------------------------------------------------------------------------------- 1 | # using map and filter 2 | sq1 = list( 3 | map(lambda n: n ** 2, filter(lambda n: not n % 2, range(10))) 4 | ) 5 | 6 | # equivalent, but using list comprehensions 7 | sq2 = [n ** 2 for n in range(10) if not n % 2] 8 | 9 | print(sq1, sq1 == sq2) # prints: [0, 4, 16, 36, 64] True 10 | -------------------------------------------------------------------------------- /Chapter05/ch5/fibonacci.elegant.py: -------------------------------------------------------------------------------- 1 | def fibonacci(N): 2 | """Return all fibonacci numbers up to N. """ 3 | a, b = 0, 1 4 | while a <= N: 5 | yield a 6 | a, b = b, a + b 7 | 8 | 9 | print(list(fibonacci(0))) # [0] 10 | print(list(fibonacci(1))) # [0, 1, 1] 11 | print(list(fibonacci(50))) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] 12 | -------------------------------------------------------------------------------- /Chapter05/ch5/fibonacci.first.py: -------------------------------------------------------------------------------- 1 | def fibonacci(N): 2 | """Return all fibonacci numbers up to N. """ 3 | result = [0] 4 | next_n = 1 5 | while next_n <= N: 6 | result.append(next_n) 7 | next_n = sum(result[-2:]) 8 | return result 9 | 10 | 11 | print(fibonacci(0)) # [0] 12 | print(fibonacci(1)) # [0, 1, 1] 13 | print(fibonacci(50)) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] 14 | -------------------------------------------------------------------------------- /Chapter05/ch5/fibonacci.second.py: -------------------------------------------------------------------------------- 1 | def fibonacci(N): 2 | """Return all fibonacci numbers up to N. """ 3 | yield 0 4 | if N == 0: 5 | return 6 | a = 0 7 | b = 1 8 | while b <= N: 9 | yield b 10 | a, b = b, a + b 11 | 12 | 13 | print(list(fibonacci(0))) # [0] 14 | print(list(fibonacci(1))) # [0, 1, 1] 15 | print(list(fibonacci(50))) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] 16 | -------------------------------------------------------------------------------- /Chapter05/ch5/filter.py: -------------------------------------------------------------------------------- 1 | # This is not a valid Python module - Don't run it. 2 | 3 | >>> _ = list 4 | >>> test = [2, 5, 8, 0, 0, 1, 0] 5 | >>> _(filter(None, test)) 6 | [2, 5, 8, 1] 7 | >>> _(filter(lambda x: x, test)) # equivalent to previous one 8 | [2, 5, 8, 1] 9 | >>> _(filter(lambda x: x > 4, test)) # keep only items > 4 10 | [5, 8] 11 | -------------------------------------------------------------------------------- /Chapter05/ch5/first.n.squares.manual.method.py: -------------------------------------------------------------------------------- 1 | def get_squares_gen(n): 2 | for x in range(n): 3 | yield x ** 2 4 | 5 | squares = get_squares_gen(3) 6 | print(squares.__next__()) # prints: 0 7 | print(squares.__next__()) # prints: 1 8 | print(squares.__next__()) # prints: 4 9 | # the following raises StopIteration, the generator is exhausted, 10 | # any further call to next will keep raising StopIteration 11 | print(squares.__next__()) 12 | -------------------------------------------------------------------------------- /Chapter05/ch5/first.n.squares.manual.py: -------------------------------------------------------------------------------- 1 | def get_squares_gen(n): 2 | for x in range(n): 3 | yield x ** 2 4 | 5 | squares = get_squares_gen(4) # this creates a generator object 6 | print(squares) # 7 | print(next(squares)) # prints: 0 8 | print(next(squares)) # prints: 1 9 | print(next(squares)) # prints: 4 10 | print(next(squares)) # prints: 9 11 | # the following raises StopIteration, the generator is exhausted, 12 | # any further call to next will keep raising StopIteration 13 | print(next(squares)) 14 | -------------------------------------------------------------------------------- /Chapter05/ch5/first.n.squares.py: -------------------------------------------------------------------------------- 1 | def get_squares(n): # classic function approach 2 | return [x ** 2 for x in range(n)] 3 | 4 | print(get_squares(10)) 5 | 6 | 7 | def get_squares_gen(n): # generator approach 8 | for x in range(n): 9 | yield x ** 2 # we yield, we don't return 10 | 11 | print(list(get_squares_gen(10))) 12 | -------------------------------------------------------------------------------- /Chapter05/ch5/functions.py: -------------------------------------------------------------------------------- 1 | 2 | def gcd(a, b): 3 | """Calculate the Greatest Common Divisor of (a, b). """ 4 | while b != 0: 5 | a, b = b, a % b 6 | return a 7 | -------------------------------------------------------------------------------- /Chapter05/ch5/gen.filter.py: -------------------------------------------------------------------------------- 1 | cubes = [x**3 for x in range(10)] 2 | 3 | odd_cubes1 = filter(lambda cube: cube % 2, cubes) 4 | odd_cubes2 = (cube for cube in cubes if cube % 2) 5 | -------------------------------------------------------------------------------- /Chapter05/ch5/gen.map.filter.py: -------------------------------------------------------------------------------- 1 | # finds the cubes of all multiples of 3 or 5 below N 2 | N = 20 3 | 4 | cubes1 = map( 5 | lambda n: (n, n**3), 6 | filter(lambda n: n % 3 == 0 or n % 5 == 0, range(N)) 7 | ) 8 | 9 | cubes2 = ( 10 | (n, n**3) for n in range(N) if n % 3 == 0 or n % 5 == 0) 11 | 12 | print(list(cubes1)) 13 | print(list(cubes2)) 14 | -------------------------------------------------------------------------------- /Chapter05/ch5/gen.map.py: -------------------------------------------------------------------------------- 1 | def adder(*n): 2 | return sum(n) 3 | 4 | s1 = sum(map(lambda *n: adder(*n), range(100), range(1, 101))) 5 | s2 = sum(adder(*n) for n in zip(range(100), range(1, 101))) 6 | -------------------------------------------------------------------------------- /Chapter05/ch5/gen.send.preparation.py: -------------------------------------------------------------------------------- 1 | def counter(start=0): 2 | n = start 3 | while True: 4 | yield n 5 | n += 1 6 | 7 | c = counter() 8 | print(next(c)) # prints: 0 9 | print(next(c)) # prints: 1 10 | print(next(c)) # prints: 2 11 | -------------------------------------------------------------------------------- /Chapter05/ch5/gen.send.preparation.stop.py: -------------------------------------------------------------------------------- 1 | stop = False 2 | 3 | def counter(start=0): 4 | n = start 5 | while not stop: 6 | yield n 7 | n += 1 8 | 9 | c = counter() 10 | print(next(c)) # prints: 0 11 | print(next(c)) # prints: 1 12 | stop = True 13 | print(next(c)) # raises StopIteration 14 | -------------------------------------------------------------------------------- /Chapter05/ch5/gen.send.py: -------------------------------------------------------------------------------- 1 | def counter(start=0): 2 | n = start 3 | while True: 4 | result = yield n # A 5 | print(type(result), result) # B 6 | if result == 'Q': 7 | break 8 | n += 1 9 | 10 | c = counter() 11 | print(next(c)) # C 12 | print(c.send('Wow!')) # D 13 | print(next(c)) # E 14 | print(c.send('Q')) # F 15 | -------------------------------------------------------------------------------- /Chapter05/ch5/gen.yield.for.py: -------------------------------------------------------------------------------- 1 | def print_squares(start, end): 2 | for n in range(start, end): 3 | yield n ** 2 4 | 5 | for n in print_squares(2, 5): 6 | print(n) 7 | -------------------------------------------------------------------------------- /Chapter05/ch5/gen.yield.from.py: -------------------------------------------------------------------------------- 1 | def print_squares(start, end): 2 | yield from (n ** 2 for n in range(start, end)) 3 | 4 | for n in print_squares(2, 5): 5 | print(n) 6 | -------------------------------------------------------------------------------- /Chapter05/ch5/gen.yield.return.py: -------------------------------------------------------------------------------- 1 | 2 | def geometric_progression(a, q): 3 | k = 0 4 | while True: 5 | result = a * q**k 6 | if result <= 100000: 7 | yield result 8 | else: 9 | return 10 | k += 1 11 | 12 | 13 | for n in geometric_progression(2, 5): 14 | print(n) 15 | -------------------------------------------------------------------------------- /Chapter05/ch5/generator.expressions.py: -------------------------------------------------------------------------------- 1 | # This is not a valid Python module - Don't run it. 2 | 3 | >>> _ = list 4 | >>> cubes = [k**3 for k in range(10)] # regular list 5 | >>> cubes 6 | [0, 1, 8, 27, 64, 125, 216, 343, 512, 729] 7 | >>> type(cubes) 8 | 9 | >>> cubes_gen = (k**3 for k in range(10)) # create as generator 10 | >>> cubes_gen 11 | at 0x103fb5a98> 12 | >>> type(cubes_gen) 13 | 14 | >>> _(cubes_gen) # this will exhaust the generator 15 | [0, 1, 8, 27, 64, 125, 216, 343, 512, 729] 16 | >>> _(cubes_gen) # nothing more to give 17 | [] 18 | -------------------------------------------------------------------------------- /Chapter05/ch5/map.example.py: -------------------------------------------------------------------------------- 1 | # This is not a valid Python module - Don't run it. 2 | 3 | >>> _ = list 4 | >>> map(lambda *a: a, range(3)) # 1 iterable 5 | # Not useful! Let's use alias 6 | >>> _(map(lambda *a: a, range(3))) # 1 iterable 7 | [(0,), (1,), (2,)] 8 | >>> _(map(lambda *a: a, range(3), 'abc')) # 2 iterables 9 | [(0, 'a'), (1, 'b'), (2, 'c')] 10 | >>> _(map(lambda *a: a, range(3), 'abc', range(4, 7))) # 3 11 | [(0, 'a', 4), (1, 'b', 5), (2, 'c', 6)] 12 | >>> # map stops at the shortest iterator 13 | >>> _(map(lambda *a: a, (), 'abc')) # empty tuple is shortest 14 | [] 15 | >>> _(map(lambda *a: a, (1, 2), 'abc')) # (1, 2) shortest 16 | [(1, 'a'), (2, 'b')] 17 | >>> _(map(lambda *a: a, (1, 2, 3, 4), 'abc')) # 'abc' shortest 18 | [(1, 'a'), (2, 'b'), (3, 'c')] 19 | -------------------------------------------------------------------------------- /Chapter05/ch5/maxims.py: -------------------------------------------------------------------------------- 1 | # This is not a valid Python module - Don't run it. 2 | 3 | >>> _ = list 4 | >>> a = [5, 9, 2, 4, 7] 5 | >>> b = [3, 7, 1, 9, 2] 6 | >>> c = [6, 8, 0, 5, 3] 7 | >>> maxs = map(lambda n: max(*n), zip(a, b, c)) 8 | >>> _(maxs) 9 | [6, 9, 2, 9, 7] 10 | -------------------------------------------------------------------------------- /Chapter05/ch5/pairs.for.loop.py: -------------------------------------------------------------------------------- 1 | 2 | items = 'ABCD' 3 | pairs = [] 4 | 5 | for a in range(len(items)): 6 | for b in range(a, len(items)): 7 | pairs.append((items[a], items[b])) 8 | 9 | print(pairs) 10 | -------------------------------------------------------------------------------- /Chapter05/ch5/pairs.list.comprehension.py: -------------------------------------------------------------------------------- 1 | 2 | items = 'ABCD' 3 | pairs = [(items[a], items[b]) 4 | for a in range(len(items)) for b in range(a, len(items))] 5 | 6 | print(pairs) 7 | -------------------------------------------------------------------------------- /Chapter05/ch5/performances.map.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | mx = 2 * 10 ** 7 4 | 5 | t = time() 6 | absloop = [] 7 | for n in range(mx): 8 | absloop.append(abs(n)) 9 | print('for loop: {:.4f} s'.format(time() - t)) 10 | 11 | t = time() 12 | abslist = [abs(n) for n in range(mx)] 13 | print('list comprehension: {:.4f} s'.format(time() - t)) 14 | 15 | t = time() 16 | absmap = list(map(abs, range(mx))) 17 | print('map: {:.4f} s'.format(time() - t)) 18 | 19 | print(absloop == abslist == absmap) 20 | -------------------------------------------------------------------------------- /Chapter05/ch5/performances.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | mx = 5000 4 | 5 | t = time() # start time for the for loop 6 | floop = [] 7 | for a in range(1, mx): 8 | for b in range(a, mx): 9 | floop.append(divmod(a, b)) 10 | print('for loop: {:.4f} s'.format(time() - t)) # elapsed time 11 | 12 | 13 | t = time() # start time for the list comprehension 14 | compr = [ 15 | divmod(a, b) for a in range(1, mx) for b in range(a, mx)] 16 | print('list comprehension: {:.4f} s'.format(time() - t)) 17 | 18 | 19 | t = time() # start time for the generator expression 20 | gener = list( 21 | divmod(a, b) for a in range(1, mx) for b in range(a, mx)) 22 | print('generator expression: {:.4f} s'.format(time() - t)) 23 | 24 | 25 | # verify correctness of results and number of items in each list 26 | print( 27 | floop == compr == gener, len(floop), len(compr), len(gener) 28 | ) 29 | -------------------------------------------------------------------------------- /Chapter05/ch5/pythagorean.triple.comprehension.py: -------------------------------------------------------------------------------- 1 | from math import sqrt 2 | # this step is the same as before 3 | mx = 10 4 | triples = [(a, b, sqrt(a**2 + b**2)) 5 | for a in range(1, mx) for b in range(a, mx)] 6 | # here we combine filter and map in one CLEAN list comprehension 7 | triples = [(a, b, int(c)) for a, b, c in triples if c.is_integer()] 8 | 9 | print(triples) # prints: [(3, 4, 5), (6, 8, 10)] 10 | -------------------------------------------------------------------------------- /Chapter05/ch5/pythagorean.triple.generation.for.py: -------------------------------------------------------------------------------- 1 | from functions import gcd 2 | 3 | 4 | def gen_triples(N): 5 | for m in range(1, int(N**.5) + 1): # 1 6 | for n in range(1, m): # 2 7 | if (m - n) % 2 and gcd(m, n) == 1: # 3 8 | c = m**2 + n**2 # 4 9 | if c <= N: # 5 10 | a = m**2 - n**2 # 6 11 | b = 2 * m * n # 7 12 | yield (a, b, c) # 8 13 | 14 | 15 | triples = sorted( 16 | gen_triples(50), key=lambda *triple: sum(*triple)) # 9 17 | print(triples) 18 | -------------------------------------------------------------------------------- /Chapter05/ch5/pythagorean.triple.generation.py: -------------------------------------------------------------------------------- 1 | from functions import gcd 2 | 3 | 4 | N = 50 5 | 6 | triples = sorted( # 1 7 | ((a, b, c) for a, b, c in ( # 2 8 | ((m**2 - n**2), (2 * m * n), (m**2 + n**2)) # 3 9 | for m in range(1, int(N**.5) + 1) # 4 10 | for n in range(1, m) # 5 11 | if (m - n) % 2 and gcd(m, n) == 1 # 6 12 | ) if c <= N), key=lambda *triple: sum(*triple) # 7 13 | ) 14 | 15 | 16 | print(triples) 17 | -------------------------------------------------------------------------------- /Chapter05/ch5/pythagorean.triple.int.py: -------------------------------------------------------------------------------- 1 | from math import sqrt 2 | 3 | mx = 10 4 | triples = [(a, b, sqrt(a**2 + b**2)) 5 | for a in range(1, mx) for b in range(a, mx)] 6 | triples = filter(lambda triple: triple[2].is_integer(), triples) 7 | 8 | # this will make the third number in the tuples integer 9 | triples = list( 10 | map(lambda triple: triple[:2] + (int(triple[2]), ), triples)) 11 | 12 | print(triples) # prints: [(3, 4, 5), (6, 8, 10)] 13 | -------------------------------------------------------------------------------- /Chapter05/ch5/pythagorean.triple.py: -------------------------------------------------------------------------------- 1 | from math import sqrt 2 | 3 | # this will generate all possible pairs 4 | mx = 10 5 | triples = [(a, b, sqrt(a**2 + b**2)) 6 | for a in range(1, mx) for b in range(a, mx)] 7 | # this will filter out all non pythagorean triples 8 | triples = list( 9 | filter(lambda triple: triple[2].is_integer(), triples)) 10 | 11 | print(triples) # prints: [(3, 4, 5.0), (6, 8, 10.0)] 12 | -------------------------------------------------------------------------------- /Chapter05/ch5/scopes.for.py: -------------------------------------------------------------------------------- 1 | s = 0 2 | for A in range(5): 3 | s += A 4 | print(A) # prints: 4 5 | print(globals()) 6 | -------------------------------------------------------------------------------- /Chapter05/ch5/scopes.noglobal.py: -------------------------------------------------------------------------------- 1 | ex1 = [A for A in range(5)] 2 | print(A) # breaks: NameError: name 'A' is not defined 3 | -------------------------------------------------------------------------------- /Chapter05/ch5/scopes.py: -------------------------------------------------------------------------------- 1 | A = 100 2 | ex1 = [A for A in range(5)] 3 | print(A) # prints: 100 4 | 5 | ex2 = list(A for A in range(5)) 6 | print(A) # prints: 100 7 | 8 | ex3 = dict((A, 2 * A) for A in range(5)) 9 | print(A) # prints: 100 10 | 11 | ex4 = set(A for A in range(5)) 12 | print(A) # prints: 100 13 | 14 | s = 0 15 | for A in range(5): 16 | s += A 17 | print(A) # prints: 4 18 | -------------------------------------------------------------------------------- /Chapter05/ch5/set.comprehensions.py: -------------------------------------------------------------------------------- 1 | word = 'Hello' 2 | 3 | letters1 = set(c for c in word) 4 | letters2 = {c for c in word} 5 | print(letters1) # prints: {'H', 'o', 'e', 'l'} 6 | print(letters1 == letters2) # prints: True 7 | -------------------------------------------------------------------------------- /Chapter05/ch5/squares.comprehension.py: -------------------------------------------------------------------------------- 1 | # This is not a valid Python module - Don't run it. 2 | 3 | >>> [n ** 2 for n in range(10)] 4 | [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 5 | -------------------------------------------------------------------------------- /Chapter05/ch5/squares.map.py: -------------------------------------------------------------------------------- 1 | # This is not a valid Python module - Don't run it. 2 | 3 | >>> _ = list 4 | # If you code like this you are not a Python dev! ;) 5 | >>> squares = [] 6 | >>> for n in range(10): 7 | ... squares.append(n ** 2) 8 | ... 9 | >>> squares 10 | [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 11 | 12 | # This is better, one line, nice and readable 13 | >>> squares = map(lambda n: n**2, range(10)) 14 | >>> _(squares) 15 | [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 16 | -------------------------------------------------------------------------------- /Chapter05/ch5/squares.py: -------------------------------------------------------------------------------- 1 | def square1(n): 2 | return n ** 2 # squaring through the power operator 3 | 4 | def square2(n): 5 | return n * n # squaring through multiplication 6 | -------------------------------------------------------------------------------- /Chapter05/ch5/sum.example.2.py: -------------------------------------------------------------------------------- 1 | s = sum([n**2 for n in range(10**8)]) # this is killed 2 | s = sum(n**2 for n in range(10**8)) # this succeeds 3 | 4 | print(s) # prints: 333333328333333350000000 5 | -------------------------------------------------------------------------------- /Chapter05/ch5/sum.example.py: -------------------------------------------------------------------------------- 1 | s1 = sum([n**2 for n in range(10**6)]) 2 | s2 = sum((n**2 for n in range(10**6))) 3 | s3 = sum(n**2 for n in range(10**6)) 4 | 5 | print(s1==s2==s3) 6 | -------------------------------------------------------------------------------- /Chapter05/ch5/zip.grades.py: -------------------------------------------------------------------------------- 1 | # This is not a valid Python module - Don't run it. 2 | 3 | >>> _ = list 4 | >>> grades = [18, 23, 30, 27] 5 | >>> avgs = [22, 21, 29, 24] 6 | >>> _(zip(avgs, grades)) 7 | [(22, 18), (21, 23), (29, 30), (24, 27)] 8 | >>> _(map(lambda *a: a, avgs, grades)) # equivalent to zip 9 | [(22, 18), (21, 23), (29, 30), (24, 27)] 10 | -------------------------------------------------------------------------------- /Chapter06/ch6/decorators/decorators.factory.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | def max_result(threshold): 4 | def decorator(func): 5 | @wraps(func) 6 | def wrapper(*args, **kwargs): 7 | result = func(*args, **kwargs) 8 | if result > threshold: 9 | print( 10 | 'Result is too big ({0}). Max allowed is {1}.' 11 | .format(result, threshold)) 12 | return result 13 | return wrapper 14 | return decorator 15 | 16 | @max_result(75) 17 | def cube(n): 18 | return n ** 3 19 | 20 | @max_result(100) 21 | def square(n): 22 | return n ** 2 23 | 24 | @max_result(1000) 25 | def multiply(a, b): 26 | return a * b 27 | 28 | print(cube(5)) 29 | -------------------------------------------------------------------------------- /Chapter06/ch6/decorators/syntax.py: -------------------------------------------------------------------------------- 1 | # This is not a valid Python module - Don't run it. 2 | 3 | # ONE DECORATOR 4 | def func(arg1, arg2, ...): 5 | pass 6 | 7 | func = decorator(func) 8 | 9 | # is equivalent to the following: 10 | 11 | @decorator 12 | def func(arg1, arg2, ...): 13 | pass 14 | 15 | 16 | # TWO DECORATORS 17 | def func(arg1, arg2, ...): 18 | pass 19 | 20 | func = deco1(deco2(func)) 21 | 22 | # is equivalent to the following: 23 | 24 | @deco1 25 | @deco2 26 | def func(arg1, arg2, ...): 27 | pass 28 | 29 | # DECORATOR WITH ARGUMENTS 30 | def func(arg1, arg2, ...): 31 | pass 32 | 33 | func = decoarg(arg_a, arg_b)(func) 34 | 35 | # is equivalent to the following: 36 | 37 | @decoarg(arg_a, arg_b) 38 | def func(arg1, arg2, ...): 39 | pass 40 | -------------------------------------------------------------------------------- /Chapter06/ch6/decorators/time.measure.arguments.py: -------------------------------------------------------------------------------- 1 | from time import sleep, time 2 | 3 | 4 | def f(sleep_time=0.1): 5 | sleep(sleep_time) 6 | 7 | def measure(func, *args, **kwargs): 8 | t = time() 9 | func(*args, **kwargs) 10 | print(func.__name__, 'took:', time() - t) 11 | 12 | measure(f, sleep_time=0.3) # f took: 0.30056095123291016 13 | measure(f, 0.2) # f took: 0.2033553123474121 14 | -------------------------------------------------------------------------------- /Chapter06/ch6/decorators/time.measure.deco1.py: -------------------------------------------------------------------------------- 1 | from time import sleep, time 2 | 3 | def f(sleep_time=0.1): 4 | sleep(sleep_time) 5 | 6 | def measure(func): 7 | def wrapper(*args, **kwargs): 8 | t = time() 9 | func(*args, **kwargs) 10 | print(func.__name__, 'took:', time() - t) 11 | return wrapper 12 | 13 | f = measure(f) # decoration point 14 | 15 | f(0.2) # f took: 0.20372915267944336 16 | f(sleep_time=0.3) # f took: 0.30455899238586426 17 | print(f.__name__) # wrapper <- ouch! 18 | -------------------------------------------------------------------------------- /Chapter06/ch6/decorators/time.measure.deco2.py: -------------------------------------------------------------------------------- 1 | from time import sleep, time 2 | from functools import wraps 3 | 4 | 5 | def measure(func): 6 | @wraps(func) 7 | def wrapper(*args, **kwargs): 8 | t = time() 9 | func(*args, **kwargs) 10 | print(func.__name__, 'took:', time() - t) 11 | return wrapper 12 | 13 | 14 | @measure 15 | def f(sleep_time=0.1): 16 | """I'm a cat. I love to sleep! """ 17 | sleep(sleep_time) 18 | 19 | 20 | f(sleep_time=0.3) # f took: 0.3010902404785156 21 | print(f.__name__, ':', f.__doc__) # f : I'm a cat. I love to sleep! 22 | -------------------------------------------------------------------------------- /Chapter06/ch6/decorators/time.measure.dry.py: -------------------------------------------------------------------------------- 1 | from time import sleep, time 2 | 3 | 4 | def f(): 5 | sleep(.3) 6 | 7 | def g(): 8 | sleep(.5) 9 | 10 | def measure(func): 11 | t = time() 12 | func() 13 | print(func.__name__, 'took:', time() - t) 14 | 15 | measure(f) # f took: 0.30434322357177734 16 | measure(g) # g took: 0.5048270225524902 17 | -------------------------------------------------------------------------------- /Chapter06/ch6/decorators/time.measure.start.py: -------------------------------------------------------------------------------- 1 | from time import sleep, time 2 | 3 | 4 | def f(): 5 | sleep(.3) 6 | 7 | def g(): 8 | sleep(.5) 9 | 10 | 11 | t = time() 12 | f() 13 | print('f took:', time() - t) # f took: 0.3001396656036377 14 | 15 | t = time() 16 | g() 17 | print('g took:', time() - t) # g took: 0.5039339065551758 18 | -------------------------------------------------------------------------------- /Chapter06/ch6/decorators/two.decorators.py: -------------------------------------------------------------------------------- 1 | from time import sleep, time 2 | from functools import wraps 3 | 4 | def measure(func): 5 | @wraps(func) 6 | def wrapper(*args, **kwargs): 7 | t = time() 8 | result = func(*args, **kwargs) 9 | print(func.__name__, 'took:', time() - t) 10 | return result 11 | return wrapper 12 | 13 | def max_result(func): 14 | @wraps(func) 15 | def wrapper(*args, **kwargs): 16 | result = func(*args, **kwargs) 17 | if result > 100: 18 | print('Result is too big ({0}). Max allowed is 100.' 19 | .format(result)) 20 | return result 21 | return wrapper 22 | 23 | @measure 24 | @max_result 25 | def cube(n): 26 | return n ** 3 27 | 28 | print(cube(2)) 29 | print(cube(5)) 30 | -------------------------------------------------------------------------------- /Chapter06/ch6/iterators/iterator.py: -------------------------------------------------------------------------------- 1 | class OddEven: 2 | 3 | def __init__(self, data): 4 | self._data = data 5 | self.indexes = (list(range(0, len(data), 2)) + 6 | list(range(1, len(data), 2))) 7 | 8 | def __iter__(self): 9 | return self 10 | 11 | def __next__(self): 12 | if self.indexes: 13 | return self._data[self.indexes.pop(0)] 14 | raise StopIteration 15 | 16 | oddeven = OddEven('ThIsIsCoOl!') 17 | print(''.join(c for c in oddeven)) # TIICO!hssol 18 | 19 | oddeven = OddEven('HoLa') # or manually... 20 | it = iter(oddeven) # this calls oddeven.__iter__ internally 21 | print(next(it)) # H 22 | print(next(it)) # L 23 | print(next(it)) # o 24 | print(next(it)) # a 25 | 26 | 27 | # make sure it works correctly with edge cases 28 | oddeven = OddEven('') 29 | print(' '.join(c for c in oddeven)) 30 | 31 | oddeven = OddEven('A') 32 | print(' '.join(c for c in oddeven)) 33 | 34 | oddeven = OddEven('Ab') 35 | print(' '.join(c for c in oddeven)) 36 | 37 | oddeven = OddEven('AbC') 38 | print(' '.join(c for c in oddeven)) 39 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/class.attribute.shadowing.py: -------------------------------------------------------------------------------- 1 | class Point: 2 | x = 10 3 | y = 7 4 | 5 | p = Point() 6 | print(p.x) # 10 (from class attribute) 7 | print(p.y) # 7 (from class attribute) 8 | 9 | p.x = 12 # p gets its own `x` attribute 10 | print(p.x) # 12 (now found on the instance) 11 | print(Point.x) # 10 (class attribute still the same) 12 | 13 | del p.x # we delete instance attribute 14 | print(p.x) # 10 (now search has to go again to find class attr) 15 | 16 | p.z = 3 # let's make it a 3D point 17 | print(p.z) # 3 18 | 19 | print(Point.z) 20 | # AttributeError: type object 'Point' has no attribute 'z' 21 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/class.init.py: -------------------------------------------------------------------------------- 1 | class Rectangle: 2 | def __init__(self, side_a, side_b): 3 | self.side_a = side_a 4 | self.side_b = side_b 5 | 6 | def area(self): 7 | return self.side_a * self.side_b 8 | 9 | r1 = Rectangle(10, 4) 10 | print(r1.side_a, r1.side_b) # 10 4 11 | print(r1.area()) # 40 12 | 13 | r2 = Rectangle(7, 3) 14 | print(r2.area()) # 21 15 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/class.issubclass.isinstance.py: -------------------------------------------------------------------------------- 1 | from class_inheritance import Car, RaceCar, F1Car 2 | 3 | 4 | car = Car() 5 | racecar = RaceCar() 6 | f1car = F1Car() 7 | cars = [(car, 'car'), (racecar, 'racecar'), (f1car, 'f1car')] 8 | car_classes = [Car, RaceCar, F1Car] 9 | 10 | for car, car_name in cars: 11 | for class_ in car_classes: 12 | belongs = isinstance(car, class_) 13 | msg = 'is a' if belongs else 'is not a' 14 | print(car_name, msg, class_.__name__) 15 | 16 | """ Prints: 17 | car is a Car 18 | car is not a RaceCar 19 | car is not a F1Car 20 | racecar is a Car 21 | racecar is a RaceCar 22 | racecar is not a F1Car 23 | f1car is a Car 24 | f1car is a RaceCar 25 | f1car is a F1Car 26 | """ 27 | 28 | print('-' * 60) 29 | 30 | for class1 in car_classes: 31 | for class2 in car_classes: 32 | is_subclass = issubclass(class1, class2) 33 | msg = '{0} a subclass of'.format( 34 | 'is' if is_subclass else 'is not') 35 | print(class1.__name__, msg, class2.__name__) 36 | 37 | """ Prints: 38 | Car is a subclass of Car 39 | Car is not a subclass of RaceCar 40 | Car is not a subclass of F1Car 41 | RaceCar is a subclass of Car 42 | RaceCar is a subclass of RaceCar 43 | RaceCar is not a subclass of F1Car 44 | F1Car is a subclass of Car 45 | F1Car is a subclass of RaceCar 46 | F1Car is a subclass of F1Car 47 | """ 48 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/class.methods.factory.py: -------------------------------------------------------------------------------- 1 | class Point: 2 | 3 | def __init__(self, x, y): 4 | self.x = x 5 | self.y = y 6 | 7 | @classmethod 8 | def from_tuple(cls, coords): # cls is Point 9 | return cls(*coords) 10 | 11 | @classmethod 12 | def from_point(cls, point): # cls is Point 13 | return cls(point.x, point.y) 14 | 15 | 16 | p = Point.from_tuple((3, 7)) 17 | print(p.x, p.y) # 3 7 18 | q = Point.from_point(p) 19 | print(q.x, q.y) # 3 7 20 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/class.methods.split.py: -------------------------------------------------------------------------------- 1 | class StringUtil: 2 | 3 | @classmethod 4 | def is_palindrome(cls, s, case_insensitive=True): 5 | s = cls._strip_string(s) 6 | # For case insensitive comparison, we lower-case s 7 | if case_insensitive: 8 | s = s.lower() 9 | return cls._is_palindrome(s) 10 | 11 | @staticmethod 12 | def _strip_string(s): 13 | return ''.join(c for c in s if c.isalnum()) 14 | 15 | @staticmethod 16 | def _is_palindrome(s): 17 | for c in range(len(s) // 2): 18 | if s[c] != s[-c -1]: 19 | return False 20 | return True 21 | 22 | @staticmethod 23 | def get_unique_words(sentence): 24 | return set(sentence.split()) 25 | 26 | print(StringUtil.is_palindrome('A nut for a jar of tuna')) # True 27 | print(StringUtil.is_palindrome('A nut for a jar of beans')) # False 28 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/class.namespaces.py: -------------------------------------------------------------------------------- 1 | class Person: 2 | species = 'Human' 3 | 4 | 5 | print(Person.species) # Human 6 | Person.alive = True # Added dynamically! 7 | print(Person.alive) # True 8 | 9 | man = Person() 10 | print(man.species) # Human (inherited) 11 | print(man.alive) # True (inherited) 12 | 13 | Person.alive = False 14 | print(man.alive) # False (inherited) 15 | 16 | man.name = 'Darth' 17 | man.surname = 'Vader' 18 | print(man.name, man.surname) # Darth Vader 19 | 20 | print(Person.name) 21 | # This doesn't work. We try to access an instance attribute 22 | # from a class. Doing the opposite works, but this will give 23 | # the following error: 24 | # AttributeError: type object 'Person' has no attribute 'name' 25 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/class.price.py: -------------------------------------------------------------------------------- 1 | class Price: 2 | def final_price(self, vat, discount=0): 3 | """Returns price after applying vat and fixed discount.""" 4 | return (self.net_price * (100 + vat) / 100) - discount 5 | 6 | p1 = Price() 7 | p1.net_price = 100 8 | print(Price.final_price(p1, 20, 10)) # 110 (100 * 1.2 - 10) 9 | print(p1.final_price(20, 10)) # equivalent 10 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/class.self.py: -------------------------------------------------------------------------------- 1 | class Square: 2 | side = 8 3 | def area(self): # self is a reference to an instance 4 | return self.side ** 2 5 | 6 | sq = Square() 7 | print(sq.area()) # 64 (side is found on the class) 8 | print(Square.area(sq)) # 64 (equivalent to sq.area()) 9 | 10 | sq.side = 10 11 | print(sq.area()) # 100 (side is found on the instance) 12 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/class_inheritance.py: -------------------------------------------------------------------------------- 1 | class Engine: 2 | def start(self): 3 | pass 4 | 5 | def stop(self): 6 | pass 7 | 8 | class ElectricEngine(Engine): # Is-A Engine 9 | pass 10 | 11 | class V8Engine(Engine): # Is-A Engine 12 | pass 13 | 14 | class Car: 15 | engine_cls = Engine 16 | 17 | def __init__(self): 18 | self.engine = self.engine_cls() # Has-A Engine 19 | 20 | def start(self): 21 | print( 22 | 'Starting engine {0} for car {1}... Wroom, wroom!' 23 | .format( 24 | self.engine.__class__.__name__, 25 | self.__class__.__name__) 26 | ) 27 | self.engine.start() 28 | 29 | def stop(self): 30 | self.engine.stop() 31 | 32 | class RaceCar(Car): # Is-A Car 33 | engine_cls = V8Engine 34 | 35 | class CityCar(Car): # Is-A Car 36 | engine_cls = ElectricEngine 37 | 38 | class F1Car(RaceCar): # Is-A RaceCar and also Is-A Car 39 | pass # engine_cls same as parent 40 | 41 | car = Car() 42 | racecar = RaceCar() 43 | citycar = CityCar() 44 | f1car = F1Car() 45 | cars = [car, racecar, citycar, f1car] 46 | 47 | for car in cars: 48 | car.start() 49 | 50 | """ Prints: 51 | Starting engine Engine for car Car... Wroom, wroom! 52 | Starting engine V8Engine for car RaceCar... Wroom, wroom! 53 | Starting engine ElectricEngine for car CityCar... Wroom, wroom! 54 | Starting engine V8Engine for car F1Car... Wroom, wroom! 55 | """ 56 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/dataclass.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class Body: 6 | '''Class to represent a physical body.''' 7 | name: str 8 | mass: float = 0. # Kg 9 | speed: float = 1. # m/s 10 | 11 | def kinetic_energy(self) -> float: 12 | return (self.mass * self.speed ** 2) / 2 13 | 14 | 15 | body = Body('Ball', 19, 3.1415) 16 | print(body.kinetic_energy()) # 93.755711375 Joule 17 | print(body) # Body(name='Ball', mass=19, speed=3.1415) 18 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/mro.py: -------------------------------------------------------------------------------- 1 | class A: 2 | label = 'a' 3 | 4 | class B(A): 5 | pass # was: label = 'b' 6 | 7 | class C(A): 8 | label = 'c' 9 | 10 | class D(B, C): 11 | pass 12 | 13 | 14 | d = D() 15 | print(d.label) # 'c' 16 | print(d.__class__.mro()) # notice another way to get the MRO 17 | # prints: 18 | # [, , 19 | # , , ] 20 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/mro.simple.py: -------------------------------------------------------------------------------- 1 | class A: 2 | label = 'a' 3 | 4 | class B(A): 5 | label = 'b' 6 | 7 | class C(A): 8 | label = 'c' 9 | 10 | class D(B, C): 11 | pass 12 | 13 | d = D() 14 | print(d.label) # Hypothetically this could be either 'b' or 'c' 15 | print(d.__class__.mro()) # notice another way to get the MRO 16 | # prints: 17 | # [, , 18 | # , , ] 19 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/multiple.inheritance.py: -------------------------------------------------------------------------------- 1 | class Shape: 2 | geometric_type = 'Generic Shape' 3 | 4 | def area(self): # This acts as placeholder for the interface 5 | raise NotImplementedError 6 | 7 | def get_geometric_type(self): 8 | return self.geometric_type 9 | 10 | 11 | class Plotter: 12 | 13 | def plot(self, ratio, topleft): 14 | # Imagine some nice plotting logic here... 15 | print('Plotting at {}, ratio {}.'.format( 16 | topleft, ratio)) 17 | 18 | 19 | class Polygon(Shape, Plotter): # base class for polygons 20 | geometric_type = 'Polygon' 21 | 22 | class RegularPolygon(Polygon): # Is-A Polygon 23 | geometric_type = 'Regular Polygon' 24 | 25 | def __init__(self, side): 26 | self.side = side 27 | 28 | 29 | class RegularHexagon(RegularPolygon): # Is-A RegularPolygon 30 | geometric_type = 'RegularHexagon' 31 | 32 | def area(self): 33 | return 1.5 * (3 ** .5 * self.side ** 2) 34 | 35 | 36 | class Square(RegularPolygon): # Is-A RegularPolygon 37 | geometric_type = 'Square' 38 | 39 | def area(self): 40 | return self.side * self.side 41 | 42 | 43 | hexagon = RegularHexagon(10) 44 | print(hexagon.area()) # 259.8076211353316 45 | print(hexagon.get_geometric_type()) # RegularHexagon 46 | hexagon.plot(0.8, (75, 77)) # Plotting at (75, 77), ratio 0.8. 47 | 48 | square = Square(12) 49 | print(square.area()) # 144 50 | print(square.get_geometric_type()) # Square 51 | square.plot(0.93, (74, 75)) # Plotting at (74, 75), ratio 0.93. 52 | 53 | print(square.__class__.__mro__) 54 | # prints: 55 | # (, , 56 | # , , 57 | # , ) 58 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/operator.overloading.py: -------------------------------------------------------------------------------- 1 | class Weird: 2 | def __init__(self, s): 3 | self._s = s 4 | 5 | def __len__(self): 6 | return len(self._s) 7 | 8 | def __bool__(self): 9 | return '42' in self._s 10 | 11 | 12 | weird = Weird('Hello! I am 9 years old!') 13 | print(len(weird)) # 24 14 | print(bool(weird)) # False 15 | 16 | weird2 = Weird('Hello! I am 42 years old!') 17 | print(len(weird2)) # 25 18 | print(bool(weird2)) # True 19 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/private.attrs.fixed.py: -------------------------------------------------------------------------------- 1 | class A: 2 | def __init__(self, factor): 3 | self.__factor = factor 4 | 5 | def op1(self): 6 | print('Op1 with factor {}...'.format(self.__factor)) 7 | 8 | class B(A): 9 | def op2(self, factor): 10 | self.__factor = factor 11 | print('Op2 with factor {}...'.format(self.__factor)) 12 | 13 | 14 | obj = B(100) 15 | obj.op1() # Op1 with factor 100... 16 | obj.op2(42) # Op2 with factor 42... 17 | obj.op1() # Op1 with factor 100... <- Wohoo! Now it's GOOD! 18 | 19 | print(obj.__dict__.keys()) 20 | # dict_keys(['_A__factor', '_B__factor']) 21 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/private.attrs.py: -------------------------------------------------------------------------------- 1 | class A: 2 | def __init__(self, factor): 3 | self._factor = factor 4 | 5 | def op1(self): 6 | print('Op1 with factor {}...'.format(self._factor)) 7 | 8 | class B(A): 9 | def op2(self, factor): 10 | self._factor = factor 11 | print('Op2 with factor {}...'.format(self._factor)) 12 | 13 | 14 | obj = B(100) 15 | obj.op1() # Op1 with factor 100... 16 | obj.op2(42) # Op2 with factor 42... 17 | obj.op1() # Op1 with factor 42... <- This is BAD 18 | 19 | print(obj.__dict__.keys()) 20 | # dict_keys(['_factor']) 21 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/property.py: -------------------------------------------------------------------------------- 1 | class Person: 2 | def __init__(self, age): 3 | self.age = age # anyone can modify this freely 4 | 5 | class PersonWithAccessors: 6 | def __init__(self, age): 7 | self._age = age 8 | 9 | def get_age(self): 10 | return self._age 11 | 12 | def set_age(self, age): 13 | if 18 <= age <= 99: 14 | self._age = age 15 | else: 16 | raise ValueError('Age must be within [18, 99]') 17 | 18 | class PersonPythonic: 19 | def __init__(self, age): 20 | self._age = age 21 | 22 | @property 23 | def age(self): 24 | return self._age 25 | 26 | @age.setter 27 | def age(self, age): 28 | if 18 <= age <= 99: 29 | self._age = age 30 | else: 31 | raise ValueError('Age must be within [18, 99]') 32 | 33 | person = PersonPythonic(39) 34 | print(person.age) # 39 - Notice we access as data attribute 35 | person.age = 42 # Notice we access as data attribute 36 | print(person.age) # 42 37 | person.age = 100 # ValueError: Age must be within [18, 99] 38 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/simplest.class.py: -------------------------------------------------------------------------------- 1 | class Simplest(): # when empty, the braces are optional 2 | pass 3 | 4 | print(type(Simplest)) # what type is this object? 5 | 6 | simp = Simplest() # we create an instance of Simplest: simp 7 | print(type(simp)) # what type is simp? 8 | # is simp an instance of Simplest? 9 | print(type(simp) == Simplest) # There's a better way for this 10 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/static.methods.py: -------------------------------------------------------------------------------- 1 | class StringUtil: 2 | 3 | @staticmethod 4 | def is_palindrome(s, case_insensitive=True): 5 | # we allow only letters and numbers 6 | s = ''.join(c for c in s if c.isalnum()) # Study this! 7 | # For case insensitive comparison, we lower-case s 8 | if case_insensitive: 9 | s = s.lower() 10 | for c in range(len(s) // 2): 11 | if s[c] != s[-c -1]: 12 | return False 13 | return True 14 | 15 | @staticmethod 16 | def get_unique_words(sentence): 17 | return set(sentence.split()) 18 | 19 | print(StringUtil.is_palindrome( 20 | 'Radar', case_insensitive=False)) # False: Case Sensitive 21 | print(StringUtil.is_palindrome('A nut for a jar of tuna')) # True 22 | print(StringUtil.is_palindrome('Never Odd, Or Even!')) # True 23 | print(StringUtil.is_palindrome( 24 | 'In Girum Imus Nocte Et Consumimur Igni') # Latin! Show-off! 25 | ) # True 26 | 27 | print(StringUtil.get_unique_words( 28 | 'I love palindromes. I really really love them!')) 29 | # {'them!', 'really', 'palindromes.', 'I', 'love'} 30 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/super.duplication.py: -------------------------------------------------------------------------------- 1 | class Book: 2 | 3 | def __init__(self, title, publisher, pages): 4 | self.title = title 5 | self.publisher = publisher 6 | self.pages = pages 7 | 8 | 9 | class Ebook(Book): 10 | 11 | def __init__(self, title, publisher, pages, format_): 12 | self.title = title 13 | self.publisher = publisher 14 | self.pages = pages 15 | self.format_ = format_ 16 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/super.explicit.py: -------------------------------------------------------------------------------- 1 | class Book: 2 | 3 | def __init__(self, title, publisher, pages): 4 | self.title = title 5 | self.publisher = publisher 6 | self.pages = pages 7 | 8 | 9 | class Ebook(Book): 10 | 11 | def __init__(self, title, publisher, pages, format_): 12 | Book.__init__(self, title, publisher, pages) 13 | self.format_ = format_ 14 | 15 | 16 | ebook = Ebook( 17 | 'Learn Python Programming', 'Packt Publishing', 500, 'PDF') 18 | print(ebook.title) # Learn Python Programming 19 | print(ebook.publisher) # Packt Publishing 20 | print(ebook.pages) # 500 21 | print(ebook.format_) # PDF 22 | -------------------------------------------------------------------------------- /Chapter06/ch6/oop/super.implicit.py: -------------------------------------------------------------------------------- 1 | class Book: 2 | 3 | def __init__(self, title, publisher, pages): 4 | self.title = title 5 | self.publisher = publisher 6 | self.pages = pages 7 | 8 | 9 | class Ebook(Book): 10 | 11 | def __init__(self, title, publisher, pages, format_): 12 | super().__init__(title, publisher, pages) 13 | # Another way to do the same thing is: 14 | # super(Ebook, self).__init__(title, publisher, pages) 15 | self.format_ = format_ 16 | 17 | 18 | ebook = Ebook( 19 | 'Learn Python Programming', 'Packt Publishing', 500, 'PDF') 20 | print(ebook.title) # Learn Python Programming 21 | print(ebook.publisher) # Packt Publishing 22 | print(ebook.pages) # 500 23 | print(ebook.format_) # PDF 24 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/buffer.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | buff = bytearray(os.path.getsize('fear.txt')) 5 | 6 | 7 | with open('fear.txt', 'rb') as f: 8 | f.readinto(buff) 9 | 10 | half = len(buff) // 2 11 | buff[:half] = buff[:half].upper() 12 | buff[half:] = buff[half:].lower() 13 | 14 | with open('fear_mod.txt', 'wb') as fw: 15 | fw.write(buff) 16 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/compression/content1.txt: -------------------------------------------------------------------------------- 1 | This is content1.txt -------------------------------------------------------------------------------- /Chapter07/ch7/files/compression/content2.txt: -------------------------------------------------------------------------------- 1 | This is content2.txt -------------------------------------------------------------------------------- /Chapter07/ch7/files/compression/subfolder/content3.txt: -------------------------------------------------------------------------------- 1 | This is subfolder/content3.txt -------------------------------------------------------------------------------- /Chapter07/ch7/files/compression/subfolder/content4.txt: -------------------------------------------------------------------------------- 1 | This is subfolder/content4.txt -------------------------------------------------------------------------------- /Chapter07/ch7/files/compression/tar.py: -------------------------------------------------------------------------------- 1 | import tarfile 2 | 3 | with tarfile.open('example.tar.gz', 'w:gz') as tar: 4 | tar.add('content1.txt') 5 | tar.add('content2.txt') 6 | tar.add('subfolder/content3.txt') 7 | tar.add('subfolder/content4.txt') 8 | 9 | with tarfile.open('example.tar.gz', 'r:gz') as tar: 10 | tar.extractall('extract_tar') 11 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/compression/zip.py: -------------------------------------------------------------------------------- 1 | from zipfile import ZipFile 2 | 3 | 4 | with ZipFile('example.zip', 'w') as zp: 5 | zp.write('content1.txt') 6 | zp.write('content2.txt') 7 | zp.write('subfolder/content3.txt') 8 | zp.write('subfolder/content4.txt') 9 | 10 | 11 | with ZipFile('example.zip') as zp: 12 | zp.extract('content1.txt', 'extract_zip') 13 | zp.extract('subfolder/content3.txt', 'extract_zip') 14 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/existence.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | filename = 'fear.txt' 5 | path = os.path.dirname(os.path.abspath(filename)) 6 | 7 | 8 | print(os.path.isfile(filename)) # True 9 | print(os.path.isdir(path)) # True 10 | print(path) # /Users/fab/srv/lpp/ch7/files 11 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/fear.txt: -------------------------------------------------------------------------------- 1 | An excerpt from Fear - By Thich Nhat Hanh 2 | 3 | The Present Is Free from Fear 4 | 5 | When we are not fully present, we are not really living. We’re not really there, either for our loved ones or for ourselves. If we’re not there, then where are we? We are running, running, running, even during our sleep. We run because we’re trying to escape from our fear. 6 | 7 | We cannot enjoy life if we spend our time and energy worrying about what happened yesterday and what will happen tomorrow. If we’re afraid all the time, we miss out on the wonderful fact that we’re alive and can be happy right now. In everyday life, we tend to believe that happiness is only possible in the future. We’re always looking for the “right” conditions that we don’t yet have to make us happy. We ignore what is happening right in front of us. We look for something that will make us feel more solid, more safe, more secure. But we’re afraid all the time of what the future will bring—afraid we’ll lose our jobs, our possessions, the people around us whom we love. So we wait and hope for that magical moment—always sometime in the future—when everything will be as we want it to be. We forget that life is available only in the present moment. The Buddha said, “It is possible to live happily in the present moment. It is the only moment we have.” 8 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/fixed_amount.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | from string import ascii_lowercase 3 | 4 | 5 | size = 60 # try different sizes 6 | 7 | 8 | with open('fear.txt') as stream: 9 | while True: 10 | data = stream.read(size) 11 | if data.strip(): 12 | print('===>', data, '<===') 13 | else: 14 | break 15 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/listing.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | with os.scandir('.') as it: 5 | for entry in it: 6 | print( 7 | entry.name, entry.path, 8 | 'File' if entry.is_file() else 'Folder' 9 | ) 10 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/manipulation.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | from string import ascii_letters 3 | 4 | 5 | chars = ascii_letters + ' ' 6 | 7 | 8 | def sanitize(s, chars): 9 | return ''.join(c for c in s if c in chars) 10 | 11 | 12 | def reverse(s): 13 | return s[::-1] 14 | 15 | 16 | with open('fear.txt') as stream: 17 | lines = [line.rstrip() for line in stream] 18 | 19 | 20 | # let's write the mirrored version of the file 21 | with open('raef.txt', 'w') as stream: 22 | stream.write('\n'.join(reverse(line) for line in lines)) 23 | 24 | 25 | # now we can calculate some statistics 26 | lines = [sanitize(line, chars) for line in lines] 27 | whole = ' '.join(lines) 28 | 29 | 30 | # we perform comparisons on the lowercased version of `whole` 31 | cnt = Counter(whole.lower().split()) 32 | 33 | # we can print the N most common words 34 | print(cnt.most_common(3)) 35 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/open_try.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | fh = open('fear.txt', 'rt') # r: read, t: text 4 | 5 | for line in fh.readlines(): 6 | print(line.strip()) # remove whitespace and print 7 | 8 | fh.close() 9 | 10 | 11 | # secured by try/finally 12 | try: 13 | fh = open('fear.txt', 'rt') 14 | 15 | for line in fh.readlines(): 16 | print(line.strip()) 17 | 18 | finally: 19 | fh.close() 20 | 21 | 22 | # equivalent to: 23 | try: 24 | fh = open('fear.txt') # rt is default 25 | 26 | for line in fh: # we can iterate directly on fh 27 | print(line.strip()) 28 | 29 | finally: 30 | fh.close() 31 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/open_with.py: -------------------------------------------------------------------------------- 1 | 2 | with open('fear.txt') as fh: 3 | for line in fh: 4 | print(line.strip()) 5 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/ops_create.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import os 3 | 4 | 5 | BASE_PATH = 'ops_example' # this will be our base path 6 | 7 | # let's perform an initial cleanup just in case 8 | if os.path.exists(BASE_PATH) and os.path.isdir(BASE_PATH): 9 | shutil.rmtree(BASE_PATH) 10 | 11 | 12 | os.mkdir(BASE_PATH) 13 | 14 | path_b = os.path.join(BASE_PATH, 'A', 'B') 15 | path_c = os.path.join(BASE_PATH, 'A', 'C') 16 | path_d = os.path.join(BASE_PATH, 'A', 'D') 17 | 18 | 19 | os.makedirs(path_b) 20 | os.makedirs(path_c) 21 | 22 | 23 | # we add three files in `ops_example/A/B` 24 | for filename in ('ex1.txt', 'ex2.txt', 'ex3.txt'): 25 | with open(os.path.join(path_b, filename), 'w') as stream: 26 | stream.write(f'Some content here in {filename}\n') 27 | 28 | 29 | shutil.move(path_b, path_d) 30 | 31 | 32 | # we can also rename files 33 | shutil.move( 34 | os.path.join(path_d, 'ex1.txt'), 35 | os.path.join(path_d, 'ex1d.txt') 36 | ) 37 | 38 | # now call $ tree ops_example 39 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/paths.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | filename = 'fear.txt' 5 | path = os.path.abspath(filename) 6 | 7 | 8 | print(path) 9 | 10 | print(os.path.basename(path)) 11 | print(os.path.dirname(path)) 12 | print(os.path.splitext(path)) 13 | print(os.path.split(path)) 14 | 15 | readme_path = os.path.join( 16 | os.path.dirname(path), '..', '..', 'README.rst') 17 | 18 | print(readme_path) 19 | print(os.path.normpath(readme_path)) 20 | 21 | 22 | 23 | """ 24 | /Users/fab/srv/lpp/ch7/files/fear.txt # path 25 | fear.txt # basename 26 | /Users/fab/srv/lpp/ch7/files # dirname 27 | ('/Users/fab/srv/lpp/ch7/files/fear', '.txt') # splitext 28 | ('/Users/fab/srv/lpp/ch7/files', 'fear.txt') # split 29 | /Users/fab/srv/lpp/ch7/files/../../README.rst # readme_path 30 | /Users/fab/srv/lpp/README.rst # normalized 31 | """ 32 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/print_file.py: -------------------------------------------------------------------------------- 1 | 2 | with open('print_example.txt', 'w') as fw: 3 | print('Hey I am printing into a file!!!', file=fw) 4 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/read_write.py: -------------------------------------------------------------------------------- 1 | 2 | with open('fear.txt') as f: 3 | lines = [line.rstrip() for line in f] 4 | 5 | 6 | with open('fear_copy.txt', 'w') as fw: # w - write 7 | fw.write('\n'.join(lines)) 8 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/read_write_bin.py: -------------------------------------------------------------------------------- 1 | 2 | with open('example.bin', 'wb') as fw: 3 | fw.write(b'This is binary data...') 4 | 5 | 6 | with open('example.bin', 'rb') as f: 7 | print(f.read()) # prints: b'This is binary data...' 8 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/tmp.py: -------------------------------------------------------------------------------- 1 | import os 2 | from tempfile import NamedTemporaryFile, TemporaryDirectory 3 | 4 | 5 | with TemporaryDirectory(dir='.') as td: 6 | print('Temp directory:', td) 7 | with NamedTemporaryFile(dir=td) as t: 8 | name = t.name 9 | print(os.path.abspath(name)) 10 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/walking.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | for root, dirs, files in os.walk('.'): 5 | abs_root = os.path.abspath(root) 6 | print(abs_root) 7 | 8 | if dirs: 9 | print('Directories:') 10 | for dir_ in dirs: 11 | print(dir_) 12 | print() 13 | 14 | if files: 15 | print('Files:') 16 | for filename in files: 17 | print(filename) 18 | print() 19 | -------------------------------------------------------------------------------- /Chapter07/ch7/files/write_not_exists.py: -------------------------------------------------------------------------------- 1 | 2 | with open('write_x.txt', 'x') as fw: 3 | fw.write('Writing line 1') # this succeeds 4 | 5 | 6 | with open('write_x.txt', 'x') as fw: 7 | fw.write('Writing line 2') # this fails 8 | -------------------------------------------------------------------------------- /Chapter07/ch7/io_examples/reqs.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | urls = { 5 | 'get': 'https://httpbin.org/get?title=learn+python+programming', 6 | 'headers': 'https://httpbin.org/headers', 7 | 'ip': 'https://httpbin.org/ip', 8 | 'now': 'https://now.httpbin.org/', 9 | 'user-agent': 'https://httpbin.org/user-agent', 10 | 'UUID': 'https://httpbin.org/uuid', 11 | } 12 | 13 | 14 | def get_content(title, url): 15 | resp = requests.get(url) 16 | print(f'Response for {title}') 17 | print(resp.json()) 18 | 19 | 20 | for title, url in urls.items(): 21 | get_content(title, url) 22 | print('-' * 40) 23 | -------------------------------------------------------------------------------- /Chapter07/ch7/io_examples/reqs_post.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | url = 'https://httpbin.org/post' 5 | data = dict(title='Learn Python Programming') 6 | 7 | 8 | resp = requests.post(url, data=data) 9 | print('Response for POST') 10 | print(resp.json()) 11 | -------------------------------------------------------------------------------- /Chapter07/ch7/io_examples/string_io.py: -------------------------------------------------------------------------------- 1 | import io 2 | 3 | 4 | stream = io.StringIO() 5 | stream.write('Learning Python Programming.\n') 6 | print('Become a Python ninja!', file=stream) 7 | 8 | contents = stream.getvalue() 9 | print(contents) 10 | 11 | stream.close() 12 | 13 | 14 | # better alternative, using a context manager 15 | with io.StringIO() as stream: 16 | stream.write('Learning Python Programming.\n') 17 | print('Become a Python ninja!', file=stream) 18 | 19 | contents = stream.getvalue() 20 | print(contents) 21 | -------------------------------------------------------------------------------- /Chapter07/ch7/json_examples/json_basic.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | 4 | 5 | data = { 6 | 'big_number': 2 ** 3141, 7 | 'max_float': sys.float_info.max, 8 | 'a_list': [2, 3, 5, 7], 9 | } 10 | 11 | 12 | json_data = json.dumps(data) 13 | data_out = json.loads(json_data) 14 | 15 | assert data == data_out # json and back, data matches 16 | 17 | 18 | # let's see how passing indent affects dumps. 19 | 20 | info = { 21 | 'full_name': 'Sherlock Holmes', 22 | 'address': { 23 | 'street': '221B Baker St', 24 | 'zip': 'NW1 6XE', 25 | 'city': 'London', 26 | 'country': 'UK', 27 | } 28 | } 29 | 30 | 31 | print(json.dumps(info, indent=2, sort_keys=True)) 32 | -------------------------------------------------------------------------------- /Chapter07/ch7/json_examples/json_cplx.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | class ComplexEncoder(json.JSONEncoder): 5 | def default(self, obj): 6 | if isinstance(obj, complex): 7 | return { 8 | '_meta': '_complex', 9 | 'num': [obj.real, obj.imag], 10 | } 11 | return json.JSONEncoder.default(self, obj) 12 | 13 | 14 | data = { 15 | 'an_int': 42, 16 | 'a_float': 3.14159265, 17 | 'a_complex': 3 + 4j, 18 | } 19 | 20 | json_data = json.dumps(data, cls=ComplexEncoder) 21 | 22 | print(json_data) 23 | 24 | 25 | def object_hook(obj): 26 | try: 27 | if obj['_meta'] == '_complex': 28 | return complex(*obj['num']) 29 | except (KeyError, TypeError): 30 | return obj 31 | 32 | 33 | data_out = json.loads(json_data, object_hook=object_hook) 34 | print(data_out) 35 | -------------------------------------------------------------------------------- /Chapter07/ch7/json_examples/json_datetime.py: -------------------------------------------------------------------------------- 1 | # exercise: do the same for date 2 | import json 3 | from datetime import datetime, timedelta, timezone 4 | 5 | 6 | now = datetime.now() 7 | now_tz = datetime.now(tz=timezone(timedelta(hours=1))) 8 | 9 | 10 | class DatetimeEncoder(json.JSONEncoder): 11 | def default(self, obj): 12 | if isinstance(obj, datetime): 13 | try: 14 | off = obj.utcoffset().seconds 15 | except AttributeError: 16 | off = None 17 | 18 | return { 19 | '_meta': '_datetime', 20 | 'data': obj.timetuple()[:6] + (obj.microsecond, ), 21 | 'utcoffset': off, 22 | } 23 | return json.JSONEncoder.default(self, obj) 24 | 25 | 26 | data = { 27 | 'an_int': 42, 28 | 'a_float': 3.14159265, 29 | 'a_datetime': now, 30 | 'a_datetime_tz': now_tz, 31 | } 32 | 33 | json_data = json.dumps(data, cls=DatetimeEncoder) 34 | 35 | print(json_data) 36 | 37 | 38 | def object_hook(obj): 39 | try: 40 | if obj['_meta'] == '_datetime': 41 | if obj['utcoffset'] is None: 42 | tz = None 43 | else: 44 | tz = timezone(timedelta(seconds=obj['utcoffset'])) 45 | return datetime(*obj['data'], tzinfo=tz) 46 | except (KeyError, TypeError): 47 | return obj 48 | 49 | 50 | data_out = json.loads(json_data, object_hook=object_hook) 51 | from pprint import pprint 52 | pprint(data_out, indent=2) 53 | print(data_out) 54 | 55 | assert data_out['a_datetime'] == data['a_datetime'] 56 | assert data_out['a_datetime_tz'] == data['a_datetime_tz'] 57 | -------------------------------------------------------------------------------- /Chapter07/ch7/json_examples/json_tuple.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | data_in = { 5 | 'a_tuple': (1, 2, 3, 4, 5), 6 | } 7 | 8 | 9 | json_data = json.dumps(data_in) 10 | print(json_data) # {"a_tuple": [1, 2, 3, 4, 5]} 11 | data_out = json.loads(json_data) 12 | print(data_out) # {'a_tuple': [1, 2, 3, 4, 5]} 13 | -------------------------------------------------------------------------------- /Chapter07/ch7/persistence/alchemy.py: -------------------------------------------------------------------------------- 1 | from alchemy_models import Person, Address, engine 2 | from sqlalchemy.orm import sessionmaker 3 | 4 | 5 | Session = sessionmaker(bind=engine) 6 | session = Session() 7 | 8 | 9 | # Create a couple of people 10 | anakin = Person(name='Anakin Skywalker', age=32) 11 | obi1 = Person(name='Obi-Wan Kenobi', age=40) 12 | 13 | # Add email addresses for both of them 14 | obi1.addresses = [ 15 | Address(email='obi1@example.com'), 16 | Address(email='wanwan@example.com'), 17 | ] 18 | 19 | # another way: we can simply append 20 | anakin.addresses.append(Address(email='ani@example.com')) 21 | anakin.addresses.append(Address(email='evil.dart@example.com')) 22 | anakin.addresses.append(Address(email='vader@example.com')) 23 | 24 | # Add people to the session. This adds addresses too. 25 | session.add(anakin) 26 | session.add(obi1) 27 | session.commit() 28 | 29 | 30 | # Query and display both 31 | obi1 = session.query(Person).filter( 32 | Person.name.like('Obi%') 33 | ).first() 34 | print(obi1, obi1.addresses) 35 | 36 | anakin = session.query(Person).filter( 37 | Person.name=='Anakin Skywalker' 38 | ).first() 39 | print(anakin, anakin.addresses) 40 | 41 | # capture anakin.id 42 | anakin_id = anakin.id 43 | 44 | # then remove the var 45 | del anakin 46 | 47 | 48 | def display_info(): 49 | # get all addresses first 50 | addresses = session.query(Address).all() 51 | 52 | # display results 53 | for address in addresses: 54 | print(f'{address.person.name} <{address.email}>') 55 | 56 | # display how many objects we have in total 57 | print('people: {}, addresses: {}'.format( 58 | session.query(Person).count(), 59 | session.query(Address).count()) 60 | ) 61 | 62 | 63 | display_info() 64 | 65 | # Fetch anakin directly by its id 66 | anakin = session.query(Person).get(anakin_id) 67 | 68 | # Delete anakin 69 | session.delete(anakin) 70 | session.commit() 71 | 72 | 73 | # let's do it again and see the changes 74 | display_info() 75 | -------------------------------------------------------------------------------- /Chapter07/ch7/persistence/alchemy_models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.ext.declarative import declarative_base 2 | from sqlalchemy import ( 3 | Column, Integer, String, ForeignKey, create_engine) 4 | from sqlalchemy.orm import relationship 5 | 6 | 7 | # swap these lines to work with an actual DB file 8 | # engine = create_engine('sqlite:///example.db') 9 | engine = create_engine('sqlite:///:memory:') 10 | 11 | 12 | Base = declarative_base() 13 | 14 | 15 | class Person(Base): 16 | __tablename__ = 'person' 17 | 18 | id = Column(Integer, primary_key=True) 19 | name = Column(String) 20 | age = Column(Integer) 21 | 22 | addresses = relationship( 23 | 'Address', 24 | back_populates='person', 25 | order_by='Address.email', 26 | cascade='all, delete-orphan' 27 | ) 28 | 29 | def __repr__(self): 30 | return f'{self.name}(id={self.id})' 31 | 32 | 33 | class Address(Base): 34 | __tablename__ = 'address' 35 | 36 | id = Column(Integer, primary_key=True) 37 | email = Column(String) 38 | person_id = Column(ForeignKey('person.id')) 39 | person = relationship('Person', back_populates='addresses') 40 | 41 | def __str__(self): 42 | return self.email 43 | __repr__ = __str__ 44 | 45 | 46 | Base.metadata.create_all(engine) 47 | -------------------------------------------------------------------------------- /Chapter07/ch7/persistence/pickler.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass 6 | class Person: 7 | first_name: str 8 | last_name: str 9 | id: int 10 | 11 | def greet(self): 12 | print(f'Hi, I am {self.first_name} {self.last_name}' 13 | f' and my ID is {self.id}' 14 | ) 15 | 16 | 17 | people = [ 18 | Person('Obi-Wan', 'Kenobi', 123), 19 | Person('Anakin', 'Skywalker', 456), 20 | ] 21 | 22 | 23 | # save data in binary format to a file 24 | with open('data.pickle', 'wb') as stream: 25 | pickle.dump(people, stream) 26 | 27 | 28 | # load data from a file 29 | with open('data.pickle', 'rb') as stream: 30 | peeps = pickle.load(stream) 31 | 32 | 33 | for person in peeps: 34 | person.greet() 35 | -------------------------------------------------------------------------------- /Chapter07/ch7/persistence/shelf.py: -------------------------------------------------------------------------------- 1 | import shelve 2 | 3 | 4 | class Person: 5 | def __init__(self, name, id): 6 | self.name = name 7 | self.id = id 8 | 9 | 10 | with shelve.open('shelf1.shelve') as db: 11 | db['obi1'] = Person('Obi-Wan', 123) 12 | db['ani'] = Person('Anakin', 456) 13 | db['a_list'] = [2, 3, 5] 14 | db['delete_me'] = 'we will have to delete this one...' 15 | 16 | print(list(db.keys())) # ['ani', 'a_list', 'delete_me', 'obi1'] 17 | 18 | del db['delete_me'] # gone! 19 | 20 | print(list(db.keys())) # ['ani', 'a_list', 'obi1'] 21 | 22 | print('delete_me' in db) # False 23 | print('ani' in db) # True 24 | 25 | a_list = db['a_list'] 26 | a_list.append(7) 27 | db['a_list'] = a_list 28 | 29 | print(db['a_list']) # [2, 3, 5, 7] 30 | 31 | 32 | # this way allows writeback: 33 | # working with lists is easier, but consumes more memory and 34 | # closing the file takes longer. 35 | with shelve.open('shelf2.shelve', writeback=True) as db: 36 | db['a_list'] = [11, 13, 17] 37 | db['a_list'].append(19) # in-place append! 38 | 39 | print(db['a_list']) # [11, 13, 17, 19] 40 | -------------------------------------------------------------------------------- /Chapter08/ch8/__init__.py: -------------------------------------------------------------------------------- 1 | # This is here to enable you to run tests within the `test` folder. 2 | -------------------------------------------------------------------------------- /Chapter08/ch8/api.py: -------------------------------------------------------------------------------- 1 | import os 2 | import csv 3 | from copy import deepcopy 4 | 5 | from marshmallow import Schema, fields, pre_load 6 | from marshmallow.validate import Length, Range 7 | 8 | 9 | class UserSchema(Schema): 10 | """Represent a *valid* user. """ 11 | 12 | email = fields.Email(required=True) 13 | name = fields.String(required=True, validate=Length(min=1)) 14 | age = fields.Integer( 15 | required=True, validate=Range(min=18, max=65) 16 | ) 17 | role = fields.String() 18 | 19 | @pre_load(pass_many=False) 20 | def strip_name(self, data): 21 | data_copy = deepcopy(data) 22 | 23 | try: 24 | data_copy['name'] = data_copy['name'].strip() 25 | except (AttributeError, KeyError, TypeError): 26 | pass 27 | 28 | return data_copy 29 | 30 | 31 | schema = UserSchema() 32 | 33 | 34 | def export(filename, users, overwrite=True): 35 | """Export a CSV file. 36 | 37 | Create a CSV file and fill with valid users. If `overwrite` 38 | is False and file already exists, raise IOError. 39 | """ 40 | if not overwrite and os.path.isfile(filename): 41 | raise IOError(f"'{filename}' already exists.") 42 | 43 | valid_users = get_valid_users(users) 44 | write_csv(filename, valid_users) 45 | 46 | 47 | def get_valid_users(users): 48 | """Yield one valid user at a time from users. """ 49 | yield from filter(is_valid, users) 50 | 51 | 52 | def is_valid(user): 53 | """Return whether or not the user is valid. """ 54 | return not schema.validate(user) 55 | 56 | 57 | def write_csv(filename, users): 58 | """Write a CSV given a filename and a list of users. 59 | 60 | The users are assumed to be valid for the given CSV structure. 61 | """ 62 | fieldnames = ['email', 'name', 'age', 'role'] 63 | 64 | with open(filename, 'x', newline='') as csvfile: 65 | writer = csv.DictWriter(csvfile, fieldnames=fieldnames) 66 | writer.writeheader() 67 | 68 | for user in users: 69 | writer.writerow(user) 70 | -------------------------------------------------------------------------------- /Chapter08/ch8/data.py: -------------------------------------------------------------------------------- 1 | def get_clean_data(source): 2 | 3 | data = load_data(source) 4 | cleaned_data = clean_data(data) 5 | 6 | return cleaned_data 7 | -------------------------------------------------------------------------------- /Chapter08/ch8/exceptions/first.example.py: -------------------------------------------------------------------------------- 1 | # This is not a valid Python module - Don't run it. 2 | # a few examples of exceptions 3 | 4 | >>> gen = (n for n in range(2)) 5 | >>> next(gen) 6 | 0 7 | >>> next(gen) 8 | 1 9 | >>> next(gen) 10 | Traceback (most recent call last): 11 | File "", line 1, in 12 | StopIteration 13 | >>> print(undefined_name) 14 | Traceback (most recent call last): 15 | File "", line 1, in 16 | NameError: name 'undefined_name' is not defined 17 | >>> mylist = [1, 2, 3] 18 | >>> mylist[5] 19 | Traceback (most recent call last): 20 | File "", line 1, in 21 | IndexError: list index out of range 22 | >>> mydict = {'a': 'A', 'b': 'B'} 23 | >>> mydict['c'] 24 | Traceback (most recent call last): 25 | File "", line 1, in 26 | KeyError: 'c' 27 | >>> 1 / 0 28 | Traceback (most recent call last): 29 | File "", line 1, in 30 | ZeroDivisionError: division by zero 31 | -------------------------------------------------------------------------------- /Chapter08/ch8/exceptions/for.loop.py: -------------------------------------------------------------------------------- 1 | n = 100 2 | found = False 3 | for a in range(n): 4 | if found: break 5 | for b in range(n): 6 | if found: break 7 | for c in range(n): 8 | if 42 * a + 17 * b + c == 5096: 9 | found = True 10 | print(a, b, c) # 79 99 95 11 | 12 | 13 | class ExitLoopException(Exception): 14 | pass 15 | 16 | try: 17 | n = 100 18 | for a in range(n): 19 | for b in range(n): 20 | for c in range(n): 21 | if 42 * a + 17 * b + c == 5096: 22 | raise ExitLoopException(a, b, c) 23 | except ExitLoopException as ele: 24 | print(ele) # (79, 99, 95) 25 | -------------------------------------------------------------------------------- /Chapter08/ch8/exceptions/json.example.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | json_data = '{}' # try: json_data = 2, and json_data = '{{' 4 | 5 | try: 6 | data = json.loads(json_data) 7 | except (ValueError, TypeError) as e: 8 | print(type(e), e) 9 | -------------------------------------------------------------------------------- /Chapter08/ch8/exceptions/multiple.except.py: -------------------------------------------------------------------------------- 1 | # This is not a valid Python module - Don't run it. 2 | 3 | try: 4 | # some code 5 | except Exception1: 6 | # react to Exception1 7 | except (Exception2, Exception3): 8 | # react to Exception2 or Exception3 9 | except Exception4: 10 | # react to Exception4 11 | ... 12 | -------------------------------------------------------------------------------- /Chapter08/ch8/exceptions/try.syntax.py: -------------------------------------------------------------------------------- 1 | 2 | def try_syntax(numerator, denominator): 3 | try: 4 | print(f'In the try block: {numerator}/{denominator}') 5 | result = numerator / denominator 6 | except ZeroDivisionError as zde: 7 | print(zde) 8 | else: 9 | print('The result is:', result) 10 | return result 11 | finally: 12 | print('Exiting') 13 | 14 | print(try_syntax(12, 4)) 15 | print(try_syntax(11, 0)) 16 | 17 | 18 | """ 19 | $ python try.syntax.py 20 | In the try block: 12/4 # try 21 | The result is: 3.0 # else 22 | Exiting # finally 23 | 3.0 # return within else 24 | 25 | In the try block: 11/0 # try 26 | division by zero # except 27 | Exiting # finally 28 | None # implicit return end of function 29 | """ 30 | -------------------------------------------------------------------------------- /Chapter08/ch8/profiling/triples.py: -------------------------------------------------------------------------------- 1 | 2 | def calc_triples(mx): 3 | triples = [] 4 | for a in range(1, mx + 1): 5 | for b in range(a, mx + 1): 6 | hypotenuse = calc_hypotenuse(a, b) 7 | if is_int(hypotenuse): 8 | triples.append((a, b, int(hypotenuse))) 9 | return triples 10 | 11 | 12 | def calc_hypotenuse(a, b): 13 | return (a**2 + b**2) ** .5 14 | 15 | 16 | def is_int(n): # n is expected to be a float 17 | return n.is_integer() 18 | 19 | 20 | triples = calc_triples(1000) 21 | 22 | """ 23 | $ python -m cProfile triples.py 24 | 1502538 function calls in 0.704 seconds 25 | 26 | Ordered by: standard name 27 | 28 | ncalls tottime percall filename:lineno(function) 29 | 500500 0.393 0.000 triples.py:17(calc_hypotenuse) 30 | 500500 0.096 0.000 triples.py:21(is_int) 31 | 1 0.000 0.000 triples.py:4() 32 | 1 0.176 0.176 triples.py:4(calc_triples) 33 | 1 0.000 0.000 {built-in method builtins.exec} 34 | 1034 0.000 0.000 {method 'append' of 'list' objects} 35 | 1 0.000 0.000 {method 'disable' of '_lsprof.Profil... 36 | 500500 0.038 0.000 {method 'is_integer' of 'float' objects} 37 | """ 38 | -------------------------------------------------------------------------------- /Chapter08/ch8/profiling/triples_v2.py: -------------------------------------------------------------------------------- 1 | 2 | def calc_triples(mx): 3 | triples = [] 4 | for a in range(1, mx + 1): 5 | for b in range(a, mx + 1): 6 | hypotenuse = calc_hypotenuse(a, b) 7 | if is_int(hypotenuse): 8 | triples.append((a, b, int(hypotenuse))) 9 | return triples 10 | 11 | 12 | def calc_hypotenuse(a, b): 13 | return (a*a + b*b) ** .5 14 | 15 | 16 | def is_int(n): # n is expected to be a float 17 | return n.is_integer() 18 | 19 | 20 | triples = calc_triples(1000) 21 | 22 | """ 23 | $ python -m cProfile triples.py 24 | 1502538 function calls in 0.446 seconds 25 | 26 | Ordered by: standard name 27 | 28 | ncalls tottime percall cumtime percall filename:lineno(function) 29 | 500500 0.137 0.000 0.137 0.000 triples.py:14(calc_hypotenuse) 30 | 500500 0.095 0.000 0.133 0.000 triples.py:18(is_int) 31 | 1 0.000 0.000 0.446 0.446 triples.py:4() 32 | 1 0.175 0.175 0.446 0.446 triples.py:4(calc_triples) 33 | 1 0.000 0.000 0.446 0.446 {built-in method builtins.exec} 34 | 1034 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects} 35 | 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 36 | 500500 0.038 0.000 0.038 0.000 {method 'is_integer' of 'float' objects} 37 | """ 38 | -------------------------------------------------------------------------------- /Chapter08/ch8/profiling/triples_v3.py: -------------------------------------------------------------------------------- 1 | 2 | def calc_triples(mx): 3 | triples = [] 4 | for a in range(1, mx + 1): 5 | for b in range(a, mx + 1): 6 | hypotenuse = calc_hypotenuse(a, b) 7 | if is_int(hypotenuse): 8 | triples.append((a, b, int(hypotenuse))) 9 | return triples 10 | 11 | 12 | def calc_hypotenuse(a, b): 13 | return (a*a + b*b) ** .5 14 | 15 | 16 | def is_int(n): 17 | return n == int(n) 18 | 19 | 20 | triples = calc_triples(1000) 21 | 22 | """ 23 | $ python -m cProfile triples.py 24 | 1002038 function calls in 0.433 seconds 25 | 26 | Ordered by: standard name 27 | 28 | ncalls tottime percall cumtime percall filename:lineno(function) 29 | 500500 0.130 0.000 0.130 0.000 triples.py:14(calc_hypotenuse) 30 | 500500 0.135 0.000 0.135 0.000 triples.py:21(is_int) 31 | 1 0.000 0.000 0.433 0.433 triples.py:4() 32 | 1 0.168 0.168 0.433 0.433 triples.py:4(calc_triples) 33 | 1 0.000 0.000 0.433 0.433 {built-in method builtins.exec} 34 | 1034 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects} 35 | 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 36 | """ 37 | -------------------------------------------------------------------------------- /Chapter08/ch8/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter08/ch8/tests/__init__.py -------------------------------------------------------------------------------- /Chapter09/ch9/claims_auth.py: -------------------------------------------------------------------------------- 1 | import jwt 2 | 3 | 4 | data = {'payload': 'data', 'iss': 'fab', 'aud': 'learn-python'} 5 | 6 | 7 | secret = 'secret-key' 8 | token = jwt.encode(data, secret) 9 | 10 | 11 | def decode(token, secret, issuer=None, audience=None): 12 | try: 13 | print(jwt.decode( 14 | token, secret, issuer=issuer, audience=audience)) 15 | except ( 16 | jwt.InvalidIssuerError, jwt.InvalidAudienceError 17 | ) as err: 18 | print(err) 19 | print(type(err)) 20 | 21 | 22 | decode(token, secret) 23 | 24 | # not providing the issuer won't break 25 | decode(token, secret, audience='learn-python') 26 | 27 | # not providing the audience will break 28 | decode(token, secret, issuer='fab') 29 | 30 | # both will break 31 | decode(token, secret, issuer='wrong', audience='learn-python') 32 | decode(token, secret, issuer='fab', audience='wrong') 33 | 34 | decode(token, secret, issuer='fab', audience='learn-python') 35 | 36 | 37 | """ 38 | $ python claims_auth.py 39 | Invalid audience 40 | 41 | 42 | {'payload': 'data', 'iss': 'fab', 'aud': 'learn-python'} 43 | 44 | Invalid audience 45 | 46 | 47 | Invalid issuer 48 | 49 | 50 | Invalid audience 51 | 52 | 53 | {'payload': 'data', 'iss': 'fab', 'aud': 'learn-python'} 54 | """ 55 | -------------------------------------------------------------------------------- /Chapter09/ch9/claims_time.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from time import sleep 3 | 4 | import jwt 5 | 6 | 7 | iat = datetime.utcnow() 8 | nfb = iat + timedelta(seconds=1) 9 | exp = iat + timedelta(seconds=3) 10 | 11 | 12 | data = {'payload': 'data', 'nbf': nfb, 'exp': exp, 'iat': iat} 13 | 14 | 15 | def decode(token, secret): 16 | print(datetime.utcnow().time().isoformat()) 17 | try: 18 | print(jwt.decode(token, secret)) 19 | except ( 20 | jwt.ImmatureSignatureError, jwt.ExpiredSignatureError 21 | ) as err: 22 | print(err) 23 | print(type(err)) 24 | 25 | 26 | secret = 'secret-key' 27 | token = jwt.encode(data, secret) 28 | 29 | 30 | decode(token, secret) 31 | sleep(2) 32 | decode(token, secret) 33 | sleep(2) 34 | decode(token, secret) 35 | 36 | 37 | """ 38 | $ python claims_time.py 39 | 14:04:13.469778 40 | The token is not yet valid (nbf) 41 | 42 | 43 | 14:04:15.475362 44 | {'payload': 'data', 'nbf': 1522591454, 'exp': 1522591456, 'iat': 1522591453} 45 | 46 | 14:04:17.476948 47 | Signature has expired 48 | 49 | """ 50 | -------------------------------------------------------------------------------- /Chapter09/ch9/hlib.py: -------------------------------------------------------------------------------- 1 | # NOT A PYTHON MODULE - DO NOT ATTEMPT TO RUN 2 | 3 | >>> import hashlib 4 | >>> h = hashlib.blake2b() 5 | >>> h.update(b'Hash me') 6 | >>> h.update(b' now!') 7 | >>> h.hexdigest() 8 | '56441b566db9aafcf8cdad3a4729fa4b2bfaab0ada36155ece29f52ff70e1e9d' 9 | '7f54cacfe44bc97c7e904cf79944357d023877929430bc58eb2dae168e73cedf' 10 | >>> h.digest() 11 | b'VD\x1bVm\xb9\xaa\xfc\xf8\xcd\xad:G)\xfaK+\xfa\xab\n\xda6\x15^' 12 | b'\xce)\xf5/\xf7\x0e\x1e\x9d\x7fT\xca\xcf\xe4K\xc9|~\x90L\xf7' 13 | b'\x99D5}\x028w\x92\x940\xbcX\xeb-\xae\x16\x8es\xce\xdf' 14 | >>> h.block_size 15 | 128 16 | >>> h.digest_size 17 | 64 18 | >>> h.name 19 | 'blake2b' 20 | 21 | >>> hashlib.blake2b(b'Hash me now!').hexdigest() 22 | '56441b566db9aafcf8cdad3a4729fa4b2bfaab0ada36155ece29f52ff70e1e9d' 23 | '7f54cacfe44bc97c7e904cf79944357d023877929430bc58eb2dae168e73cedf' 24 | >>> hashlib.sha256(b'Hash me now!').hexdigest() 25 | '10d561fa94a89a25ea0c7aa47708bdb353bbb062a17820292cd905a3a60d6783' 26 | 27 | >>> dk = hashlib.pbkdf2_hmac( 28 | ... 'sha256', b'Password123', os.urandom(16), 100000 29 | ... ) 30 | >>> dk.hex() 31 | 'f8715c37906df067466ce84973e6e52a955be025a59c9100d9183c4cbec27a9e' 32 | >>> hashlib.algorithms_available 33 | {'SHA512', 'SHA256', 'shake_256', 'sha3_256', 'ecdsa-with-SHA1', 34 | 'DSA-SHA', 'sha1', 'sha384', 'sha3_224', 'whirlpool', 'mdc2', 35 | 'RIPEMD160', 'shake_128', 'MD4', 'dsaEncryption', 'dsaWithSHA', 36 | 'SHA1', 'blake2s', 'md5', 'sha', 'sha224', 'SHA', 'MD5', 37 | 'sha256', 'SHA384', 'sha3_384', 'md4', 'SHA224', 'MDC2', 38 | 'sha3_512', 'sha512', 'blake2b', 'DSA', 'ripemd160'} 39 | >>> hashlib.algorithms_guaranteed 40 | {'blake2s', 'md5', 'sha224', 'sha3_512', 'shake_256', 'sha3_256', 41 | 'shake_128', 'sha256', 'sha1', 'sha512', 'blake2b', 'sha3_384', 42 | 'sha384', 'sha3_224'} 43 | 44 | >>> import os 45 | >>> h = hashlib.blake2b( 46 | ... b'Important payload', digest_size=16, key=b'secret-key', 47 | ... salt=b'random-salt', person=b'fabrizio' 48 | ... ) 49 | >>> h.hexdigest() 50 | 'c2d63ead796d0d6d734a5c3c578b6e41' 51 | -------------------------------------------------------------------------------- /Chapter09/ch9/hmc.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | import hashlib 3 | 4 | 5 | def calc_digest(key, message): 6 | key = bytes(key, 'utf-8') 7 | message = bytes(message, 'utf-8') 8 | 9 | dig = hmac.new(key, message, hashlib.sha256) 10 | return dig.hexdigest() 11 | 12 | 13 | digest = calc_digest('secret-key', 'Important Message') 14 | print(digest) 15 | -------------------------------------------------------------------------------- /Chapter09/ch9/rsa/key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAz87T1pNQnbPZLSxuHP8y0HQDcNsKpwd1LwtghPld9l0lK6f2 3 | pDNlJYwqI0il8JUfIfWE9VaO9KsxTfBtYPyLO8Tz7Nk+04KDlkQOKgpKJHrYTd+5 4 | iSre3D68IVHdxBku2VFh57uqBNJCqGBF0OgVHh/ms9f0kDVaaUNSj7kMu5sLVPWR 5 | vnZgyJN0v/LHS2y22tkXncj7wf9kC13qN36MsZ2qH4VF0f8/WTkE6zUSdTwY2ZEv 6 | K66zjmTfuFoQ8df5N5Pj4STQGk+2l6uU/kxNbBNWjYmJNp1EpdJJ3m9A1nDrRRQf 7 | M1HdckOCJVn+eYDrzD2W/bSMm/3SgqzM3o1ONQIDAQABAoIBADv9QgwUlNYhwBUZ 8 | WWRHi3y/sOqYYRfcwEhnaWKZtjVTqFJU949pQs63CscKxtSPx9/+x3Ynmbcp4F7W 9 | hEzWOr9zWNNL16YpbCEp3kjmlYa4a873QdFGLfW4qoiX4IQmnO6hXrkN5MPlitae 10 | jCkFhmse3HOYaQJkVIhEpgXVbsaNMvSa6woAfT5tVPSxDSEdB4a61zwSpssYfLMF 11 | ZFsHEV6mE6hkmfZxw22/I2I7Up0sV6f7KOgjoPaeqL6i5ibEhIWzDyQiKyW6YN3j 12 | moZNrIAHoq8oAP9wWavrpbnslp5c3SculUueVxdUv46LDy9RbmFOajdaJALFdY/D 13 | NI+X7WECgYEA+qBnI0cHmYfOqGeSVUsTPUk0uG8N2fDWh1m4GPRzU4a00Mh7nG+l 14 | 8j54FDLqQeoWyWN0808dRMakjeHNzjIEi8paHbrrWIS+4Xp8pVH56iCG9wTtbj6j 15 | ni5CeoKFtRd3NcqwTOWznR+OvE02WzqE8kDQbNmT1Y68MPf/4vEpABkCgYEA1ENo 16 | pKknQv+e9ZapWtHk6DDtq/Xw/R+VadwfGBDdIU1XQsUjlTw/igeElrDQOPVao8xL 17 | SK9vCNvPUi+W0W241ZoMGCIeywGsnixKoRx7DWNxYchcPDWQtoZtrNUwkJBW/Lha 18 | iFMQDdQU7aLYJlwi09KeJB9w7JHNpi4R5N6Kkn0CgYB0jdApOckw/1V+P9xvyiBC 19 | ah8Yfp0Ec9pwy6qwAE750zWXuwSxdcLI64BQdHU4/jJmqdgjpvFHoNG6If7iG6S9 20 | lfdeyCTW37UdiycYgw0kcsgRbLs2f+77iyvjaXvhAe6wl3hx6okjUx/ANnBG8OoO 21 | 91F2raDwuhaO92aLFfgpUQKBgAh1t1H4u+vbrEY1fwJzox/t80sLPlOYUqgpccPP 22 | yEBviK7MlKJ6CD5EFnC7E1Fx1e75UXbQJyi/OgAYjXZDXT6GKT08/uUwZ+TV9xN8 23 | wxrBf99Z0PNFX0MnEG/2/zyDxDPGVTVhuh4S+dKOzvaYbXrrxgnChwmmtf+NjoEE 24 | rZkxAoGAQvv6ZrKrhUMYIsgPVyjXe5E421cv/pj4KCsqdeclV+3vR87SwOfpej9y 25 | /gpZuEIHxzzgAz5eFgrGJjm/4VGqNEJ3PGxa6i6a0ld3Cv2j/qyjV8BvL+MtFH1r 26 | X4lEs3tRFn9LSqyeQjfDRP+Fv91OgKaRb6wJ8HWBnx8Jpkd19kU= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /Chapter09/ch9/rsa/key.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDPztPWk1Cds9ktLG4c/zLQdANw2wqnB3UvC2CE+V32XSUrp/akM2UljCojSKXwlR8h9YT1Vo70qzFN8G1g/Is7xPPs2T7TgoOWRA4qCkokethN37mJKt7cPrwhUd3EGS7ZUWHnu6oE0kKoYEXQ6BUeH+az1/SQNVppQ1KPuQy7mwtU9ZG+dmDIk3S/8sdLbLba2RedyPvB/2QLXeo3foyxnaofhUXR/z9ZOQTrNRJ1PBjZkS8rrrOOZN+4WhDx1/k3k+PhJNAaT7aXq5T+TE1sE1aNiYk2nUSl0kneb0DWcOtFFB8zUd1yQ4IlWf55gOvMPZb9tIyb/dKCrMzejU41 fab@fabmp.local 2 | -------------------------------------------------------------------------------- /Chapter09/ch9/rsa/keypwd: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-128-CBC,6B1FF5E922949C8E4AA51B8F5B0F0CBE 4 | 5 | E6B7lpXoI0SXOImuBllCOoFPonK6JObMVOp2qrEagcVP9loRXOecRi/qG+nF/BJO 6 | W/FnY4OmsCYAgFPB281plp8eVodNr6XxcPZaQx92G9RlA5FSCWICWFfhOlcDo1bR 7 | 1OAkmZjzWve3C2XNbOVpMweO3ElFzKy2KPzuBHaDVfuZm6pMMxqn4C+MzL6cYtF/ 8 | a3aJRwRJJYF9OLOSpfoFCCVC/NfOjhczPLgNqRHnpnnAypddwju8R6KPJ1SkZaru 9 | Jd1hwAYzcFY/G21SwCzUp9MOp9faeomWYYiv8BT/65LOY/e8evITh9XDpe83S3KQ 10 | oY+7NiVZdQzjaw8lA9KRWQTsXTLSXAFaHJLmfITr/QnC1HhEPBiux3MY2vjU1QMJ 11 | JtsrlTqBjBIKMJh4RoqvNrcNCCCYaXleNmSObcGR75s41s9yGAaNNKKGfvvPvW5j 12 | 69mOEqOmoKdtJuClpkeabqPXVqQLyf3CKZmqZbctmdAytTfZFRSTFw4VZZXKj/nR 13 | 7eLPqlg4Q8F057LWlTklsRDZvQOp9Ci5kCFK+5fOQ79CBeBx59i121+tYSpLWos/ 14 | bYOKcvSGfDghJYhGgfIg/H2e409VGkum1wTfEjD88n+wsjDQb94a0kiH2Kk/xqoy 15 | Bv24SSIPMgLHuWTJtkUOGESRm1op4URtVBmPURaAAacnXbr6jXyBrl8leN5T394U 16 | hn9Y9iNTTGBDFZL7gqZpSWzW5kI8Ju66Tt1WExlvHlb3h/0doIwlo0+hR6Rs4C4L 17 | 2YuOiiV6IBjDPF2ySg2zaUfGSVkxFL85Jrt8yQCLh3ebGeVkOptx653kVm0wMWcy 18 | a8vxTAI4m8szB272ZzMWKf312q83RjRBHPw7dMM6tq9jQL0Aea9/Ia9fLElA8rH/ 19 | 1i3g4zz3yhTqctkqiKIk7CeMQcYC3IL2ddUHftcijWfjvBChhjvWupegFC2DM29r 20 | H0H5lRyQHT8daaNqWYR9PEL+n8Au/FAgtz4U7lgYVdHstTq7Mch+vnWfc/4Y7pEl 21 | UWZBSmyMl2dpDTGCD79XFgXTuoqtkuML788ll5sz8C0Bh+jVfDwPsc8pimABag/s 22 | 0N7l3cPHJX5Vv3lSrVtOdsT7Athhkz4klHijou+a1efDNX+aEbC08oy4M3OaV2Ri 23 | o1yHpvZo9Wsw3IMmxuPswWd42BIweUrLeJy1PuO8dONwi4SzT2RBXIV3H56It9Ad 24 | XQK7BYAtOa1sBJ9tYycP/f9Bfg36SpjxNAwBQkQB9V8nNEDwW4818zQs7aLs5NkM 25 | BMIETlGBSMX+OQTOTNPoABlx+c0Xn6NSm3GpHr5biZOhmShQaDDBdnPV8yk1XQ9j 26 | 06tO2LvaevhOVfGt0GuPwUQbOwoJ8dAFFM7A7//2vd/QFEtJKXQK3N2cQTDmvTch 27 | dBt4vpqgiZfwomglHqVw/8NHSIIdTHyVYjCCNQvK7OfjricjBIAMGio+XlNiCbwP 28 | Jpo5fRtZ3bJNLiHIDFYjelWJhSN+B/h0DfSo3XF6roncXC2ljZ07TDi66ju0j80Q 29 | /n5pc2m1ckjjf46tkuSsW6kQN1YBdpzLyyAHeuy9rAR/Vo8inPld7zJa7B4oOuow 30 | -----END RSA PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /Chapter09/ch9/rsa/keypwd.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAvimJusND8agzuXFhG+d4sPrn6AqC69H/icI77baF15/ztE9b/SioeBl2B+1HI/EnkqMpK8vLJOAN27cHUps9yMkjdU4KrRqK9pjHaeiwxZ+24LVt4QzvT6DcpREymB5y1GX34hIFjcXZDZgV8BkWwVW6qrocLtdl9c4vycPbqAYZ7o/hJzlbmhRClpB1FYamlZhuiH+5neWXuWtN7yV91Pi+DS7l0SbX37ElOComvZy9ug2UVqrK4+mPwCAtxnppHZVQsyVd2l2rKARqE3LRn2mbvlUtInDhfCU12GjRqItbbDgrcKoKeAJrExPUfWc9W3Iknh/2Os0FcbJSr6PX fab@fabmp.local 2 | -------------------------------------------------------------------------------- /Chapter09/ch9/secrs/secr_gen.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | from string import digits, ascii_letters 3 | 4 | 5 | def generate_pwd(length=8): 6 | chars = digits + ascii_letters 7 | return ''.join(secrets.choice(chars) for c in range(length)) 8 | 9 | 10 | def generate_secure_pwd(length=16, upper=3, digits=3): 11 | if length < upper + digits + 1: 12 | raise ValueError('Nice try!') 13 | 14 | while True: 15 | pwd = generate_pwd(length) 16 | if (any(c.islower() for c in pwd) 17 | and sum(c.isupper() for c in pwd) >= upper 18 | and sum(c.isdigit() for c in pwd) >= digits): 19 | return pwd 20 | 21 | 22 | print(generate_secure_pwd()) 23 | print(generate_secure_pwd(length=3, upper=1, digits=1)) 24 | 25 | """ 26 | $ python secr_gen.py 27 | nsL5voJnCi7Ote3F 28 | J5e 29 | """ 30 | -------------------------------------------------------------------------------- /Chapter09/ch9/secrs/secr_rand.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | 3 | 4 | # utils 5 | print(secrets.choice('Choose one of these words'.split())) 6 | 7 | print(secrets.randbelow(10 ** 6)) 8 | 9 | print(secrets.randbits(32)) 10 | 11 | 12 | # tokens 13 | print(secrets.token_bytes(16)) 14 | 15 | print(secrets.token_hex(32)) 16 | 17 | print(secrets.token_urlsafe(32)) 18 | 19 | # compare digests against timing attacks 20 | secrets.compare_digest('abc123', 'abc123') 21 | 22 | """ 23 | $ python secr_rand.py 24 | one 25 | 504156 26 | 3172492450 27 | b'\xda\x863\xeb\xbb|\x8fk\x9b\xbd\x14Q\xd4\x8d\x15}' 28 | 9f90fd042229570bf633e91e92505523811b45e1c3a72074e19bbeb2e5111bf7 29 | bl4qz_Av7QNvPEqZtKsLuTOUsNLFmXW3O03pn50leiY 30 | """ 31 | -------------------------------------------------------------------------------- /Chapter09/ch9/secrs/secr_reset.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | 3 | 4 | def get_reset_pwd_url(token_length=16): 5 | token = secrets.token_urlsafe(token_length) 6 | return f'https://fabdomain.com/reset-pwd/{token}' 7 | 8 | 9 | print(get_reset_pwd_url()) 10 | 11 | 12 | """ 13 | $ python secr_reset.py 14 | https://fabdomain.com/reset-pwd/m4jb7aKgzTGuyjs9lTIspw 15 | """ 16 | -------------------------------------------------------------------------------- /Chapter09/ch9/tok.py: -------------------------------------------------------------------------------- 1 | import jwt 2 | 3 | 4 | data = {'payload': 'data', 'id': 123456789} 5 | 6 | 7 | token = jwt.encode(data, 'secret-key') 8 | data_out = jwt.decode(token, 'secret-key') 9 | print(token) 10 | print(data_out) 11 | 12 | 13 | # decode without verifying the signature 14 | jwt.decode(token, verify=False) 15 | 16 | 17 | # let's use another algorithm 18 | token512 = jwt.encode(data, 'secret-key', algorithm='HS512') 19 | data_out = jwt.decode(token512, 'secret-key', algorithm='HS512') 20 | print(data_out) 21 | -------------------------------------------------------------------------------- /Chapter09/ch9/token_rsa.py: -------------------------------------------------------------------------------- 1 | import jwt 2 | from cryptography.hazmat.backends import default_backend 3 | from cryptography.hazmat.primitives import serialization 4 | 5 | 6 | data = {'payload': 'data'} 7 | 8 | 9 | def encode(data, priv_filename, priv_pwd=None, algorithm='RS256'): 10 | 11 | with open(priv_filename, 'rb') as key: 12 | private_key = serialization.load_pem_private_key( 13 | key.read(), 14 | password=priv_pwd, 15 | backend=default_backend() 16 | ) 17 | 18 | return jwt.encode(data, private_key, algorithm=algorithm) 19 | 20 | 21 | def decode(data, pub_filename, algorithm='RS256'): 22 | 23 | with open(pub_filename, 'rb') as key: 24 | public_key = key.read() 25 | 26 | return jwt.decode(data, public_key, algorithm=algorithm) 27 | 28 | 29 | # no pwd 30 | token = encode(data, 'rsa/key') 31 | data_out = decode(token, 'rsa/key.pub') 32 | print(data_out) 33 | 34 | 35 | # with pwd 36 | token = encode(data, 'rsa/keypwd', priv_pwd=b'Password123') 37 | data_out = decode(token, 'rsa/keypwd.pub') 38 | print(data_out) 39 | -------------------------------------------------------------------------------- /Chapter10/ch10/aio/pics/53a51667.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter10/ch10/aio/pics/53a51667.jpg -------------------------------------------------------------------------------- /Chapter10/ch10/aio/pics/81846f84.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter10/ch10/aio/pics/81846f84.jpg -------------------------------------------------------------------------------- /Chapter10/ch10/aio/randompix_corout.py: -------------------------------------------------------------------------------- 1 | import os 2 | from secrets import token_hex 3 | import asyncio 4 | 5 | import aiohttp 6 | 7 | 8 | PICS_FOLDER = 'pics' 9 | URL = 'http://lorempixel.com/640/480/' 10 | # URL = 'https://lorempizza.com/640/480/' 11 | 12 | 13 | async def download_image(url): 14 | async with aiohttp.ClientSession() as session: 15 | async with session.get(url) as resp: 16 | return await resp.read() 17 | 18 | 19 | async def download(url, semaphore): 20 | async with semaphore: 21 | content = await download_image(url) 22 | filename = save_image(content) 23 | return filename 24 | 25 | 26 | def save_image(content): 27 | filename = '{}.jpg'.format(token_hex(4)) 28 | path = os.path.join(PICS_FOLDER, filename) 29 | with open(path, 'wb') as stream: 30 | stream.write(content) 31 | return filename 32 | 33 | 34 | def batch_download(images, url): 35 | loop = asyncio.get_event_loop() 36 | semaphore = asyncio.Semaphore(10) 37 | cors = [download(url, semaphore) for _ in range(images)] 38 | res, _ = loop.run_until_complete(asyncio.wait(cors)) 39 | loop.close() 40 | return [r.result() for r in res] 41 | 42 | 43 | if __name__ == '__main__': 44 | saved = batch_download(20, URL) 45 | print(saved) 46 | -------------------------------------------------------------------------------- /Chapter10/ch10/aio/randompix_proc.py: -------------------------------------------------------------------------------- 1 | import os 2 | from secrets import token_hex 3 | from concurrent.futures import ProcessPoolExecutor, as_completed 4 | 5 | import requests 6 | 7 | 8 | PICS_FOLDER = 'pics' 9 | URL = 'http://lorempixel.com/640/480/' 10 | # URL = 'https://lorempizza.com/640/480/' 11 | 12 | 13 | def download(url): 14 | resp = requests.get(URL) 15 | return save_image(resp.content) 16 | 17 | 18 | def save_image(content): 19 | filename = '{}.jpg'.format(token_hex(4)) 20 | path = os.path.join(PICS_FOLDER, filename) 21 | with open(path, 'wb') as stream: 22 | stream.write(content) 23 | return filename 24 | 25 | 26 | def batch_download(url, n, workers=4): 27 | with ProcessPoolExecutor(max_workers=workers) as executor: 28 | futures = (executor.submit(download, url) for _ in range(n)) 29 | return [future.result() for future in as_completed(futures)] 30 | 31 | 32 | if __name__ == '__main__': 33 | saved = batch_download(URL, 10) 34 | print(saved) 35 | -------------------------------------------------------------------------------- /Chapter10/ch10/aio/randompix_serial.py: -------------------------------------------------------------------------------- 1 | import os 2 | from secrets import token_hex 3 | 4 | import requests 5 | 6 | 7 | PICS_FOLDER = 'pics' 8 | URL = 'http://lorempixel.com/640/480/' 9 | # URL = 'https://lorempizza.com/640/480/' 10 | 11 | 12 | def download(url): 13 | resp = requests.get(URL) 14 | return save_image(resp.content) 15 | 16 | 17 | def save_image(content): 18 | filename = '{}.jpg'.format(token_hex(4)) 19 | path = os.path.join(PICS_FOLDER, filename) 20 | with open(path, 'wb') as stream: 21 | stream.write(content) 22 | return filename 23 | 24 | 25 | def batch_download(url, n): 26 | return [download(url) for _ in range(n)] 27 | 28 | 29 | if __name__ == '__main__': 30 | saved = batch_download(URL, 10) 31 | print(saved) 32 | -------------------------------------------------------------------------------- /Chapter10/ch10/comm_queue.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from queue import Queue 3 | 4 | 5 | SENTINEL = object() 6 | 7 | 8 | def producer(q, n): 9 | a, b = 0, 1 10 | while a <= n: 11 | q.put(a) 12 | a, b = b, a + b 13 | q.put(SENTINEL) 14 | 15 | 16 | def consumer(q): 17 | while True: 18 | num = q.get() 19 | q.task_done() 20 | if num is SENTINEL: 21 | break 22 | print(f'Got number {num}') 23 | 24 | 25 | q = Queue() 26 | cns = threading.Thread(target=consumer, args=(q, )) 27 | prd = threading.Thread(target=producer, args=(q, 35)) 28 | 29 | cns.start() 30 | prd.start() 31 | 32 | q.join() 33 | 34 | """ 35 | $ python comm_queue.py 36 | Got number 0 37 | Got number 1 38 | Got number 1 39 | Got number 2 40 | Got number 3 41 | Got number 5 42 | Got number 8 43 | Got number 13 44 | Got number 21 45 | Got number 34 46 | """ 47 | -------------------------------------------------------------------------------- /Chapter10/ch10/comm_queue_proc.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | 3 | 4 | SENTINEL = 'STOP' 5 | 6 | 7 | def producer(q, n): 8 | a, b = 0, 1 9 | while a <= n: 10 | q.put(a) 11 | a, b = b, a + b 12 | q.put(SENTINEL) 13 | 14 | 15 | def consumer(q): 16 | while True: 17 | num = q.get() 18 | if num == SENTINEL: 19 | break 20 | print(f'Got number {num}') 21 | 22 | 23 | q = multiprocessing.Queue() 24 | cns = multiprocessing.Process(target=consumer, args=(q, )) 25 | prd = multiprocessing.Process(target=producer, args=(q, 35)) 26 | 27 | cns.start() 28 | prd.start() 29 | 30 | """ 31 | $ python comm_queue_proc.py 32 | Got number 0 33 | Got number 1 34 | Got number 1 35 | Got number 2 36 | Got number 3 37 | Got number 5 38 | Got number 8 39 | Got number 13 40 | Got number 21 41 | Got number 34 42 | """ 43 | -------------------------------------------------------------------------------- /Chapter10/ch10/evt.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | def fire(): 5 | print('Firing event...') 6 | event.set() 7 | 8 | 9 | def listen(): 10 | event.wait() 11 | print('Event has been fired') 12 | 13 | 14 | event = threading.Event() 15 | t1 = threading.Thread(target=fire) 16 | t2 = threading.Thread(target=listen) 17 | t2.start() 18 | t1.start() 19 | 20 | """ 21 | $ python evt.py 22 | Firing event... 23 | Event has been fired 24 | """ 25 | -------------------------------------------------------------------------------- /Chapter10/ch10/hostres/README.md: -------------------------------------------------------------------------------- 1 | # Hostname Resolution 2 | 3 | In order to run the tests just make sure you are in this folder 4 | and then run: 5 | 6 | ``` 7 | $ pytest -vv -x tests 8 | ``` 9 | -------------------------------------------------------------------------------- /Chapter10/ch10/hostres/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter10/ch10/hostres/__init__.py -------------------------------------------------------------------------------- /Chapter10/ch10/hostres/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter10/ch10/hostres/tests/__init__.py -------------------------------------------------------------------------------- /Chapter10/ch10/hostres/tests/test_util.py: -------------------------------------------------------------------------------- 1 | import socket 2 | from time import sleep 3 | from unittest.mock import patch 4 | 5 | import pytest 6 | 7 | from ..util import resolve 8 | 9 | 10 | @pytest.fixture 11 | def gethostbyname_mock(): 12 | with patch('hostres.util.gethostbyname') as m: 13 | yield m 14 | 15 | 16 | def test_resolve(gethostbyname_mock): 17 | 18 | def side_effect(hostname, queue): 19 | queue.put('1.2.3.4') 20 | gethostbyname_mock.side_effect = side_effect 21 | 22 | out = resolve('hostname') 23 | 24 | assert out == '1.2.3.4' 25 | 26 | 27 | def test_resolve_oserror(gethostbyname_mock): 28 | gethostbyname_mock.side_effect = socket.error('BOOM!') 29 | 30 | out = resolve('hostname') 31 | 32 | assert out == 'hostname' 33 | 34 | 35 | def test_resolve_timeout(gethostbyname_mock): 36 | 37 | def side_effect(hostname, queue): 38 | sleep(2) 39 | queue.put('1.2.3.4') # will be ignored 40 | gethostbyname_mock.side_effect = side_effect 41 | 42 | out = resolve('hostname', 0.1) 43 | 44 | assert out == 'hostname' 45 | -------------------------------------------------------------------------------- /Chapter10/ch10/hostres/util.py: -------------------------------------------------------------------------------- 1 | import socket 2 | from multiprocessing import Process, Queue 3 | 4 | 5 | def resolve(hostname, timeout=5): 6 | exitcode, ip = resolve_host(hostname, timeout) 7 | 8 | if exitcode == 0: 9 | return ip 10 | else: 11 | return hostname 12 | 13 | 14 | def resolve_host(hostname, timeout): 15 | queue = Queue() 16 | 17 | proc = Process(target=gethostbyname, args=(hostname, queue)) 18 | proc.start() 19 | proc.join(timeout=timeout) 20 | 21 | if queue.empty(): 22 | proc.terminate() 23 | ip = None 24 | else: 25 | ip = queue.get() 26 | 27 | return proc.exitcode, ip 28 | 29 | 30 | def gethostbyname(hostname, queue): 31 | ip = socket.gethostbyname(hostname) 32 | queue.put(ip) 33 | -------------------------------------------------------------------------------- /Chapter10/ch10/local.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from random import randint 3 | 4 | 5 | local = threading.local() 6 | 7 | 8 | def run(local, barrier): 9 | local.my_value = randint(0, 10**2) 10 | t = threading.current_thread() 11 | print(f'Thread {t.name} has value {local.my_value}') 12 | barrier.wait() 13 | print(f'Thread {t.name} still has value {local.my_value}') 14 | 15 | 16 | count = 3 17 | barrier = threading.Barrier(count) 18 | 19 | threads = [ 20 | threading.Thread( 21 | target=run, name=f'T{name}', args=(local, barrier) 22 | ) for name in range(count) 23 | ] 24 | 25 | for t in threads: 26 | t.start() 27 | 28 | """ 29 | $ python local.py 30 | Thread T0 has value 61 31 | Thread T1 has value 52 32 | Thread T2 has value 38 33 | Thread T2 still has value 38 34 | Thread T0 still has value 61 35 | Thread T1 still has value 52 36 | """ 37 | -------------------------------------------------------------------------------- /Chapter10/ch10/ms/README.md: -------------------------------------------------------------------------------- 1 | # Mergesort Section 2 | 3 | In order to run the tests just make sure you are in this folder 4 | and then run: 5 | 6 | ``` 7 | $ pytest -vv -x tests 8 | ``` 9 | 10 | You can test the performances by running the `performance.py` 11 | module in the parent folder with the following command: 12 | 13 | ``` 14 | $ python performance.py 15 | ``` 16 | -------------------------------------------------------------------------------- /Chapter10/ch10/ms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter10/ch10/ms/__init__.py -------------------------------------------------------------------------------- /Chapter10/ch10/ms/algo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter10/ch10/ms/algo/__init__.py -------------------------------------------------------------------------------- /Chapter10/ch10/ms/algo/mergesort.py: -------------------------------------------------------------------------------- 1 | 2 | def sort(v): 3 | if len(v) <= 1: 4 | return v 5 | mid = len(v) // 2 6 | v1, v2 = sort(v[:mid]), sort(v[mid:]) 7 | return merge(v1, v2) 8 | 9 | 10 | def merge(v1, v2): 11 | """Merge two vectors together. 12 | 13 | Assumes v1 and v2 are sorted (ascending). Resulting vector 14 | will also be sorted ascending. 15 | """ 16 | v = [] 17 | h = k = 0 18 | len_v1, len_v2 = len(v1), len(v2) 19 | while h < len_v1 or k < len_v2: 20 | if k == len_v2 or (h < len_v1 and v1[h] < v2[k]): 21 | v.append(v1[h]) 22 | h += 1 23 | else: 24 | v.append(v2[k]) 25 | k += 1 26 | return v 27 | -------------------------------------------------------------------------------- /Chapter10/ch10/ms/algo/mergesort_proc.py: -------------------------------------------------------------------------------- 1 | from functools import reduce 2 | from math import ceil 3 | from concurrent.futures import ProcessPoolExecutor, as_completed 4 | 5 | from .mergesort import sort as _sort, merge 6 | 7 | 8 | def sort(v, workers=2): 9 | if len(v) == 0: 10 | return v 11 | 12 | dim = ceil(len(v) / workers) 13 | chunks = (v[k: k + dim] for k in range(0, len(v), dim)) 14 | 15 | with ProcessPoolExecutor(max_workers=workers) as executor: 16 | futures = [ 17 | executor.submit(_sort, chunk) for chunk in chunks 18 | ] 19 | return reduce( 20 | merge, 21 | (future.result() for future in as_completed(futures)) 22 | ) 23 | -------------------------------------------------------------------------------- /Chapter10/ch10/ms/algo/mergesort_thread.py: -------------------------------------------------------------------------------- 1 | from functools import reduce 2 | from math import ceil 3 | from concurrent.futures import ThreadPoolExecutor, as_completed 4 | 5 | from .mergesort import sort as _sort, merge 6 | 7 | 8 | def sort(v, workers=2): 9 | if len(v) == 0: 10 | return v 11 | 12 | dim = ceil(len(v) / workers) 13 | chunks = (v[k: k + dim] for k in range(0, len(v), dim)) 14 | 15 | with ThreadPoolExecutor(max_workers=workers) as executor: 16 | futures = [ 17 | executor.submit(_sort, chunk) for chunk in chunks 18 | ] 19 | return reduce( 20 | merge, 21 | (future.result() for future in as_completed(futures)) 22 | ) 23 | -------------------------------------------------------------------------------- /Chapter10/ch10/ms/algo/multi_mergesort.py: -------------------------------------------------------------------------------- 1 | from functools import reduce 2 | 3 | from .mergesort import merge 4 | 5 | 6 | def sort(v, parts=2): 7 | assert parts > 1, 'Parts need to be at least 2.' 8 | if len(v) <= 1: 9 | return v 10 | 11 | chunk_len = max(1, len(v) // parts) 12 | chunks = ( 13 | sort(v[k: k + chunk_len], parts=parts) 14 | for k in range(0, len(v), chunk_len) 15 | ) 16 | return multi_merge(*chunks) 17 | 18 | 19 | def multi_merge(*v): 20 | return reduce(merge, v) 21 | -------------------------------------------------------------------------------- /Chapter10/ch10/ms/performance.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | from random import randint 3 | from functools import partial 4 | from contextlib import contextmanager 5 | 6 | from algo.mergesort import sort 7 | from algo.mergesort_thread import sort as sort_thread 8 | from algo.mergesort_proc import sort as sort_proc 9 | 10 | 11 | @contextmanager 12 | def timer(): 13 | t = time() 14 | yield 15 | tot = time() - t 16 | print(f'Elapsed time: {tot:.3f}s') 17 | 18 | 19 | workers = 4 20 | mx = 10 ** 7 21 | sizes = [10 ** 5, 5 * 10 ** 5] 22 | vectors = [ 23 | [randint(-mx, mx) for _ in range(size)] for size in sizes 24 | ] 25 | 26 | 27 | sort_th = partial(sort_thread, workers=workers) 28 | sort_pr = partial(sort_proc, workers=workers) 29 | 30 | sort.__name = 'Sort' 31 | sort_th.__name = 'Sort Thread' 32 | sort_pr.__name = 'Sort Proc' 33 | 34 | for func in (sort, sort_th, sort_pr): 35 | print(f'Testing {func.__name}') 36 | for vector in vectors: 37 | items = len(vector) 38 | print(f'Size: {items}') 39 | with timer(): 40 | func(vector) 41 | print() 42 | 43 | 44 | """ 45 | $ python performance.py 46 | 47 | Testing Sort 48 | Size: 100000 49 | Elapsed time: 0.492s 50 | Size: 500000 51 | Elapsed time: 2.739s 52 | 53 | Testing Sort Thread 54 | Size: 100000 55 | Elapsed time: 0.482s 56 | Size: 500000 57 | Elapsed time: 2.818s 58 | 59 | Testing Sort Proc 60 | Size: 100000 61 | Elapsed time: 0.313s 62 | Size: 500000 63 | Elapsed time: 1.586s 64 | """ 65 | -------------------------------------------------------------------------------- /Chapter10/ch10/ms/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter10/ch10/ms/tests/__init__.py -------------------------------------------------------------------------------- /Chapter10/ch10/ms/tests/test_mergesort.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | import pytest 4 | 5 | from ms.algo.mergesort import sort 6 | 7 | 8 | @pytest.fixture(params=list(range(1, 25)) + [10000]) 9 | def vector(request): 10 | yield [randint(-10**5, 10**5) for n in range(request.param)] 11 | 12 | 13 | def test_random_vector(vector): 14 | assert sort(vector) == sorted(vector) 15 | 16 | 17 | @pytest.mark.parametrize('v', [ 18 | [], 19 | [1], 20 | [1, 1], 21 | [1, 2], 22 | [2, 1], 23 | ]) 24 | def test_sort_edge_cases(v): 25 | sorted_v = sort(v) 26 | assert sorted_v == sorted(v) 27 | 28 | 29 | def test_pure_function(): 30 | v = [2, 3, 1] 31 | idv = id(v) 32 | assert sort(v) == [1, 2, 3] 33 | assert id(v) == idv 34 | assert v == [2, 3, 1] 35 | -------------------------------------------------------------------------------- /Chapter10/ch10/ms/tests/test_mergesort_concurrent.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | import pytest 4 | 5 | from ms.algo.mergesort_thread import sort as sort_thread 6 | from ms.algo.mergesort_proc import sort as sort_proc 7 | 8 | 9 | # the following helps when running $ pytest -vv tests 10 | sort_thread.__name__ = 'Sort Thread' 11 | sort_proc.__name__ = 'Sort Proc' 12 | 13 | 14 | @pytest.fixture(params=[sort_thread, sort_proc]) 15 | def sort(request): 16 | return request.param 17 | 18 | 19 | @pytest.fixture(params=list(range(1, 25)) + [10000]) 20 | def vector(request): 21 | yield [randint(-10**5, 10**5) for n in range(request.param)] 22 | 23 | 24 | @pytest.fixture(params=[1, 2, 4, 8]) 25 | def workers(request): 26 | yield request.param 27 | 28 | 29 | def test_random_vector(sort, vector, workers): 30 | assert sort(vector, workers=workers) == sorted(vector) 31 | 32 | 33 | @pytest.mark.parametrize('v', [ 34 | [], 35 | [1], 36 | [1, 1], 37 | [1, 2], 38 | [2, 1], 39 | ]) 40 | def test_sort_edge_cases(v, sort): 41 | sorted_v = sort(v) 42 | assert sorted_v == sorted(v) 43 | 44 | 45 | def test_pure_function(sort): 46 | v = [2, 3, 1] 47 | idv = id(v) 48 | assert sort(v) == [1, 2, 3] 49 | assert id(v) == idv 50 | assert v == [2, 3, 1] 51 | -------------------------------------------------------------------------------- /Chapter10/ch10/ms/tests/test_multi_mergesort.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | import pytest 4 | 5 | from ms.algo.multi_mergesort import sort 6 | 7 | 8 | @pytest.fixture(params=list(range(1, 25)) + [10000]) 9 | def vector(request): 10 | yield [randint(-10**5, 10**5) for n in range(request.param)] 11 | 12 | 13 | @pytest.fixture(params=[2, 4, 8, 16]) 14 | def parts(request): 15 | yield request.param 16 | 17 | 18 | def test_random_vector(vector, parts): 19 | assert sort(vector, parts=parts) == sorted(vector) 20 | 21 | 22 | def test_at_least_two_parts(): 23 | with pytest.raises(AssertionError) as err: 24 | sort([2, 7, 1], 1) 25 | 26 | assert err.match(r'Parts need to be at least 2\.') 27 | 28 | 29 | @pytest.mark.parametrize('v', [ 30 | [], 31 | [1], 32 | [1, 1], 33 | [1, 2], 34 | [2, 1], 35 | ]) 36 | def test_sort_edge_cases(v): 37 | sorted_v = sort(v) 38 | assert sorted_v == sorted(v) 39 | 40 | 41 | def test_pure_function(): 42 | v = [2, 3, 1] 43 | idv = id(v) 44 | assert sort(v) == [1, 2, 3] 45 | assert id(v) == idv 46 | assert v == [2, 3, 1] 47 | -------------------------------------------------------------------------------- /Chapter10/ch10/pool.py: -------------------------------------------------------------------------------- 1 | from concurrent.futures import ThreadPoolExecutor, as_completed 2 | from random import randint 3 | import threading 4 | 5 | 6 | def run(name): 7 | value = randint(0, 10**2) 8 | tname = threading.current_thread().name 9 | print(f'Hi, I am {name} ({tname}) and my value is {value}') 10 | return (name, value) 11 | 12 | 13 | with ThreadPoolExecutor(max_workers=3) as executor: 14 | futures = [ 15 | executor.submit(run, f'T{name}') for name in range(5) 16 | ] 17 | for future in as_completed(futures): 18 | name, value = future.result() 19 | print(f'Thread {name} returned {value}') 20 | 21 | """ 22 | $ python pool.py 23 | Hi, I am T0 (ThreadPoolExecutor-0_0) and my value is 5 24 | Hi, I am T1 (ThreadPoolExecutor-0_0) and my value is 23 25 | Hi, I am T2 (ThreadPoolExecutor-0_1) and my value is 58 26 | Thread T1 returned 23 27 | Thread T0 returned 5 28 | Hi, I am T3 (ThreadPoolExecutor-0_0) and my value is 93 29 | Hi, I am T4 (ThreadPoolExecutor-0_1) and my value is 62 30 | Thread T2 returned 58 31 | Thread T3 returned 93 32 | Thread T4 returned 62 33 | """ 34 | -------------------------------------------------------------------------------- /Chapter10/ch10/pool_proc.py: -------------------------------------------------------------------------------- 1 | from concurrent.futures import ProcessPoolExecutor, as_completed 2 | from random import randint 3 | from time import sleep 4 | 5 | 6 | def run(name): 7 | sleep(.05) 8 | value = randint(0, 10**2) 9 | print(f'Hi, I am {name} and my value is {value}') 10 | return (name, value) 11 | 12 | 13 | with ProcessPoolExecutor(max_workers=3) as executor: 14 | futures = [ 15 | executor.submit(run, f'P{name}') for name in range(5) 16 | ] 17 | for future in as_completed(futures): 18 | name, value = future.result() 19 | print(f'Process {name} returned {value}') 20 | 21 | """ 22 | $ python pool_proc.py 23 | Hi, I am P0 and my value is 19 24 | Hi, I am P1 and my value is 97 25 | Hi, I am P2 and my value is 74 26 | Process P0 returned 19 27 | Process P1 returned 97 28 | Process P2 returned 74 29 | Hi, I am P3 and my value is 80 30 | Hi, I am P4 and my value is 68 31 | Process P3 returned 80 32 | Process P4 returned 68 33 | """ 34 | -------------------------------------------------------------------------------- /Chapter10/ch10/race.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from time import sleep 3 | from random import random 4 | 5 | 6 | counter = 0 7 | randsleep = lambda: sleep(0.1 * random()) 8 | 9 | 10 | def incr(n): 11 | global counter 12 | for count in range(n): 13 | current = counter 14 | randsleep() 15 | counter = current + 1 16 | randsleep() 17 | 18 | 19 | n = 5 20 | t1 = threading.Thread(target=incr, args=(n, )) 21 | t2 = threading.Thread(target=incr, args=(n, )) 22 | 23 | t1.start() 24 | t2.start() 25 | 26 | t1.join() 27 | t2.join() 28 | 29 | print(f'Counter: {counter}') 30 | 31 | """ 32 | $ python race.py 33 | Counter: 6 # varies between different runs 34 | """ 35 | -------------------------------------------------------------------------------- /Chapter10/ch10/race_with_lock.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from time import sleep 3 | from random import random 4 | 5 | 6 | counter = 0 7 | randsleep = lambda: sleep(0.1 * random()) 8 | 9 | 10 | def incr(n): 11 | global counter 12 | for count in range(n): 13 | with incr_lock: 14 | current = counter 15 | randsleep() 16 | counter = current + 1 17 | randsleep() 18 | 19 | 20 | n = 5 21 | incr_lock = threading.Lock() 22 | t1 = threading.Thread(target=incr, args=(n, )) 23 | t2 = threading.Thread(target=incr, args=(n, )) 24 | 25 | t1.start() 26 | t2.start() 27 | 28 | t1.join() 29 | t2.join() 30 | 31 | print(f'Counter: {counter}') 32 | 33 | """ 34 | $ python race.py 35 | Counter: 10 # every time 36 | """ 37 | -------------------------------------------------------------------------------- /Chapter10/ch10/start.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | def sum_and_product(a, b): 5 | s, p = a + b, a * b 6 | print(f'{a}+{b}={s}, {a}*{b}={p}') 7 | 8 | 9 | t = threading.Thread( 10 | target=sum_and_product, name='SumProd', args=(3, 7) 11 | ) 12 | t.start() 13 | 14 | 15 | """ 16 | $ python start.py 17 | 3+7=10, 3*7=21 18 | """ 19 | -------------------------------------------------------------------------------- /Chapter10/ch10/start_proc.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | 3 | 4 | def sum_and_product(a, b): 5 | s, p = a + b, a * b 6 | print(f'{a}+{b}={s}, {a}*{b}={p}') 7 | 8 | 9 | p = multiprocessing.Process( 10 | target=sum_and_product, name='SumProdProc', args=(7, 9) 11 | ) 12 | p.start() 13 | 14 | 15 | """ 16 | $ python start_proc.py 17 | 7+9=16, 7*9=63 18 | """ 19 | -------------------------------------------------------------------------------- /Chapter10/ch10/start_with_info.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from time import sleep 3 | 4 | 5 | def sum_and_product(a, b): 6 | sleep(.2) 7 | print_current() 8 | s, p = a + b, a * b 9 | print(f'{a}+{b}={s}, {a}*{b}={p}') 10 | 11 | 12 | def status(t): 13 | if t.is_alive(): 14 | print(f'Thread {t.name} is alive.') 15 | else: 16 | print(f'Thread {t.name} has terminated.') 17 | 18 | 19 | def print_current(): 20 | print('The current thread is {}.'.format( 21 | threading.current_thread() 22 | )) 23 | print('Threads: {}'.format(list(threading.enumerate()))) 24 | 25 | 26 | print_current() 27 | t = threading.Thread( 28 | target=sum_and_product, name='SumProd', args=(3, 7) 29 | ) 30 | t.start() 31 | 32 | status(t) 33 | 34 | t.join() 35 | 36 | status(t) 37 | 38 | """ 39 | $ python start_with_info.py 40 | The current thread is 41 | <_MainThread(MainThread, started 140735733822336)>. 42 | Threads: [<_MainThread(MainThread, started 140735733822336)>] 43 | Thread SumProd is alive. 44 | The current thread is . 45 | Threads: [ 46 | <_MainThread(MainThread, started 140735733822336)>, 47 | 48 | ] 49 | 3+7=10, 3*7=21 50 | Thread SumProd has terminated. 51 | """ 52 | -------------------------------------------------------------------------------- /Chapter10/ch10/starwars.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from time import sleep 3 | from random import random 4 | 5 | 6 | def run(n): 7 | t = threading.current_thread() 8 | for count in range(n): 9 | print(f'Hello from {t.name}! ({count})') 10 | sleep(0.2 * random()) 11 | 12 | 13 | obi = threading.Thread(target=run, name='Obi-Wan', args=(4, )) 14 | ani = threading.Thread(target=run, name='Anakin', args=(3, )) 15 | 16 | obi.start() 17 | ani.start() 18 | 19 | obi.join() 20 | ani.join() 21 | 22 | 23 | """ 24 | $ python starwars.py 25 | Hello from Obi-Wan! (0) 26 | Hello from Anakin! (0) 27 | Hello from Obi-Wan! (1) 28 | Hello from Obi-Wan! (2) 29 | Hello from Anakin! (1) 30 | Hello from Obi-Wan! (3) 31 | Hello from Anakin! (2) 32 | """ 33 | -------------------------------------------------------------------------------- /Chapter10/ch10/stop.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from time import sleep 3 | 4 | 5 | class Fibo(threading.Thread): 6 | 7 | def __init__(self, *a, **kwa): 8 | super().__init__(*a, **kwa) 9 | self._running = True 10 | 11 | def stop(self): 12 | self._running = False 13 | 14 | def run(self): 15 | a, b = 0, 1 16 | while self._running: 17 | print(a, end=' ') 18 | a, b = b, a + b 19 | sleep(0.07) 20 | print() 21 | 22 | 23 | fibo = Fibo() 24 | fibo.start() 25 | sleep(1) 26 | fibo.stop() 27 | fibo.join() 28 | 29 | print('All done.') 30 | 31 | 32 | """ 33 | $ python stop.py 34 | 0 1 1 2 3 5 8 13 21 34 55 89 144 233 35 | All done. 36 | """ 37 | -------------------------------------------------------------------------------- /Chapter10/ch10/sudoku/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Fabrizio Romano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Chapter10/ch10/sudoku/README.md: -------------------------------------------------------------------------------- 1 | # Sudoku Section 2 | 3 | In order to run the tests just make sure you are in this folder 4 | and then run: 5 | 6 | ``` 7 | $ pytest -vv -x tests 8 | ``` 9 | 10 | If you want to see how the solver can be run using multi processing 11 | you can checkout `process_solver.py`. To run it: 12 | 13 | ``` 14 | $ python process_solver.py 15 | ``` -------------------------------------------------------------------------------- /Chapter10/ch10/sudoku/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter10/ch10/sudoku/__init__.py -------------------------------------------------------------------------------- /Chapter10/ch10/sudoku/algo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter10/ch10/sudoku/algo/__init__.py -------------------------------------------------------------------------------- /Chapter10/ch10/sudoku/norvig/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2010-2017 Peter Norvig 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Chapter10/ch10/sudoku/process_solver.py: -------------------------------------------------------------------------------- 1 | import os 2 | from functools import reduce 3 | from operator import concat 4 | from math import ceil 5 | from time import time 6 | from contextlib import contextmanager 7 | from concurrent.futures import ProcessPoolExecutor, as_completed 8 | from unittest import TestCase 9 | 10 | from algo.solver import solve 11 | 12 | 13 | @contextmanager 14 | def timer(): 15 | t = time() 16 | yield 17 | tot = time() - t 18 | print(f'Elapsed time: {tot:.3f}s') 19 | 20 | 21 | def batch_solve(puzzles): 22 | # Single thread batch solve. 23 | return [solve(puzzle) for puzzle in puzzles] 24 | 25 | 26 | def parallel_single_solver(puzzles, workers=4): 27 | # Parallel solve - 1 process per each puzzle 28 | with ProcessPoolExecutor(max_workers=workers) as executor: 29 | futures = ( 30 | executor.submit(solve, puzzle) for puzzle in puzzles 31 | ) 32 | 33 | return [ 34 | future.result() for future in as_completed(futures) 35 | ] 36 | 37 | 38 | def parallel_batch_solver(puzzles, workers=4): 39 | # Parallel batch solve - Puzzles are chunked into `workers` 40 | # chunks. A process for each chunk. 41 | assert len(puzzles) >= workers 42 | 43 | dim = ceil(len(puzzles) / workers) 44 | chunks = ( 45 | puzzles[k: k + dim] for k in range(0, len(puzzles), dim) 46 | ) 47 | 48 | with ProcessPoolExecutor(max_workers=workers) as executor: 49 | futures = ( 50 | executor.submit(batch_solve, chunk) for chunk in chunks 51 | ) 52 | 53 | results = ( 54 | future.result() for future in as_completed(futures) 55 | ) 56 | 57 | return reduce(concat, results) 58 | 59 | 60 | if __name__ == '__main__': 61 | 62 | puzzles_file = os.path.join('puzzles', 'sudoku-topn234.txt') 63 | 64 | with open(puzzles_file) as stream: 65 | puzzles = [puzzle.strip() for puzzle in stream] 66 | 67 | # single thread solve 68 | with timer(): 69 | res_batch = batch_solve(puzzles) 70 | 71 | # parallel solve, 1 process per puzzle 72 | with timer(): 73 | res_parallel_single = parallel_single_solver(puzzles) 74 | 75 | # parallel batch solve, 1 batch per process 76 | with timer(): 77 | res_parallel_batch = parallel_batch_solver(puzzles) 78 | 79 | # Quick way to verify that the results are the same, but 80 | # possibly in a different order, as they depend on how the 81 | # processes have been scheduled. 82 | assert_items_equal = TestCase().assertCountEqual 83 | 84 | assert_items_equal(res_batch, res_parallel_single) 85 | assert_items_equal(res_batch, res_parallel_batch) 86 | 87 | print('Done.') 88 | 89 | 90 | """ 91 | $ python process_solver.py 92 | Elapsed time: 5.368s 93 | Elapsed time: 2.856s 94 | Elapsed time: 2.818s 95 | Done. 96 | """ 97 | -------------------------------------------------------------------------------- /Chapter10/ch10/sudoku/puzzles/resources.txt: -------------------------------------------------------------------------------- 1 | The puzzles in this folder come from http://magictour.free.fr/sudoku.htm 2 | -------------------------------------------------------------------------------- /Chapter10/ch10/sudoku/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter10/ch10/sudoku/tests/__init__.py -------------------------------------------------------------------------------- /Chapter10/ch10/sudoku/tests/test_solver.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from sudoku.algo.solver import solve, solve_batch 6 | 7 | 8 | @pytest.fixture 9 | def puzzles_filename(): 10 | return os.path.join('puzzles', 'sudoku-top95.txt') 11 | 12 | 13 | def get_num(result): 14 | return int(result['A1'] + result['A2'] + result['A3']) 15 | 16 | 17 | def test_solver(puzzles_filename): 18 | with open(puzzles_filename) as stream: 19 | results = [solve(puzzle.strip()) for puzzle in stream] 20 | s = sum(get_num(res) for res in results) 21 | assert s == 49074 22 | 23 | 24 | def test_solver_batch(puzzles_filename): 25 | results = solve_batch(puzzles_filename, stats=False) 26 | s = sum(get_num(res) for res in results) 27 | assert s == 49074 28 | -------------------------------------------------------------------------------- /Chapter11/ch11/assertions.py: -------------------------------------------------------------------------------- 1 | mylist = [1, 2, 3] # this ideally comes from some place 2 | 3 | assert 4 == len(mylist) # this will break 4 | 5 | for position in range(4): 6 | print(mylist[position]) 7 | -------------------------------------------------------------------------------- /Chapter11/ch11/custom.py: -------------------------------------------------------------------------------- 1 | def debug(*msg, print_separator=True): 2 | print(*msg) 3 | if print_separator: 4 | print('-' * 40) 5 | 6 | 7 | debug('Data is ...') 8 | debug('Different', 'Strings', 'Are not a problem') 9 | debug('After while loop', print_separator=False) 10 | -------------------------------------------------------------------------------- /Chapter11/ch11/custom_timestamp.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | 4 | def debug(*msg, timestamp=[None]): 5 | print(*msg) 6 | from time import time # local import 7 | if timestamp[0] is None: 8 | timestamp[0] = time() #1 9 | else: 10 | now = time() 11 | print( 12 | ' Time elapsed: {:.3f}s'.format(now - timestamp[0]) 13 | ) 14 | timestamp[0] = now #2 15 | 16 | 17 | debug('Entering nasty piece of code...') 18 | sleep(.3) 19 | debug('First step done.') 20 | sleep(.5) 21 | debug('Second step done.') 22 | -------------------------------------------------------------------------------- /Chapter11/ch11/log.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig( 4 | filename='ch11.log', 5 | level=logging.DEBUG, # minimum level capture in the file 6 | format='[%(asctime)s] %(levelname)s: %(message)s', 7 | datefmt='%m/%d/%Y %I:%M:%S %p') 8 | 9 | mylist = [1, 2, 3] 10 | logging.info('Starting to process `mylist`...') 11 | 12 | for position in range(4): 13 | try: 14 | logging.debug( 15 | 'Value at position %s is %s', position, mylist[position] 16 | ) 17 | except IndexError: 18 | logging.exception('Faulty position: %s', position) 19 | 20 | logging.info('Done parsing `mylist`.') 21 | -------------------------------------------------------------------------------- /Chapter11/ch11/pdebugger.py: -------------------------------------------------------------------------------- 1 | # d comes from a JSON payload we don't control 2 | d = {'first': 'v1', 'second': 'v2', 'fourth': 'v4'} 3 | # keys also comes from a JSON payload we don't control 4 | keys = ('first', 'second', 'third', 'fourth') 5 | 6 | def do_something_with_value(value): 7 | print(value) 8 | 9 | for key in keys: 10 | do_something_with_value(d[key]) 11 | 12 | print('Validation done.') 13 | -------------------------------------------------------------------------------- /Chapter11/ch11/pdebugger_pdb.py: -------------------------------------------------------------------------------- 1 | # d comes from a JSON payload we don't control 2 | d = {'first': 'v1', 'second': 'v2', 'fourth': 'v4'} 3 | # keys also comes from a JSON payload we don't control 4 | keys = ('first', 'second', 'third', 'fourth') 5 | 6 | def do_something_with_value(value): 7 | print(value) 8 | 9 | 10 | import pdb 11 | pdb.set_trace() 12 | 13 | # or: 14 | # breakpoint() 15 | 16 | 17 | for key in keys: 18 | do_something_with_value(d[key]) 19 | 20 | print('Validation done.') 21 | 22 | 23 | """ 24 | $ python pdebugger_pdb.py 25 | (Pdb++) l 26 | 16 27 | 17 -> for key in keys: # breakpoint comes in 28 | 18 do_something_with_value(d[key]) 29 | 19 30 | 31 | (Pdb++) keys # inspecting the keys tuple 32 | ('first', 'second', 'third', 'fourth') 33 | (Pdb++) d.keys() # inspecting keys of `d` 34 | dict_keys(['first', 'second', 'fourth']) 35 | (Pdb++) d['third'] = 'placeholder' # add tmp placeholder 36 | (Pdb++) c # continue 37 | v1 38 | v2 39 | placeholder 40 | v4 41 | Validation done. 42 | """ 43 | -------------------------------------------------------------------------------- /Chapter11/ch11/traceback_simple.py: -------------------------------------------------------------------------------- 1 | d = {'some': 'key'} 2 | key = 'some-other' 3 | print(d[key]) 4 | -------------------------------------------------------------------------------- /Chapter11/ch11/traceback_validator.py: -------------------------------------------------------------------------------- 1 | class ValidatorError(Exception): 2 | """Raised when accessing a dict results in KeyError. """ 3 | 4 | d = {'some': 'key'} 5 | mandatory_key = 'some-other' 6 | try: 7 | print(d[mandatory_key]) 8 | except KeyError as err: 9 | raise ValidatorError( 10 | f'`{mandatory_key}` not found in d.' 11 | ) from err 12 | -------------------------------------------------------------------------------- /Chapter12/ch12/simple_server/img/owl-alcohol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter12/ch12/simple_server/img/owl-alcohol.png -------------------------------------------------------------------------------- /Chapter12/ch12/simple_server/img/owl-book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter12/ch12/simple_server/img/owl-book.png -------------------------------------------------------------------------------- /Chapter12/ch12/simple_server/img/owl-books.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter12/ch12/simple_server/img/owl-books.png -------------------------------------------------------------------------------- /Chapter12/ch12/simple_server/img/owl-ebook.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter12/ch12/simple_server/img/owl-ebook.jpg -------------------------------------------------------------------------------- /Chapter12/ch12/simple_server/img/owl-rose.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter12/ch12/simple_server/img/owl-rose.jpeg -------------------------------------------------------------------------------- /Chapter12/ch12/simple_server/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cool Owls! 4 | 5 |

Welcome to my owl gallery

6 |
7 | 8 | 9 | 10 | 11 | 12 |
13 |

Do you like my owls?

14 | 15 | 16 | -------------------------------------------------------------------------------- /Chapter12/ch12/simple_server/serve.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # start a simple HTTP Server 4 | python -m http.server 8000 5 | -------------------------------------------------------------------------------- /Chapter13/ch13/example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "def fibonacci(N):\n", 10 | " a, b = 0, 1\n", 11 | " while a < N:\n", 12 | " yield a\n", 13 | " a, b = b, a + b" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 2, 19 | "metadata": {}, 20 | "outputs": [ 21 | { 22 | "data": { 23 | "text/plain": [ 24 | "[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]" 25 | ] 26 | }, 27 | "execution_count": 2, 28 | "metadata": {}, 29 | "output_type": "execute_result" 30 | } 31 | ], 32 | "source": [ 33 | "list(fibonacci(100))" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 3, 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "name": "stdout", 43 | "output_type": "stream", 44 | "text": [ 45 | "356 ns ± 36.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)\n" 46 | ] 47 | } 48 | ], 49 | "source": [ 50 | "%timeit fibonacci(10**4)" 51 | ] 52 | } 53 | ], 54 | "metadata": { 55 | "kernelspec": { 56 | "display_name": "Python 3", 57 | "language": "python", 58 | "name": "python3" 59 | }, 60 | "language_info": { 61 | "codemirror_mode": { 62 | "name": "ipython", 63 | "version": 3 64 | }, 65 | "file_extension": ".py", 66 | "mimetype": "text/x-python", 67 | "name": "python", 68 | "nbconvert_exporter": "python", 69 | "pygments_lexer": "ipython3", 70 | "version": "3.6.5" 71 | } 72 | }, 73 | "nbformat": 4, 74 | "nbformat_minor": 1 75 | } 76 | -------------------------------------------------------------------------------- /Chapter14/ch14/falcon/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | 4 | import falcon 5 | 6 | from quotes import quotes 7 | 8 | 9 | class QuoteResource: 10 | def on_get(self, req, resp): 11 | quote = { 12 | 'quote': random.choice(quotes), 13 | 'author': 'The Buddha' 14 | } 15 | resp.body = json.dumps(quote) 16 | 17 | 18 | api = falcon.API() 19 | api.add_route('/quote', QuoteResource()) 20 | -------------------------------------------------------------------------------- /Chapter14/ch14/falcon/quotes.py: -------------------------------------------------------------------------------- 1 | quotes = [ 2 | "Thousands of candles can be lighted from a single candle, " 3 | "and the life of the candle will not be shortened. " 4 | "Happiness never decreases by being shared.", 5 | "You will not be punished for your anger, " 6 | "you will be punished by your anger.", 7 | "Peace comes from within. Do not seek it without.", 8 | "The mind is everything. What you think you become.", 9 | "No one saves us but ourselves. No one can and no one may. " 10 | "We ourselves must walk the path.", 11 | "Do not dwell in the past, do not dream of the future, " 12 | "concentrate the mind on the present moment.", 13 | "We are shaped by our thoughts; we become what we think. " 14 | "When the mind is pure, joy follows like a shadow that " 15 | "never leaves.", 16 | "There are only two mistakes one can make along the road " 17 | "to truth; not going all the way, and not starting.", 18 | "The whole secret of existence is to have no fear. " 19 | "Never fear what will become of you, depend on no one. " 20 | "Only the moment you reject all help are you freed.", 21 | "However many holy words you read, however many you speak, " 22 | "what good will they do you if you do not act on upon them?", 23 | ] 24 | -------------------------------------------------------------------------------- /Chapter14/ch14/falcon/stress.py: -------------------------------------------------------------------------------- 1 | from concurrent.futures import ThreadPoolExecutor, as_completed 2 | import urllib.request 3 | from time import time 4 | 5 | N = 2500 6 | 7 | URLS = ['http://127.0.0.1:8000/quote'] * N 8 | 9 | 10 | def load_url(url, timeout): 11 | return urllib.request.urlopen(url, timeout=timeout).read() 12 | 13 | start = time() 14 | with ThreadPoolExecutor(max_workers=16) as executor: 15 | future_to_url = dict((executor.submit(load_url, url, 60), url) 16 | for url in URLS) 17 | 18 | counter = 0 19 | for future in as_completed(future_to_url): 20 | url = future_to_url[future] 21 | if future.exception() is not None: 22 | print('%r generated an exception: %s' % ( 23 | url, future.exception()) 24 | ) 25 | else: 26 | counter += 1 27 | # print('%r page is %d bytes' % ( 28 | # url, len(future.result())) 29 | # ) 30 | # print(future.result()) 31 | 32 | tot = time() - start 33 | print(f'Rendered {N} pages in {tot:.3f} secs') 34 | -------------------------------------------------------------------------------- /Chapter14/ch14/flask/main.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template 2 | 3 | app = Flask(__name__) 4 | 5 | 6 | @app.route('/') 7 | @app.route('/') 8 | def hello(name=None): 9 | return render_template('main.html', name=name) 10 | 11 | 12 | # start with: 13 | # $ FLASK_APP=main.py flask run 14 | -------------------------------------------------------------------------------- /Chapter14/ch14/flask/templates/main.html: -------------------------------------------------------------------------------- 1 | 2 | Hello from Flask 3 |

4 | {% if name %} 5 | Hello {{ name }}! 6 | {% else %} 7 | Hello shy person! 8 | {% endif %} 9 |

10 | -------------------------------------------------------------------------------- /Chapter14/ch14/regex/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter14/ch14/regex/db.sqlite3 -------------------------------------------------------------------------------- /Chapter14/ch14/regex/entries/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter14/ch14/regex/entries/__init__.py -------------------------------------------------------------------------------- /Chapter14/ch14/regex/entries/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Entry 4 | 5 | 6 | @admin.register(Entry) 7 | class EntryAdmin(admin.ModelAdmin): 8 | fieldsets = [ 9 | ('Regular Expression', 10 | {'fields': ['pattern', 'test_string']}), 11 | ('Other Information', 12 | {'fields': ['user', 'date_added']}), 13 | ] 14 | list_display = ('pattern', 'test_string', 'user') 15 | list_filter = ['user'] 16 | search_fields = ['test_string'] 17 | -------------------------------------------------------------------------------- /Chapter14/ch14/regex/entries/forms.py: -------------------------------------------------------------------------------- 1 | from django.forms import ModelForm 2 | 3 | from .models import Entry 4 | 5 | 6 | class EntryForm(ModelForm): 7 | class Meta: 8 | model = Entry 9 | fields = ['pattern', 'test_string'] 10 | -------------------------------------------------------------------------------- /Chapter14/ch14/regex/entries/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-05-15 20:58 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Entry', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('pattern', models.CharField(max_length=255)), 23 | ('test_string', models.CharField(max_length=255)), 24 | ('date_added', models.DateTimeField(default=django.utils.timezone.now)), 25 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 26 | ], 27 | options={ 28 | 'verbose_name_plural': 'entries', 29 | }, 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /Chapter14/ch14/regex/entries/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter14/ch14/regex/entries/migrations/__init__.py -------------------------------------------------------------------------------- /Chapter14/ch14/regex/entries/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from django.utils import timezone 4 | 5 | 6 | class Entry(models.Model): 7 | user = models.ForeignKey(User, on_delete=models.CASCADE) 8 | pattern = models.CharField(max_length=255) 9 | test_string = models.CharField(max_length=255) 10 | date_added = models.DateTimeField(default=timezone.now) 11 | 12 | class Meta: 13 | verbose_name_plural = 'entries' 14 | -------------------------------------------------------------------------------- /Chapter14/ch14/regex/entries/static/entries/css/main.css: -------------------------------------------------------------------------------- 1 | /* HTML tags */ 2 | a { color: #333; } 3 | /* Classes */ 4 | .footer { margin-top: 1em; color: #333; } 5 | .entries-table { border-spacing: 0; padding-top: 1em; } 6 | .entries-table thead { background-color: #333; color: #fff; 7 | padding: 2em; font-size: 1.4em; } 8 | .entries-table td { padding: 1em; vertical-align: top; } 9 | .light-gray { background-color: #eee; } 10 | .white { background-color: #fff; } 11 | .code { font-size: 1.1em; font-weight: bolder; color: #333; } 12 | .home-option { padding: .6em 0; } 13 | /* Messages */ 14 | .success, .errorlist {font-size: 1.2em; font-weight: bold; } 15 | .success {color: #25B725; } 16 | .errorlist {color: #B12B2B; } 17 | -------------------------------------------------------------------------------- /Chapter14/ch14/regex/entries/templates/entries/base.html: -------------------------------------------------------------------------------- 1 | {% load static from staticfiles %} 2 | 3 | 4 | 5 | {% block meta %} 6 | 7 | 9 | {% endblock meta %} 10 | 11 | {% block styles %} 12 | 14 | {% endblock styles %} 15 | 16 | {% block title %}Title{% endblock title %} 17 | 18 | 19 | 20 |
21 | {% block page-content %} 22 | {% endblock page-content %} 23 |
24 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Chapter14/ch14/regex/entries/templates/entries/footer.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /Chapter14/ch14/regex/entries/templates/entries/home.html: -------------------------------------------------------------------------------- 1 | {% extends "entries/base.html" %} 2 | {% block title%}Welcome to the Entry website.{% endblock title %} 3 | 4 | {% block page-content %} 5 |

Welcome {{ user.first_name }}!

6 | 7 |
To see the list of your entries 8 | please click here. 9 |
10 |
To insert a new entry please click 11 | here. 12 |
13 |
To login as another user please click 14 | here. 15 |
16 |
To go to the admin panel 17 | please click here. 18 |
19 | {% endblock page-content %} 20 | -------------------------------------------------------------------------------- /Chapter14/ch14/regex/entries/templates/entries/insert.html: -------------------------------------------------------------------------------- 1 | {% extends "entries/base.html" %} 2 | {% block title%}Insert a new Entry{% endblock title %} 3 | 4 | {% block page-content %} 5 | {% if messages %} 6 | {% for message in messages %} 7 |

{{ message }}

8 | {% endfor %} 9 | {% endif %} 10 | 11 |

Insert a new Entry

12 |
13 | {% csrf_token %}{{ form.as_p }} 14 | 15 |

16 | {% endblock page-content %} 17 | 18 | {% block footer %} 19 | 20 | {% include "entries/footer.html" %} 21 | {% endblock footer %} 22 | -------------------------------------------------------------------------------- /Chapter14/ch14/regex/entries/templates/entries/list.html: -------------------------------------------------------------------------------- 1 | {% extends "entries/base.html" %} 2 | {% block title%} Entries list {% endblock title %} 3 | 4 | {% block page-content %} 5 | {% if entries %} 6 |

Your entries ({{ entries|length }} found)

7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for entry, match in entries %} 15 | 16 | 23 | 33 | 34 | {% endfor %} 35 | 36 |
EntryMatches
17 | Pattern: 18 | "{{ entry.pattern }}"
19 | Test String: 20 | "{{ entry.test_string }}"
21 | Added: {{ entry.date_added }} 22 |
24 | {% if match %} 25 | Group: {{ match.0 }}
26 | Subgroups: 27 | {{ match.1|default_if_none:"none" }}
28 | Group Dict: {{ match.2|default_if_none:"none" }} 29 | {% else %} 30 | No matches found. 31 | {% endif %} 32 |
37 | {% else %} 38 |

You have no entries

39 | 40 | {% endif %} 41 | {% endblock page-content %} 42 | 43 | {% block footer %} 44 | {% include "entries/footer.html" %} 45 | {% endblock footer %} 46 | -------------------------------------------------------------------------------- /Chapter14/ch14/regex/entries/views.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django.contrib.auth.decorators import login_required 4 | from django.contrib.messages.views import SuccessMessageMixin 5 | from django.urls import reverse_lazy 6 | from django.utils.decorators import method_decorator 7 | from django.views.generic import FormView, TemplateView 8 | 9 | from .forms import EntryForm 10 | from .models import Entry 11 | 12 | 13 | class HomeView(TemplateView): 14 | template_name = 'entries/home.html' 15 | 16 | @method_decorator( 17 | login_required(login_url=reverse_lazy('login'))) 18 | def get(self, request, *args, **kwargs): 19 | return super(HomeView, self).get(request, *args, **kwargs) 20 | 21 | 22 | class EntryListView(TemplateView): 23 | template_name = 'entries/list.html' 24 | 25 | @method_decorator( 26 | login_required(login_url=reverse_lazy('login'))) 27 | def get(self, request, *args, **kwargs): 28 | context = self.get_context_data(**kwargs) 29 | entries = Entry.objects.filter( 30 | user=request.user).order_by('-date_added') 31 | matches = (self._parse_entry(entry) for entry in entries) 32 | context['entries'] = list(zip(entries, matches)) 33 | return self.render_to_response(context) 34 | 35 | def _parse_entry(self, entry): 36 | match = re.search(entry.pattern, entry.test_string) 37 | if match is not None: 38 | return ( 39 | match.group(), 40 | match.groups() or None, 41 | match.groupdict() or None 42 | ) 43 | return None 44 | 45 | 46 | class EntryFormView(SuccessMessageMixin, FormView): 47 | template_name = 'entries/insert.html' 48 | form_class = EntryForm 49 | success_url = reverse_lazy('insert') 50 | success_message = "Entry was created successfully" 51 | 52 | @method_decorator( 53 | login_required(login_url=reverse_lazy('login'))) 54 | def get(self, request, *args, **kwargs): 55 | return super(EntryFormView, self).get( 56 | request, *args, **kwargs) 57 | 58 | @method_decorator( 59 | login_required(login_url=reverse_lazy('login'))) 60 | def post(self, request, *args, **kwargs): 61 | return super(EntryFormView, self).post( 62 | request, *args, **kwargs) 63 | 64 | def form_valid(self, form): 65 | self._save_with_user(form) 66 | return super(EntryFormView, self).form_valid(form) 67 | 68 | def _save_with_user(self, form): 69 | self.object = form.save(commit=False) 70 | self.object.user = self.request.user 71 | self.object.save() 72 | -------------------------------------------------------------------------------- /Chapter14/ch14/regex/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "regex.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /Chapter14/ch14/regex/regex/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Learn-Python-Programming-Second-Edition/54fee44ff1c696df0c7da1e3e84a6c2271a78904/Chapter14/ch14/regex/regex/__init__.py -------------------------------------------------------------------------------- /Chapter14/ch14/regex/regex/urls.py: -------------------------------------------------------------------------------- 1 | """regex URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path 18 | from django.contrib.auth import views as auth_views 19 | from django.urls import reverse_lazy 20 | 21 | from entries.views import HomeView, EntryListView, EntryFormView 22 | 23 | 24 | urlpatterns = [ 25 | path('admin/', admin.site.urls), 26 | path('entries/', EntryListView.as_view(), name='entries'), 27 | path('entries/insert', 28 | EntryFormView.as_view(), 29 | name='insert'), 30 | 31 | path('login/', 32 | auth_views.login, 33 | kwargs={'template_name': 'admin/login.html'}, 34 | name='login'), 35 | path('logout/', 36 | auth_views.logout, 37 | kwargs={'next_page': reverse_lazy('home')}, 38 | name='logout'), 39 | 40 | path('', HomeView.as_view(), name='home'), 41 | ] 42 | -------------------------------------------------------------------------------- /Chapter14/ch14/regex/regex/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for regex project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "regex.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /requirements/requirements.data.science.in: -------------------------------------------------------------------------------- 1 | arrow 2 | faker 3 | jupyter 4 | matplotlib 5 | numpy 6 | pandas 7 | xlwt -------------------------------------------------------------------------------- /requirements/requirements.data.science.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file requirements.data.science.txt requirements.data.science.in 6 | # 7 | appnope==0.1.0 # via ipython 8 | arrow==0.12.1 9 | backcall==0.1.0 # via ipython 10 | bleach==2.1.3 # via nbconvert 11 | cycler==0.10.0 # via matplotlib 12 | decorator==4.3.0 # via ipython, traitlets 13 | entrypoints==0.2.3 # via nbconvert 14 | faker==0.8.16 15 | html5lib==1.0.1 # via bleach 16 | ipykernel==4.8.2 # via ipywidgets, jupyter, jupyter-console, notebook, qtconsole 17 | ipython-genutils==0.2.0 # via nbformat, notebook, qtconsole, traitlets 18 | ipython==6.4.0 # via ipykernel, ipywidgets, jupyter-console 19 | ipywidgets==7.2.1 # via jupyter 20 | jedi==0.12.0 # via ipython 21 | jinja2==2.10 # via nbconvert, notebook 22 | jsonschema==2.6.0 # via nbformat 23 | jupyter-client==5.2.3 # via ipykernel, jupyter-console, notebook, qtconsole 24 | jupyter-console==5.2.0 # via jupyter 25 | jupyter-core==4.4.0 # via jupyter-client, nbconvert, nbformat, notebook, qtconsole 26 | jupyter==1.0.0 27 | kiwisolver==1.0.1 # via matplotlib 28 | markupsafe==1.0 # via jinja2 29 | matplotlib==2.2.2 30 | mistune==0.8.3 # via nbconvert 31 | nbconvert==5.3.1 # via jupyter, notebook 32 | nbformat==4.4.0 # via ipywidgets, nbconvert, notebook 33 | notebook==5.5.0 # via jupyter, widgetsnbextension 34 | numpy==1.14.5 35 | pandas==0.23.1 36 | pandocfilters==1.4.2 # via nbconvert 37 | parso==0.2.1 # via jedi 38 | pexpect==4.6.0 # via ipython 39 | pickleshare==0.7.4 # via ipython 40 | prompt-toolkit==1.0.15 # via ipython, jupyter-console 41 | ptyprocess==0.6.0 # via pexpect, terminado 42 | pygments==2.2.0 # via ipython, jupyter-console, nbconvert, qtconsole 43 | pyparsing==2.2.0 # via matplotlib 44 | python-dateutil==2.7.3 # via arrow, faker, jupyter-client, matplotlib, pandas 45 | pytz==2018.4 # via matplotlib, pandas 46 | pyzmq==17.0.0 # via jupyter-client, notebook 47 | qtconsole==4.3.1 # via jupyter 48 | send2trash==1.5.0 # via notebook 49 | simplegeneric==0.8.1 # via ipython 50 | six==1.11.0 # via bleach, cycler, faker, html5lib, matplotlib, prompt-toolkit, python-dateutil, traitlets 51 | terminado==0.8.1 # via notebook 52 | testpath==0.3.1 # via nbconvert 53 | text-unidecode==1.2 # via faker 54 | tornado==5.0.2 # via ipykernel, jupyter-client, notebook, terminado 55 | traitlets==4.3.2 # via ipykernel, ipython, ipywidgets, jupyter-client, jupyter-core, nbconvert, nbformat, notebook, qtconsole 56 | wcwidth==0.1.7 # via prompt-toolkit 57 | webencodings==0.5.1 # via html5lib 58 | widgetsnbextension==3.2.1 # via ipywidgets 59 | xlwt==1.3.0 60 | -------------------------------------------------------------------------------- /requirements/requirements.in: -------------------------------------------------------------------------------- 1 | aiohttp 2 | beautifulsoup4 3 | cryptography 4 | django 5 | falcon 6 | flask 7 | gunicorn 8 | marshmallow 9 | pdbpp 10 | pyjwt 11 | pytest 12 | requests 13 | SQLAlchemy 14 | -------------------------------------------------------------------------------- /requirements/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file requirements.txt requirements.in 6 | # 7 | aiohttp==3.3.2 8 | asn1crypto==0.24.0 # via cryptography 9 | async-timeout==3.0.0 # via aiohttp 10 | atomicwrites==1.1.5 # via pytest 11 | attrs==18.1.0 # via aiohttp, pytest 12 | beautifulsoup4==4.6.0 13 | certifi==2018.4.16 # via requests 14 | cffi==1.11.5 # via cryptography 15 | chardet==3.0.4 # via aiohttp, requests 16 | click==6.7 # via flask 17 | cryptography==2.2.2 18 | django==2.0.6 19 | falcon==1.4.1 20 | fancycompleter==0.8 # via pdbpp 21 | flask==1.0.2 22 | gunicorn==19.8.1 23 | idna==2.7 # via cryptography, requests, yarl 24 | itsdangerous==0.24 # via flask 25 | jinja2==2.10 # via flask 26 | markupsafe==1.0 # via jinja2 27 | marshmallow==2.15.3 28 | more-itertools==4.2.0 # via pytest 29 | multidict==4.3.1 # via aiohttp, yarl 30 | pdbpp==0.9.2 31 | pluggy==0.6.0 # via pytest 32 | py==1.5.3 # via pytest 33 | pycparser==2.18 # via cffi 34 | pygments==2.2.0 # via pdbpp 35 | pyjwt==1.6.4 36 | pytest==3.6.2 37 | python-mimeparse==1.6.0 # via falcon 38 | pytz==2018.4 # via django 39 | requests==2.19.1 40 | six==1.11.0 # via cryptography, falcon, more-itertools, pytest 41 | sqlalchemy==1.2.8 42 | urllib3==1.23 # via requests 43 | werkzeug==0.14.1 # via flask 44 | wmctrl==0.3 # via pdbpp 45 | yarl==1.2.6 # via aiohttp 46 | --------------------------------------------------------------------------------