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

Sign up
Appearance settings

Commit 80f7f84

Browse files
committed
2e reviewed manuscript
1 parent f5e3cb8 commit 80f7f84

File tree

32 files changed

+323
-156
lines changed

32 files changed

+323
-156
lines changed

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def standard_env() -> Environment:
8484
'-': op.sub,
8585
'*': op.mul,
8686
'/': op.truediv,
87-
'//': op.floordiv,
87+
'quotient': op.floordiv,
8888
'>': op.gt,
8989
'<': op.lt,
9090
'>=': op.ge,
@@ -149,7 +149,7 @@ def evaluate(exp: Expression, env: Environment) -> Any:
149149
# end::EVAL_MATCH_TOP[]
150150
case int(x) | float(x):
151151
return x
152-
case Symbol(name):
152+
case Symbol() asname:
153153
return env[name]
154154
# tag::EVAL_MATCH_MIDDLE[]
155155
case ['quote', x]: # <1>
@@ -161,12 +161,12 @@ def evaluate(exp: Expression, env: Environment) -> Any:
161161
return evaluate(alternative, env)
162162
case ['lambda', [*parms], *body] if body: # <3>
163163
return Procedure(parms, body, env)
164-
case ['define', Symbol(name), value_exp]: # <4>
164+
case ['define', Symbol() asname, value_exp]: # <4>
165165
env[name] = evaluate(value_exp, env)
166166
# end::EVAL_MATCH_MIDDLE[]
167-
case ['define', [Symbol(name), *parms], *body] if body:
167+
case ['define', [Symbol() asname, *parms], *body] if body:
168168
env[name] = Procedure(parms, body, env)
169-
case ['set!', Symbol(name), value_exp]:
169+
case ['set!', Symbol() asname, value_exp]:
170170
env.change(name, evaluate(value_exp, env))
171171
case [func_exp, *args] if func_exp not in KEYWORDS:
172172
proc = evaluate(func_exp, env)

‎11-pythonic-obj/vector2d_v3.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,6 @@
7979
8080
>>> v1 = Vector2d(3, 4)
8181
>>> v2 = Vector2d(3.1, 4.2)
82-
>>> hash(v1), hash(v2)
83-
(7, 384307168202284039)
8482
>>> len({v1, v2})
8583
2
8684
@@ -124,7 +122,7 @@ def __eq__(self, other):
124122
return tuple(self) == tuple(other)
125123

126124
def __hash__(self):
127-
return hash(self.x) ^hash(self.y)
125+
return hash((self.x, self.y))
128126

129127
def __abs__(self):
130128
return math.hypot(self.x, self.y)

‎11-pythonic-obj/vector2d_v3_prophash.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,6 @@
8181
8282
>>> v1 = Vector2d(3, 4)
8383
>>> v2 = Vector2d(3.1, 4.2)
84-
>>> hash(v1), hash(v2)
85-
(7, 384307168202284039)
8684
>>> len({v1, v2})
8785
2
8886
@@ -131,7 +129,7 @@ def __eq__(self, other):
131129

132130
# tag::VECTOR_V3_HASH[]
133131
def __hash__(self):
134-
return hash(self.x) ^hash(self.y)
132+
return hash((self.x, self.y))
135133
# end::VECTOR_V3_HASH[]
136134

137135
def __abs__(self):

‎11-pythonic-obj/vector2d_v3_slots.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,6 @@
7878
7979
>>> v1 = Vector2d(3, 4)
8080
>>> v2 = Vector2d(3.1, 4.2)
81-
>>> hash(v1), hash(v2)
82-
(7, 384307168202284039)
8381
>>> len({v1, v2})
8482
2
8583
@@ -126,7 +124,7 @@ def __eq__(self, other):
126124
return tuple(self) == tuple(other)
127125

128126
def __hash__(self):
129-
return hash(self.x) ^hash(self.y)
127+
return hash((self.x, self.y))
130128

