Skip to content

Commit 47cafc8

Browse files
committed
ch11-24: clean up by @eumiro & sync with Atlas
1 parent 03ace4f commit 47cafc8

File tree

143 files changed

+21690
-61
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

143 files changed

+21690
-61
lines changed

11-pythonic-obj/private/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.class
2+
.jython_cache/

13-protocol-abc/README.rst

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Sample code for Chapter 11 - "Interfaces, protocols and ABCs"
2+
3+
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
4+
http://shop.oreilly.com/product/0636920032519.do

13-protocol-abc/bingo.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# tag::TOMBOLA_BINGO[]
2+
3+
import random
4+
5+
from tombola import Tombola
6+
7+
8+
class BingoCage(Tombola): # <1>
9+
10+
def __init__(self, items):
11+
self._randomizer = random.SystemRandom() # <2>
12+
self._items = []
13+
self.load(items) # <3>
14+
15+
def load(self, items):
16+
self._items.extend(items)
17+
self._randomizer.shuffle(self._items) # <4>
18+
19+
def pick(self): # <5>
20+
try:
21+
return self._items.pop()
22+
except IndexError:
23+
raise LookupError('pick from empty BingoCage')
24+
25+
def __call__(self): # <6>
26+
self.pick()
27+
28+
# end::TOMBOLA_BINGO[]
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def double(x: object) -> object:
2+
return x * 2
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from typing import TypeVar, Protocol
2+
3+
T = TypeVar('T') # <1>
4+
5+
class Repeatable(Protocol):
6+
def __mul__(self: T, repeat_count: int) -> T: ... # <2>
7+
8+
RT = TypeVar('RT', bound=Repeatable) # <3>
9+
10+
def double(x: RT) -> RT: # <4>
11+
return x * 2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from collections import abc
2+
from typing import Any
3+
4+
def double(x: abc.Sequence) -> Any:
5+
return x * 2
6+

13-protocol-abc/double/double_test.py

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from typing import TYPE_CHECKING
2+
import pytest
3+
from double_protocol import double
4+
5+
def test_double_int() -> None:
6+
given = 2
7+
result = double(given)
8+
assert result == given * 2
9+
if TYPE_CHECKING:
10+
reveal_type(given)
11+
reveal_type(result)
12+
13+
14+
def test_double_str() -> None:
15+
given = 'A'
16+
result = double(given)
17+
assert result == given * 2
18+
if TYPE_CHECKING:
19+
reveal_type(given)
20+
reveal_type(result)
21+
22+
23+
def test_double_fraction() -> None:
24+
from fractions import Fraction
25+
given = Fraction(2, 5)
26+
result = double(given)
27+
assert result == given * 2
28+
if TYPE_CHECKING:
29+
reveal_type(given)
30+
reveal_type(result)
31+
32+
33+
def test_double_array() -> None:
34+
from array import array
35+
given = array('d', [1.0, 2.0, 3.14])
36+
result = double(given)
37+
if TYPE_CHECKING:
38+
reveal_type(given)
39+
reveal_type(result)
40+
41+
42+
def test_double_nparray() -> None:
43+
import numpy as np # type: ignore
44+
given = np.array([[1, 2], [3, 4]])
45+
result = double(given)
46+
comparison = result == given * 2
47+
assert comparison.all()
48+
if TYPE_CHECKING:
49+
reveal_type(given)
50+
reveal_type(result)
51+
52+
53+
def test_double_none() -> None:
54+
given = None
55+
with pytest.raises(TypeError):
56+
result = double(given)

13-protocol-abc/drum.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from random import shuffle
2+
3+
from tombola import Tombola
4+
5+
6+
class TumblingDrum(Tombola):
7+
8+
def __init__(self, iterable):
9+
self._balls = []
10+
self.load(iterable)
11+
12+
def load(self, iterable):
13+
self._balls.extend(iterable)
14+
shuffle(self._balls)
15+
16+
def pick(self):
17+
return self._balls.pop()

13-protocol-abc/frenchdeck2.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import collections
2+
3+
Card = collections.namedtuple('Card', ['rank', 'suit'])
4+
5+
class FrenchDeck2(collections.MutableSequence):
6+
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
7+
suits = 'spades diamonds clubs hearts'.split()
8+
9+
def __init__(self):
10+
self._cards = [Card(rank, suit) for suit in self.suits
11+
for rank in self.ranks]
12+
13+
def __len__(self):
14+
return len(self._cards)
15+
16+
def __getitem__(self, position):
17+
return self._cards[position]
18+
19+
def __setitem__(self, position, value): # <1>
20+
self._cards[position] = value
21+
22+
def __delitem__(self, position): # <2>
23+
del self._cards[position]
24+
25+
def insert(self, position, value): # <3>
26+
self._cards.insert(position, value)

13-protocol-abc/lotto.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# tag::LOTTERY_BLOWER[]
2+
3+
import random
4+
5+
from tombola import Tombola
6+
7+
8+
class LotteryBlower(Tombola):
9+
10+
def __init__(self, iterable):
11+
self._balls = list(iterable) # <1>
12+
13+
def load(self, iterable):
14+
self._balls.extend(iterable)
15+
16+
def pick(self):
17+
try:
18+
position = random.randrange(len(self._balls)) # <2>
19+
except ValueError:
20+
raise LookupError('pick from empty BingoCage')
21+
return self._balls.pop(position) # <3>
22+
23+
def loaded(self): # <4>
24+
return bool(self._balls)
25+
26+
def inspect(self): # <5>
27+
return tuple(sorted(self._balls))
28+
29+
30+
# end::LOTTERY_BLOWER[]

13-protocol-abc/tombola.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# tag::TOMBOLA_ABC[]
2+
3+
import abc
4+
5+
class Tombola(abc.ABC): # <1>
6+
7+
@abc.abstractmethod
8+
def load(self, iterable): # <2>
9+
"""Add items from an iterable."""
10+
11+
@abc.abstractmethod
12+
def pick(self): # <3>
13+
"""Remove item at random, returning it.
14+
15+
This method should raise `LookupError` when the instance is empty.
16+
"""
17+
18+
def loaded(self): # <4>
19+
"""Return `True` if there's at least 1 item, `False` otherwise."""
20+
return bool(self.inspect()) # <5>
21+
22+
23+
def inspect(self):
24+
"""Return a sorted tuple with the items currently inside."""
25+
items = []
26+
while True: # <6>
27+
try:
28+
items.append(self.pick())
29+
except LookupError:
30+
break
31+
self.load(items) # <7>
32+
return tuple(sorted(items))
33+
34+
35+
# end::TOMBOLA_ABC[]

13-protocol-abc/tombola_runner.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# tag::TOMBOLA_RUNNER[]
2+
import doctest
3+
4+
from tombola import Tombola
5+
6+
# modules to test
7+
import bingo, lotto, tombolist, drum # <1>
8+
9+
TEST_FILE = 'tombola_tests.rst'
10+
TEST_MSG = '{0:16} {1.attempted:2} tests, {1.failed:2} failed - {2}'
11+
12+
13+
def main(argv):
14+
verbose = '-v' in argv
15+
real_subclasses = Tombola.__subclasses__() # <2>
16+
virtual_subclasses = list(Tombola._abc_registry) # <3>
17+
18+
for cls in real_subclasses + virtual_subclasses: # <4>
19+
test(cls, verbose)
20+
21+
22+
def test(cls, verbose=False):
23+
24+
res = doctest.testfile(
25+
TEST_FILE,
26+
globs={'ConcreteTombola': cls}, # <5>
27+
verbose=verbose,
28+
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)
29+
tag = 'FAIL' if res.failed else 'OK'
30+
print(TEST_MSG.format(cls.__name__, res, tag)) # <6>
31+
32+
33+
if __name__ == '__main__':
34+
import sys
35+
main(sys.argv)
36+
# end::TOMBOLA_RUNNER[]

13-protocol-abc/tombola_subhook.py

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""
2+
Variation of ``tombola.Tombola`` implementing ``__subclasshook__``.
3+
4+
Tests with simple classes::
5+
6+
>>> Tombola.__subclasshook__(object)
7+
NotImplemented
8+
>>> class Complete:
9+
... def __init__(): pass
10+
... def load(): pass
11+
... def pick(): pass
12+
... def loaded(): pass
13+
...
14+
>>> Tombola.__subclasshook__(Complete)
15+
True
16+
>>> issubclass(Complete, Tombola)
17+
18+
"""
19+
20+
21+
from abc import ABC, abstractmethod
22+
from inspect import getmembers, isfunction
23+
24+
25+
class Tombola(ABC): # <1>
26+
27+
@abstractmethod
28+
def __init__(self, iterable): # <2>
29+
"""New instance is loaded from an iterable."""
30+
31+
@abstractmethod
32+
def load(self, iterable):
33+
"""Add items from an iterable."""
34+
35+
@abstractmethod
36+
def pick(self): # <3>
37+
"""Remove item at random, returning it.
38+
39+
This method should raise `LookupError` when the instance is empty.
40+
"""
41+
42+
def loaded(self): # <4>
43+
try:
44+
item = self.pick()
45+
except LookupError:
46+
return False
47+
else:
48+
self.load([item]) # put it back
49+
return True
50+
51+
@classmethod
52+
def __subclasshook__(cls, other_cls):
53+
if cls is Tombola:
54+
interface_names = function_names(cls)
55+
found_names = set()
56+
for a_cls in other_cls.__mro__:
57+
found_names |= function_names(a_cls)
58+
if found_names >= interface_names:
59+
return True
60+
return NotImplemented
61+
62+
63+
def function_names(obj):
64+
return {name for name, _ in getmembers(obj, isfunction)}

0 commit comments

Comments
 (0)