Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e3ee75a

Browse filesBrowse files
committed
Merge branch 'master' of github.com:fluentpython/example-code-2e
2 parents f152417 + 6fb0832 commit e3ee75a
Copy full SHA for e3ee75a
Expand file treeCollapse file tree

29 files changed

+149
-71
lines changed

‎02-array-seq/lispy/py3.10/examples_test.py

Copy file name to clipboardExpand all lines: 02-array-seq/lispy/py3.10/examples_test.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def test_factorial():
147147

148148
gcd_src = """
149149
(define (mod m n)
150-
(- m (* n (// m n))))
150+
(- m (* n (quotient m n))))
151151
(define (gcd m n)
152152
(if (= n 0)
153153
m

‎02-array-seq/lispy/py3.9/lis.py

Copy file name to clipboardExpand all lines: 02-array-seq/lispy/py3.9/lis.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def standard_env() -> Environment:
8080
'-': op.sub,
8181
'*': op.mul,
8282
'/': op.truediv,
83-
'//': op.floordiv,
83+
'quotient': op.floordiv,
8484
'>': op.gt,
8585
'<': op.lt,
8686
'>=': op.ge,

‎18-with-match/lispy/py3.10/examples_test.py

Copy file name to clipboardExpand all lines: 18-with-match/lispy/py3.10/examples_test.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def test_factorial():
146146

147147
gcd_src = """
148148
(define (mod m n)
149-
(- m (* n (// m n))))
149+
(- m (* n (quotient m n))))
150150
(define (gcd m n)
151151
(if (= n 0)
152152
m

‎18-with-match/lispy/py3.10/lis.py

Copy file name to clipboardExpand all lines: 18-with-match/lispy/py3.10/lis.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def standard_env() -> Environment:
8383
'-': op.sub,
8484
'*': op.mul,
8585
'/': op.truediv,
86-
'//': op.floordiv,
86+
'quotient': op.floordiv,
8787
'>': op.gt,
8888
'<': op.lt,
8989
'>=': op.ge,

‎18-with-match/lispy/py3.9/examples_test.py

Copy file name to clipboardExpand all lines: 18-with-match/lispy/py3.9/examples_test.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def test_factorial():
147147

148148
gcd_src = """
149149
(define (mod m n)
150-
(- m (* n (// m n))))
150+
(- m (* n (quotient m n))))
151151
(define (gcd m n)
152152
(if (= n 0)
153153
m
@@ -255,4 +255,4 @@ def test_closure_with_change(capsys):
255255
def test_closure_averager():
256256
got = run(closure_averager_src)
257257
assert got == 12.0
258-
# end::RUN_AVERAGER[]
258+
# end::RUN_AVERAGER[]

‎18-with-match/lispy/py3.9/lis.py

Copy file name to clipboardExpand all lines: 18-with-match/lispy/py3.9/lis.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def standard_env() -> Environment:
8080
'-': op.sub,
8181
'*': op.mul,
8282
'/': op.truediv,
83-
'//': op.floordiv,
83+
'quotient': op.floordiv,
8484
'>': op.gt,
8585
'<': op.lt,
8686
'>=': op.ge,

‎19-concurrency/primes/procs.py

Copy file name to clipboardExpand all lines: 19-concurrency/primes/procs.py
+30-30Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,46 +31,46 @@ def worker(jobs: JobQueue, results: ResultQueue) -> None: # <7>
3131
while n := jobs.get(): # <8>
3232
results.put(check(n)) # <9>
3333
results.put(PrimeResult(0, False, 0.0)) # <10>
34-
# end::PRIMES_PROC_TOP[]
3534

36-
# tag::PRIMES_PROC_MIDDLE[]
37-
def start_jobs(workers: int, jobs: JobQueue, results: ResultQueue) -> None:
35+
def start_jobs(
36+
procs: int, jobs: JobQueue, results: ResultQueue # <11>
37+
) -> None:
3838
for n in NUMBERS:
39-
jobs.put(n) # <1>
40-
for _ in range(workers):
41-
proc = Process(target=worker, args=(jobs, results)) # <2>
42-
proc.start() # <3>
43-
jobs.put(0) # <4>
44-
45-
def report(workers: int, results: ResultQueue) -> int:
46-
checked = 0
47-
workers_done = 0
48-
while workers_done < workers:
49-
n, prime, elapsed = results.get()
50-
if n == 0:
51-
workers_done += 1
52-
else:
53-
checked += 1
54-
label = 'P' if prime else ' '
55-
print(f'{n:16} {label} {elapsed:9.6f}s')
56-
return checked
57-
# end::PRIMES_PROC_MIDDLE[]
39+
jobs.put(n) # <12>
40+
for _ in range(procs):
41+
proc = Process(target=worker, args=(jobs, results)) # <13>
42+
proc.start() # <14>
43+
jobs.put(0) # <15>
44+
# end::PRIMES_PROC_TOP[]
5845

5946
# tag::PRIMES_PROC_MAIN[]
6047
def main() -> None:
61-
if len(sys.argv) < 2:
62-
workers = cpu_count()
48+
if len(sys.argv) < 2: # <1>
49+
procs = cpu_count()
6350
else:
64-
workers = int(sys.argv[1])
51+
procs = int(sys.argv[1])
6552

66-
print(f'Checking {len(NUMBERS)} numbers with {workers} processes:')
53+
print(f'Checking {len(NUMBERS)} numbers with {procs} processes:')
6754
t0 = perf_counter()
68-
jobs: JobQueue = SimpleQueue()
55+
jobs: JobQueue = SimpleQueue() # <2>
6956
results: ResultQueue = SimpleQueue()
70-
start_jobs(workers, jobs, results)
71-
checked = report(workers, results)
57+
start_jobs(procs, jobs, results) # <3>
58+
checked = report(procs, results) # <4>
7259
elapsed = perf_counter() - t0
73-
print(f'{checked} checks in {elapsed:.2f}s')
60+
print(f'{checked} checks in {elapsed:.2f}s') # <5>
61+
62+
def report(procs: int, results: ResultQueue) -> int: # <6>
63+
checked = 0
64+
procs_done = 0
65+
while procs_done < procs: # <7>
66+
n, prime, elapsed = results.get() # <8>
67+
if n == 0: # <9>
68+
procs_done += 1
69+
else:
70+
checked += 1 # <10>
71+
label = 'P' if prime else ' '
72+
print(f'{n:16} {label} {elapsed:9.6f}s')
73+
return checked
7474

7575
if __name__ == '__main__':
7676
main()
+80Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
procs.py: shows that multiprocessing on a multicore machine
5+
can be faster than sequential code for CPU-intensive work.
6+
"""
7+
8+
# tag::PRIMES_PROC_TOP[]
9+
import sys
10+
from time import perf_counter
11+
from typing import NamedTuple
12+
from multiprocessing import Process, SimpleQueue, cpu_count # <1>
13+
from multiprocessing import queues # <2>
14+
15+
from primes import is_prime, NUMBERS
16+
17+
class PrimeResult(NamedTuple): # <3>
18+
n: int
19+
prime: bool
20+
elapsed: float
21+
22+
JobQueue = queues.SimpleQueue[int] # <4>
23+
ResultQueue = queues.SimpleQueue[PrimeResult] # <5>
24+
25+
def check(n: int) -> PrimeResult: # <6>
26+
t0 = perf_counter()
27+
res = is_prime(n)
28+
return PrimeResult(n, res, perf_counter() - t0)
29+
30+
def worker(jobs: JobQueue, results: ResultQueue) -> None: # <7>
31+
while n := jobs.get(): # <8>
32+
results.put(check(n)) # <9>
33+
results.put(PrimeResult(0, False, 0.0))
34+
# end::PRIMES_PROC_TOP[]
35+
36+
def start_jobs(workers: int) -> ResultQueue:
37+
jobs: JobQueue = SimpleQueue() # <2>
38+
results: ResultQueue = SimpleQueue()
39+
40+
for n in NUMBERS: # <3>
41+
jobs.put(n)
42+
43+
for _ in range(workers):
44+
proc = Process(target=worker, args=(jobs, results)) # <4>
45+
proc.start() # <5>
46+
jobs.put(0) # <6>
47+
48+
return results
49+
50+
def report(workers: int, results: ResultQueue) -> int:
51+
workers_done = 0
52+
checked = 0
53+
while workers_done < workers:
54+
n, prime, elapsed = results.get() # <7>
55+
if n == 0:
56+
workers_done += 1
57+
else:
58+
checked += 1
59+
label = 'P' if prime else ' '
60+
print(f'{n:16} {label} {elapsed:9.6f}s') # <8>
61+
return checked
62+
63+
64+
# tag::PRIMES_PROC_MAIN[]
65+
def main() -> None:
66+
if len(sys.argv) < 2: # <1>
67+
workers = cpu_count()
68+
else:
69+
workers = int(sys.argv[1])
70+
71+
print(f'Checking {len(NUMBERS)} numbers with {workers} processes:')
72+
t0 = perf_counter()
73+
results = start_jobs(workers)
74+
checked = report(workers, results)
75+
elapsed = perf_counter() - t0
76+
print(f'{checked} checks in {elapsed:.2f}s')
77+
78+
if __name__ == '__main__':
79+
main()
80+
# end::PRIMES_PROC_MAIN[]
File renamed without changes.

‎20-executors/getflags/.gitignore

Copy file name to clipboard
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
flags/
2+
downloaded/
File renamed without changes.
File renamed without changes.
File renamed without changes.

‎20-futures/getflags/flags2_asyncio.py renamed to ‎20-executors/getflags/flags2_asyncio.py

Copy file name to clipboardExpand all lines: 20-executors/getflags/flags2_asyncio.py
+24-27Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,37 +16,36 @@
1616

1717
from flags2_common import main, DownloadStatus, save_flag
1818

19-
# default set low to avoid errors from remote site, such as
20-
# 503 - Service Temporarily Unavailable
19+
# low concurrency default to avoid errors from remote site,
20+
# such as 503 - Service Temporarily Unavailable
2121
DEFAULT_CONCUR_REQ = 5
2222
MAX_CONCUR_REQ = 1000
2323

24-
async def get_flag(session: httpx.AsyncClient, # <2>
24+
async def get_flag(client: httpx.AsyncClient, # <1>
2525
base_url: str,
2626
cc: str) -> bytes:
2727
url = f'{base_url}/{cc}/{cc}.gif'.lower()
28-
resp = await session.get(url, timeout=3.1, follow_redirects=True) # <3>
28+
resp = await client.get(url, timeout=3.1, follow_redirects=True) # <2>
2929
resp.raise_for_status()
3030
return resp.content
3131

32-
async def download_one(session: httpx.AsyncClient,
32+
async def download_one(client: httpx.AsyncClient,
3333
cc: str,
3434
base_url: str,
35-
semaphore: asyncio.Semaphore, # <4>
35+
semaphore: asyncio.Semaphore,
3636
verbose: bool) -> DownloadStatus:
3737
try:
38-
async with semaphore: # <5>
39-
image = await get_flag(session, base_url, cc)
40-
except httpx.HTTPStatusError as exc: # <4>
38+
async with semaphore: # <3>
39+
image = await get_flag(client, base_url, cc)
40+
except httpx.HTTPStatusError as exc: # <5>
4141
res = exc.response
4242
if res.status_code == HTTPStatus.NOT_FOUND:
43-
status = DownloadStatus.NOT_FOUND # <5>
43+
status = DownloadStatus.NOT_FOUND
4444
msg = f'not found: {res.url}'
4545
else:
4646
raise
47-
4847
else:
49-
await asyncio.to_thread(save_flag, image, f'{cc}.gif')
48+
await asyncio.to_thread(save_flag, image, f'{cc}.gif') # <6>
5049
status = DownloadStatus.OK
5150
msg = 'OK'
5251
if verbose and msg:
@@ -61,33 +60,31 @@ async def supervisor(cc_list: list[str],
6160
concur_req: int) -> Counter[DownloadStatus]: # <1>
6261
counter: Counter[DownloadStatus] = Counter()
6362
semaphore = asyncio.Semaphore(concur_req) # <2>
64-
async with httpx.AsyncClient() as session:
65-
to_do = [download_one(session, cc, base_url, semaphore, verbose)
63+
async with httpx.AsyncClient() as client:
64+
to_do = [download_one(client, cc, base_url, semaphore, verbose)
6665
for cc in sorted(cc_list)] # <3>
6766
to_do_iter = asyncio.as_completed(to_do) # <4>
6867
if not verbose:
6968
to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) # <5>
70-
error: httpx.HTTPError | None = None
71-
for coro in to_do_iter: # <6>
69+
error: httpx.HTTPError | None = None # <6>
70+
for coro in to_do_iter: # <7>
7271
try:
73-
status = await coro # <7>
74-
except httpx.HTTPStatusError as exc: # <8>
72+
status = await coro # <8>
73+
except httpx.HTTPStatusError as exc:
7574
error_msg = 'HTTP error {resp.status_code} - {resp.reason_phrase}'
7675
error_msg = error_msg.format(resp=exc.response)
77-
error = exc
78-
except httpx.RequestError as exc: # <9>
76+
error = exc # <9>
77+
except httpx.RequestError as exc:
7978
error_msg = f'{exc} {type(exc)}'.strip()
80-
error = exc
81-
except KeyboardInterrupt: # <10>
79+
error = exc # <10>
80+
except KeyboardInterrupt:
8281
break
83-
else: # <11>
84-
error = None
8582

8683
if error:
87-
status = DownloadStatus.ERROR # <12>
84+
status = DownloadStatus.ERROR # <11>
8885
if verbose:
89-
url = str(error.request.url) # <13>
90-
cc = Path(url).stem.upper() # <14>
86+
url = str(error.request.url) # <12>
87+
cc = Path(url).stem.upper() # <13>
9188
print(f'{cc} error: {error_msg}')
9289
counter[status] += 1
9390

‎20-futures/getflags/flags2_threadpool.py renamed to ‎20-executors/getflags/flags2_threadpool.py

Copy file name to clipboardExpand all lines: 20-executors/getflags/flags2_threadpool.py
+5-5Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
# tag::FLAGS2_THREADPOOL[]
2222
from collections import Counter
23-
from concurrent import futures
23+
from concurrent.futures import ThreadPoolExecutor, as_completed
2424

2525
import httpx
2626
import tqdm # type: ignore
@@ -37,13 +37,13 @@ def download_many(cc_list: list[str],
3737
verbose: bool,
3838
concur_req: int) -> Counter[DownloadStatus]:
3939
counter: Counter[DownloadStatus] = Counter()
40-
with futures.ThreadPoolExecutor(max_workers=concur_req) as executor: # <4>
40+
with ThreadPoolExecutor(max_workers=concur_req) as executor: # <4>
4141
to_do_map = {} # <5>
4242
for cc in sorted(cc_list): # <6>
4343
future = executor.submit(download_one, cc,
4444
base_url, verbose) # <7>
4545
to_do_map[future] = cc # <8>
46-
done_iter = futures.as_completed(to_do_map) # <9>
46+
done_iter = as_completed(to_do_map) # <9>
4747
if not verbose:
4848
done_iter = tqdm.tqdm(done_iter, total=len(cc_list)) # <10>
4949
for future in done_iter: # <11>
@@ -52,7 +52,7 @@ def download_many(cc_list: list[str],
5252
except httpx.HTTPStatusError as exc: # <13>
5353
error_msg = 'HTTP error {resp.status_code} - {resp.reason_phrase}'
5454
error_msg = error_msg.format(resp=exc.response)
55-
except httpx.RequestError as exc: # <15>
55+
except httpx.RequestError as exc:
5656
error_msg = f'{exc} {type(exc)}'.strip()
5757
except KeyboardInterrupt:
5858
break
@@ -63,7 +63,7 @@ def download_many(cc_list: list[str],
6363
status = DownloadStatus.ERROR
6464
counter[status] += 1
6565
if verbose and error_msg:
66-
cc = to_do_map[future] # <16>
66+
cc = to_do_map[future] # <14>
6767
print(f'{cc} error: {error_msg}')
6868

6969
return counter
File renamed without changes.
File renamed without changes.
File renamed without changes.

‎20-futures/getflags/.gitignore

Copy file name to clipboardExpand all lines: 20-futures/getflags/.gitignore
-1Lines changed: 0 additions & 1 deletion
This file was deleted.

‎README.md

Copy file name to clipboardExpand all lines: README.md
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Part / Chapter #|Title|Directory|1<sup>st</sup> ed. Chapter&nbsp;#
4444
17|Iterators, Generators, and Classic Coroutines|[17-it-generator](17-it-generator)|14
4545
18|Context Managers and else Blocks|[18-with-match](18-with-match)|15
4646
19|Concurrency Models in Python|[19-concurrency](19-concurrency)|🆕
47-
20|Concurrency with Futures|[20-futures](20-futures)|17
47+
20|Concurrent Executors|[20-executors](20-executors)|17
4848
21|Asynchronous Programming|[21-async](21-async)|18
4949
**VI – Metaprogramming**|
5050
22|Dynamic Attributes and Properties|[22-dyn-attr-prop](22-dyn-attr-prop)|19

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.