131129
def __abs__(self):
132130
return math.hypot(self.x, self.y)

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@
2727
>>> inner_env = {'a': 2}
2828
>>> outer_env = {'a': 0, 'b': 1}
2929
>>> env = Environment(inner_env, outer_env)
30-
>>> env['a'] = 111 # <1>
30+
>>> env['a'] # <1>
31+
2
32+
>>> env['a'] = 111 # <2>
3133
>>> env['c'] = 222
3234
>>> env
3335
Environment({'a': 111, 'c': 222}, {'a': 0, 'b': 1})
34-
>>> env.change('b', 333) # <2>
36+
>>> env.change('b', 333) # <3>
3537
>>> env
3638
Environment({'a': 111, 'c': 222}, {'a': 0, 'b': 333})
3739

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def parse_atom(token: str) -> Atom:
6565
class Environment(ChainMap[Symbol, Any]):
6666
"A ChainMap that allows changing an item in-place."
6767

68-
def change(self, key: Symbol, value: object) -> None:
68+
def change(self, key: Symbol, value: Any) -> None:
6969
"Find where key is defined and change the value there."
7070
for map in self.maps:
7171
if key in map:

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def parse_atom(token: str) -> Atom:
6262
class Environment(ChainMap[Symbol, Any]):
6363
"A ChainMap that allows changing an item in-place."
6464

65-
def change(self, key: Symbol, value: object) -> None:
65+
def change(self, key: Symbol, value: Any) -> None:
6666
"Find where key is defined and change the value there."
6767
for map in self.maps:
6868
if key in map:

‎20-executors/getflags/flags2_asyncio.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ async def download_one(client: httpx.AsyncClient,
3737
try:
3838
async with semaphore: # <3>
3939
image = await get_flag(client, base_url, cc)
40-
except httpx.HTTPStatusError as exc: # <5>
40+
except httpx.HTTPStatusError as exc: # <4>
4141
res = exc.response
4242
if res.status_code == HTTPStatus.NOT_FOUND:
4343
status = DownloadStatus.NOT_FOUND
4444
msg = f'not found: {res.url}'
4545
else:
4646
raise
4747
else:
48-
await asyncio.to_thread(save_flag, image, f'{cc}.gif') # <6>
48+
await asyncio.to_thread(save_flag, image, f'{cc}.gif') # <5>
4949
status = DownloadStatus.OK
5050
msg = 'OK'
5151
if verbose and msg:

‎20-executors/getflags/flags2_asyncio_executor.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,23 @@
2222
MAX_CONCUR_REQ = 1000
2323

2424

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

3333

34-
async def download_one(session: httpx.AsyncClient,
34+
async def download_one(client: httpx.AsyncClient,
3535
cc: str,
3636
base_url: str,
3737
semaphore: asyncio.Semaphore,
3838
verbose: bool) -> DownloadStatus:
3939
try:
4040
async with semaphore:
41-
image = await get_flag(session, base_url, cc)
41+
image = await get_flag(client, base_url, cc)
4242
except httpx.HTTPStatusError as exc:
4343
res = exc.response
4444
if res.status_code == HTTPStatus.NOT_FOUND:
@@ -64,8 +64,8 @@ async def supervisor(cc_list: list[str],
6464
concur_req: int) -> Counter[DownloadStatus]: # <1>
6565
counter: Counter[DownloadStatus] = Counter()
6666
semaphore = asyncio.Semaphore(concur_req) # <2>
67-
async with httpx.AsyncClient() as session:
68-
to_do = [download_one(session, cc, base_url, semaphore, verbose)
67+
async with httpx.AsyncClient() as client:
68+
to_do = [download_one(client, cc, base_url, semaphore, verbose)
6969
for cc in sorted(cc_list)] # <3>
7070
to_do_iter = asyncio.as_completed(to_do) # <4>
7171
if not verbose:
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/usr/bin/env python3
2+
3+
"""Download flags of countries (with error handling).
4+
5+
asyncio async/await version
6+
7+
"""
8+
# tag::FLAGS2_ASYNCIO_TOP[]
9+
import asyncio
10+
from collections import Counter
11+
from http import HTTPStatus
12+
from pathlib import Path
13+
14+
import httpx
15+
import tqdm # type: ignore
16+
17+
from flags2_common import main, DownloadStatus, save_flag
18+
19+
# low concurrency default to avoid errors from remote site,
20+
# such as 503 - Service Temporarily Unavailable
21+
DEFAULT_CONCUR_REQ = 5
22+
MAX_CONCUR_REQ = 1000
23+
24+
async def get_flag(client: httpx.AsyncClient, # <1>
25+
base_url: str,
26+
cc: str) -> bytes:
27+
url = f'{base_url}/{cc}/{cc}.gif'.lower()
28+
resp = await client.get(url, timeout=3.1, follow_redirects=True) # <2>
29+
resp.raise_for_status()
30+
return resp.content
31+
32+
# tag::FLAGS3_ASYNCIO_GET_COUNTRY[]
33+
async def get_country(client: httpx.AsyncClient,
34+
base_url: str,
35+
cc: str) -> str: # <1>
36+
url = f'{base_url}/{cc}/metadata.json'.lower()
37+
resp = await client.get(url, timeout=3.1, follow_redirects=True)
38+
resp.raise_for_status()
39+
metadata = resp.json() # <2>
40+
return metadata['country'] # <3>
41+
# end::FLAGS3_ASYNCIO_GET_COUNTRY[]
42+
43+
# tag::FLAGS3_ASYNCIO_DOWNLOAD_ONE[]
44+
async def download_one(client: httpx.AsyncClient,
45+
cc: str,
46+
base_url: str,
47+
semaphore: asyncio.Semaphore,
48+
verbose: bool) -> DownloadStatus:
49+
try:
50+
async with semaphore: # <1>
51+
image = await get_flag(client, base_url, cc)
52+
async with semaphore: # <2>
53+
country = await get_country(client, base_url, cc)
54+
except httpx.HTTPStatusError as exc:
55+
res = exc.response
56+
if res.status_code == HTTPStatus.NOT_FOUND:
57+
status = DownloadStatus.NOT_FOUND
58+
msg = f'not found: {res.url}'
59+
else:
60+
raise
61+
else:
62+
filename = country.replace(' ', '_') # <3>
63+
await asyncio.to_thread(save_flag, image, f'{filename}.gif')
64+
status = DownloadStatus.OK
65+
msg = 'OK'
66+
if verbose and msg:
67+
print(cc, msg)
68+
return status
69+
# end::FLAGS3_ASYNCIO_DOWNLOAD_ONE[]
70+
71+
# tag::FLAGS2_ASYNCIO_START[]
72+
async def supervisor(cc_list: list[str],
73+
base_url: str,
74+
verbose: bool,
75+
concur_req: int) -> Counter[DownloadStatus]: # <1>
76+
counter: Counter[DownloadStatus] = Counter()
77+
semaphore = asyncio.Semaphore(concur_req) # <2>
78+
async with httpx.AsyncClient() as client:
79+
to_do = [download_one(client, cc, base_url, semaphore, verbose)
80+
for cc in sorted(cc_list)] # <3>
81+
to_do_iter = asyncio.as_completed(to_do) # <4>
82+
if not verbose:
83+
to_do_iter = tqdm.tqdm(to_do_iter, total=len(cc_list)) # <5>
84+
error: httpx.HTTPError | None = None # <6>
85+
for coro in to_do_iter: # <7>
86+
try:
87+
status = await coro # <8>
88+
except httpx.HTTPStatusError as exc:
89+
error_msg = 'HTTP error {resp.status_code} - {resp.reason_phrase}'
90+
error_msg = error_msg.format(resp=exc.response)
91+
error = exc # <9>
92+
except httpx.RequestError as exc:
93+
error_msg = f'{exc} {type(exc)}'.strip()
94+
error = exc # <10>
95+
except KeyboardInterrupt:
96+
break
97+
98+
if error:
99+
status = DownloadStatus.ERROR # <11>
100+
if verbose:
101+
url = str(error.request.url) # <12>
102+
cc = Path(url).stem.upper() # <13>
103+
print(f'{cc} error: {error_msg}')
104+
counter[status] += 1
105+
106+
return counter
107+
108+
def download_many(cc_list: list[str],
109+
base_url: str,
110+
verbose: bool,
111+
concur_req: int) -> Counter[DownloadStatus]:
112+
coro = supervisor(cc_list, base_url, verbose, concur_req)
113+
counts = asyncio.run(coro) # <14>
114+
115+
return counts
116+
117+
if __name__ == '__main__':
118+
main(download_many, DEFAULT_CONCUR_REQ, MAX_CONCUR_REQ)
119+
# end::FLAGS2_ASYNCIO_START[]

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /