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 584a7f2

Browse filesBrowse files
authored
Merge pull request fluentpython#4 from eumiro/ch21
ch21 cleanup, f-strings, and pathlib
2 parents 5ce5f8a + 93dfeae commit 584a7f2
Copy full SHA for 584a7f2
Expand file treeCollapse file tree

11 files changed

+70
-81
lines changed

‎21-futures/demo_executor_map.py

Copy file name to clipboardExpand all lines: 21-futures/demo_executor_map.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def main():
2424
display('results:', results) # <6>
2525
display('Waiting for individual results:')
2626
for i, result in enumerate(results): # <7>
27-
display('result {}: {}'.format(i, result))
27+
display(f'result {i}: {result}')
2828

2929
if __name__ == '__main__':
3030
main()

‎21-futures/getflags/flags.py

Copy file name to clipboardExpand all lines: 21-futures/getflags/flags.py
+3-6Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
"""
1818

1919
# tag::FLAGS_PY[]
20-
import os
2120
import time
21+
from pathlib import Path
2222
from typing import Callable
2323

2424
import requests # <1>
@@ -27,12 +27,10 @@
2727
'MX PH VN ET EG DE IR TR CD FR').split() # <2>
2828

2929
BASE_URL = 'http://fluentpython.com/data/flags' # <3>
30-
DEST_DIR = 'downloaded/' # <4>
30+
DEST_DIR = Path('downloaded') # <4>
3131

3232
def save_flag(img: bytes, filename: str) -> None: # <5>
33-
path = os.path.join(DEST_DIR, filename)
34-
with open(path, 'wb') as fp:
35-
fp.write(img)
33+
(DEST_DIR / filename).write_bytes(img)
3634

3735
def get_flag(cc: str) -> bytes: # <6>
3836
cc = cc.lower()
@@ -45,7 +43,6 @@ def download_many(cc_list: list[str]) -> int: # <7>
4543
image = get_flag(cc)
4644
print(cc, end=' ', flush=True) # <9>
4745
save_flag(image, cc.lower() + '.gif')
48-
4946
return len(cc_list)
5047

5148
def main(downloader: Callable[[list[str]], int]) -> None: # <10>

‎21-futures/getflags/flags2_asyncio.py

Copy file name to clipboardExpand all lines: 21-futures/getflags/flags2_asyncio.py
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ def __init__(self, country_code):
2828

2929

3030
async def get_flag(session, base_url, cc): # <2>
31-
url = '{}/{cc}/{cc}.gif'.format(base_url, cc=cc.lower())
31+
cc = cc.lower()
32+
url = f'{base_url}/{cc}/{cc}.gif'
3233
async with session.get(url) as resp:
3334
if resp.status == 200:
3435
return await resp.read()
@@ -87,8 +88,7 @@ async def downloader_coro(cc_list: list[str],
8788
except IndexError:
8889
error_msg = exc.__cause__.__class__.__name__ # <11>
8990
if verbose and error_msg:
90-
msg = '*** Error for {}: {}'
91-
print(msg.format(country_code, error_msg))
91+
print(f'*** Error for {country_code}: {error_msg}')
9292
status = HTTPStatus.error
9393
else:
9494
status = res.status

‎21-futures/getflags/flags2_common.py

Copy file name to clipboard
+45-45Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
"""Utilities for second set of flag examples.
22
"""
33

4-
import os
5-
import time
6-
import sys
7-
import string
84
import argparse
5+
import string
6+
import sys
7+
import time
98
from collections import namedtuple, Counter
109
from enum import Enum
11-
10+
from pathlib import Path
1211

1312
Result = namedtuple('Result', 'status data')
1413

@@ -28,14 +27,12 @@
2827
}
2928
DEFAULT_SERVER = 'LOCAL'
3029

31-
DEST_DIR = 'downloaded/'
32-
COUNTRY_CODES_FILE = 'country_codes.txt'
30+
DEST_DIR = Path('downloaded')
31+
COUNTRY_CODES_FILE = Path('country_codes.txt')
3332

3433

3534
def save_flag(img: bytes, filename: str) -> None:
36-
path = os.path.join(DEST_DIR, filename)
37-
with open(path, 'wb') as fp:
38-
fp.write(img)
35+
(DEST_DIR / filename).write_bytes(img)
3936

4037

4138
def initial_report(cc_list: list[str],
@@ -44,77 +41,79 @@ def initial_report(cc_list: list[str],
4441
if len(cc_list) <= 10:
4542
cc_msg = ', '.join(cc_list)
4643
else:
47-
cc_msg = 'from {} to {}'.format(cc_list[0], cc_list[-1])
48-
print('{} site: {}'.format(server_label, SERVERS[server_label]))
49-
msg = 'Searching for {} flag{}: {}'
44+
cc_msg = f'from {cc_list[0]} to {cc_list[-1]}'
45+
print(f'{server_label} site: {SERVERS[server_label]}')
5046
plural = 's' if len(cc_list) != 1 else ''
51-
print(msg.format(len(cc_list), plural, cc_msg))
47+
print(f'Searching for {len(cc_list)} flag{plural}: {cc_msg}')
5248
plural = 's' if actual_req != 1 else ''
53-
msg = '{} concurrent connection{} will be used.'
54-
print(msg.format(actual_req, plural))
49+
print(f'{actual_req} concurrent connection{plural} will be used.')
5550

5651

5752
def final_report(cc_list: list[str],
5853
counter: Counter[HTTPStatus],
5954
start_time: float) -> None:
60-
elapsed = time.time() - start_time
55+
elapsed = time.perf_counter() - start_time
6156
print('-' * 20)
62-
msg = '{} flag{} downloaded.'
6357
plural = 's' if counter[HTTPStatus.ok] != 1 else ''
64-
print(msg.format(counter[HTTPStatus.ok], plural))
58+
print(f'{counter[HTTPStatus.ok]} flag{plural} downloaded.')
6559
if counter[HTTPStatus.not_found]:
66-
print(counter[HTTPStatus.not_found], 'not found.')
60+
print(f'{counter[HTTPStatus.not_found]} not found.')
6761
if counter[HTTPStatus.error]:
6862
plural = 's' if counter[HTTPStatus.error] != 1 else ''
69-
print('{} error{}.'.format(counter[HTTPStatus.error], plural))
70-
print('Elapsed time: {:.2f}s'.format(elapsed))
63+
print(f'{counter[HTTPStatus.error]} error{plural}')
64+
print(f'Elapsed time: {elapsed:.2f}s')
7165

7266

7367
def expand_cc_args(every_cc: bool,
7468
all_cc: bool,
7569
cc_args: list[str],
7670
limit: int) -> list[str]:
7771
codes: set[str] = set()
78-
A_Z = string.ascii_uppercase
72+
A_Z = set(string.ascii_uppercase)
7973
if every_cc:
80-
codes.update(a+b for a in A_Z for b in A_Z)
74+
codes.update(f'{a}{b}' for a in A_Z for b in A_Z)
8175
elif all_cc:
82-
with open(COUNTRY_CODES_FILE) as fp:
83-
text = fp.read()
76+
text = COUNTRY_CODES_FILE.read_text()
8477
codes.update(text.split())
8578
else:
8679
for cc in (c.upper() for c in cc_args):
8780
if len(cc) == 1 and cc in A_Z:
88-
codes.update(cc+c for c in A_Z)
81+
codes.update(cc + c for c in A_Z)
8982
elif len(cc) == 2 and all(c in A_Z for c in cc):
9083
codes.add(cc)
9184
else:
92-
msg = 'each CC argument must be A to Z or AA to ZZ.'
93-
raise ValueError('*** Usage error: '+msg)
85+
raise ValueError('*** Usage error: each CC argument '
86+
'must be A to Z or AA to ZZ.')
9487
return sorted(codes)[:limit]
9588

9689

9790
def process_args(default_concur_req):
9891
server_options = ', '.join(sorted(SERVERS))
9992
parser = argparse.ArgumentParser(
10093
description='Download flags for country codes. '
101-
'Default: top 20 countries by population.')
102-
parser.add_argument('cc', metavar='CC', nargs='*',
94+
'Default: top 20 countries by population.')
95+
parser.add_argument(
96+
'cc', metavar='CC', nargs='*',
10397
help='country code or 1st letter (eg. B for BA...BZ)')
104-
parser.add_argument('-a', '--all', action='store_true',
98+
parser.add_argument(
99+
'-a', '--all', action='store_true',
105100
help='get all available flags (AD to ZW)')
106-
parser.add_argument('-e', '--every', action='store_true',
101+
parser.add_argument(
102+
'-e', '--every', action='store_true',
107103
help='get flags for every possible code (AA...ZZ)')
108-
parser.add_argument('-l', '--limit', metavar='N', type=int,
109-
help='limit to N first codes', default=sys.maxsize)
110-
parser.add_argument('-m', '--max_req', metavar='CONCURRENT', type=int,
104+
parser.add_argument(
105+
'-l', '--limit', metavar='N', type=int, help='limit to N first codes',
106+
default=sys.maxsize)
107+
parser.add_argument(
108+
'-m', '--max_req', metavar='CONCURRENT', type=int,
111109
default=default_concur_req,
112110
help=f'maximum concurrent requests (default={default_concur_req})')
113-
parser.add_argument('-s', '--server', metavar='LABEL',
114-
default=DEFAULT_SERVER,
115-
help=('Server to hit; one of ' +
116-
f'{server_options} (default={DEFAULT_SERVER})'))
117-
parser.add_argument('-v', '--verbose', action='store_true',
111+
parser.add_argument(
112+
'-s', '--server', metavar='LABEL', default=DEFAULT_SERVER,
113+
help=f'Server to hit; one of {server_options} '
114+
f'(default={DEFAULT_SERVER})')
115+
parser.add_argument(
116+
'-v', '--verbose', action='store_true',
118117
help='output detailed progress info')
119118
args = parser.parse_args()
120119
if args.max_req < 1:
@@ -127,8 +126,8 @@ def process_args(default_concur_req):
127126
sys.exit(1)
128127
args.server = args.server.upper()
129128
if args.server not in SERVERS:
130-
print('*** Usage error: --server LABEL must be one of',
131-
server_options)
129+
print(f'*** Usage error: --server LABEL '
130+
f'must be one of {server_options}')
132131
parser.print_usage()
133132
sys.exit(1)
134133
try:
@@ -148,8 +147,9 @@ def main(download_many, default_concur_req, max_concur_req):
148147
actual_req = min(args.max_req, max_concur_req, len(cc_list))
149148
initial_report(cc_list, actual_req, args.server)
150149
base_url = SERVERS[args.server]
151-
t0 = time.time()
150+
t0 = time.perf_counter()
152151
counter = download_many(cc_list, base_url, args.verbose, actual_req)
153-
assert sum(counter.values()) == len(cc_list), \
152+
assert sum(counter.values()) == len(cc_list), (
154153
'some downloads are unaccounted for'
154+
)
155155
final_report(cc_list, counter, t0)

‎21-futures/getflags/flags2_sequential.py

Copy file name to clipboardExpand all lines: 21-futures/getflags/flags2_sequential.py
-1Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
from flags2_common import main, save_flag, HTTPStatus, Result
2626

27-
2827
DEFAULT_CONCUR_REQ = 1
2928
MAX_CONCUR_REQ = 1
3029

‎21-futures/getflags/flags2_threadpool.py

Copy file name to clipboardExpand all lines: 21-futures/getflags/flags2_threadpool.py
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ def download_many(cc_list: list[str],
5050
try:
5151
res = future.result() # <14>
5252
except requests.exceptions.HTTPError as exc: # <15>
53-
error_msg = 'HTTP {res.status_code} - {res.reason}'
54-
error_msg = error_msg.format(res=exc.response)
53+
error_fmt = 'HTTP {res.status_code} - {res.reason}'
54+
error_msg = error_fmt.format(res=exc.response)
5555
except requests.exceptions.ConnectionError:
5656
error_msg = 'Connection error'
5757
else:
@@ -63,7 +63,7 @@ def download_many(cc_list: list[str],
6363
counter[status] += 1
6464
if verbose and error_msg:
6565
cc = to_do_map[future] # <16>
66-
print('*** Error for {}: {}'.format(cc, error_msg))
66+
print(f'*** Error for {cc}: {error_msg}')
6767

6868
return counter
6969

‎21-futures/getflags/flags_asyncio.py

Copy file name to clipboardExpand all lines: 21-futures/getflags/flags_asyncio.py
+1-2Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from flags import BASE_URL, save_flag, main # <2>
2020

21-
async def get_flag(session: ClientSession ,cc: str) -> bytes: # <3>
21+
async def get_flag(session: ClientSession, cc: str) -> bytes: # <3>
2222
cc = cc.lower()
2323
url = f'{BASE_URL}/{cc}/{cc}.gif'
2424
async with session.get(url) as resp: # <4>
@@ -36,7 +36,6 @@ async def supervisor(cc_list):
3636
to_do = [download_one(session, cc)
3737
for cc in sorted(cc_list)] # <8>
3838
res = await asyncio.gather(*to_do) # <9>
39-
4039
return len(res)
4140

4241
def download_many(cc_list):

‎21-futures/getflags/flags_threadpool.py

Copy file name to clipboardExpand all lines: 21-futures/getflags/flags_threadpool.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ def download_many(cc_list: list[str]) -> int:
3131

3232
if __name__ == '__main__':
3333
main(download_many) # <6>
34-
# end::FLAGS_THREADPOOL[]
34+
# end::FLAGS_THREADPOOL[]

‎21-futures/getflags/flags_threadpool_futures.py

Copy file name to clipboardExpand all lines: 21-futures/getflags/flags_threadpool_futures.py
+4-9Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"""
77
from concurrent import futures
88

9-
from flags import save_flag, main
9+
from flags import main
1010
from flags_threadpool import download_one
1111

1212

@@ -18,19 +18,14 @@ def download_many(cc_list: list[str]) -> int:
1818
for cc in sorted(cc_list): # <3>
1919
future = executor.submit(download_one, cc) # <4>
2020
to_do.append(future) # <5>
21-
msg = 'Scheduled for {}: {}'
22-
print(msg.format(cc, future)) # <6>
21+
print(f'Scheduled for {cc}: {future}') # <6>
2322

24-
count = 0
25-
for future in futures.as_completed(to_do): # <7>
23+
for count, future in enumerate(futures.as_completed(to_do)): # <7>
2624
res: str = future.result() # <8>
27-
msg = '{} result: {!r}'
28-
print(msg.format(future, res)) # <9>
29-
count += 1
25+
print(f'{future} result: {res!r}') # <9>
3026

3127
return count
3228
# end::FLAGS_THREADPOOL_AS_COMPLETED[]
3329

3430
if __name__ == '__main__':
3531
main(download_many)
36-

‎21-futures/getflags/slow_server.py

Copy file name to clipboardExpand all lines: 21-futures/getflags/slow_server.py
+5-6Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
the requests if given the --error_rate command-line argument.
99
"""
1010

11-
import time
11+
import contextlib
1212
import os
1313
import socket
14-
import contextlib
14+
import time
1515
from functools import partial
16-
from random import random
1716
from http import server, HTTPStatus
1817
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
18+
from random import random
1919

2020

2121
class SlowHTTPRequestHandler(SimpleHTTPRequestHandler):
@@ -52,7 +52,6 @@ def do_GET(self):
5252
# https://github.com/python/cpython/blob/master/Lib/http/server.py
5353

5454
if __name__ == '__main__':
55-
5655
import argparse
5756

5857
parser = argparse.ArgumentParser()
@@ -61,11 +60,11 @@ def do_GET(self):
6160
'[default: all interfaces]')
6261
parser.add_argument('--directory', '-d', default=os.getcwd(),
6362
help='Specify alternative directory '
64-
'[default:current directory]')
63+
'[default:current directory]')
6564
parser.add_argument('--error-rate', '-e', metavar='PROBABILITY',
6665
default=0.0, type=float,
6766
help='Error rate; e.g. use .25 for 25%% probability '
68-
'[default:0.0]')
67+
'[default:0.0]')
6968
parser.add_argument('port', action='store',
7069
default=8000, type=int,
7170
nargs='?',

‎21-futures/primes/proc_pool.py

Copy file name to clipboardExpand all lines: 21-futures/primes/proc_pool.py
+4-4Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
"""
77

88
# tag::PRIMES_POOL[]
9+
import sys
10+
from concurrent import futures # <1>
911
from time import perf_counter
1012
from typing import NamedTuple
11-
from concurrent import futures # <1>
12-
import sys
1313

1414
from primes import is_prime, NUMBERS
1515

@@ -43,8 +43,8 @@ def main() -> None:
4343
print(f'{n:16} {label} {elapsed:9.6f}s')
4444

4545
time = perf_counter() - t0
46-
print('Total time:', f'{time:0.2f}s')
46+
print(f'Total time: {time:.2f}s')
4747

4848
if __name__ == '__main__':
4949
main()
50-
# end::PRIMES_POOL[]
50+
# end::PRIMES_POOL[]

0 commit comments

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