sync with Atlas repo
This commit is contained in:
@@ -42,7 +42,6 @@ Tests for update using a `dict` or a sequence of pairs::
|
|||||||
# BEGIN STRKEYDICT
|
# BEGIN STRKEYDICT
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import collections.abc
|
|
||||||
|
|
||||||
|
|
||||||
class StrKeyDict(collections.UserDict): # <1>
|
class StrKeyDict(collections.UserDict): # <1>
|
||||||
@@ -58,15 +57,4 @@ class StrKeyDict(collections.UserDict): # <1>
|
|||||||
def __setitem__(self, key, item):
|
def __setitem__(self, key, item):
|
||||||
self.data[str(key)] = item # <4>
|
self.data[str(key)] = item # <4>
|
||||||
|
|
||||||
def update(self, iterable=None, **kwds):
|
|
||||||
if iterable is not None:
|
|
||||||
if isinstance(iterable, collections.abc.Mapping): # <5>
|
|
||||||
pairs = iterable.items()
|
|
||||||
else:
|
|
||||||
pairs = ((k, v) for k, v in iterable) # <6>
|
|
||||||
for key, value in pairs:
|
|
||||||
self[key] = value # <7>
|
|
||||||
if kwds:
|
|
||||||
self.update(kwds) # <8>
|
|
||||||
|
|
||||||
# END STRKEYDICT
|
# END STRKEYDICT
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ class BingoCage(Tombola): # <1>
|
|||||||
def load(self, items):
|
def load(self, items):
|
||||||
self._balls.extend(items)
|
self._balls.extend(items)
|
||||||
|
|
||||||
def pop(self):
|
def pick(self):
|
||||||
try:
|
try:
|
||||||
position = random.randrange(len(self._balls)) # <4>
|
position = random.randrange(len(self._balls)) # <3>
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise LookupError('pop from empty BingoCage')
|
raise LookupError('pop from empty BingoCage')
|
||||||
return self._balls.pop(position)
|
return self._balls.pop(position) # <4>
|
||||||
|
|||||||
19
interfaces/diamond.py
Normal file
19
interfaces/diamond.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
class A:
|
||||||
|
def ping(self):
|
||||||
|
print('ping:', self)
|
||||||
|
|
||||||
|
|
||||||
|
class B(A):
|
||||||
|
def pong(self):
|
||||||
|
print('pong:', self)
|
||||||
|
|
||||||
|
|
||||||
|
class C(A):
|
||||||
|
def pong(self):
|
||||||
|
print('PONG:', self)
|
||||||
|
|
||||||
|
|
||||||
|
class D(B, C):
|
||||||
|
def pingpong(self):
|
||||||
|
super().ping()
|
||||||
|
super().pong()
|
||||||
@@ -13,5 +13,5 @@ class TumblingDrum(Tombola):
|
|||||||
self._balls.extend(iterable)
|
self._balls.extend(iterable)
|
||||||
shuffle(self._balls)
|
shuffle(self._balls)
|
||||||
|
|
||||||
def pop(self):
|
def pick(self):
|
||||||
return self._balls.pop()
|
return self._balls.pop()
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class LotteryBlower(Tombola):
|
|||||||
self._balls.extend(iterable)
|
self._balls.extend(iterable)
|
||||||
self.randomizer.shuffle(self._balls) # <2>
|
self.randomizer.shuffle(self._balls) # <2>
|
||||||
|
|
||||||
def pop(self):
|
def pick(self):
|
||||||
return self._balls.pop() # <3>
|
return self._balls.pop() # <3>
|
||||||
|
|
||||||
def loaded(self): # <4>
|
def loaded(self): # <4>
|
||||||
|
|||||||
40
interfaces/pypy_difference.rst
Normal file
40
interfaces/pypy_difference.rst
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
====================================
|
||||||
|
Differences between PyPy and CPython
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Note: this is an excerpt from the `PyPy`_ documentation. On Nov. 19, 2014 I ran this test on PyPy 2.4.0 and PyPy3 2.4.0 and the result was not as described, but was the same as with CPython: 'foo'.
|
||||||
|
|
||||||
|
.. _PyPy: http://pypy.readthedocs.org/en/latest/cpython_differences.html#subclasses-of-built-in-types
|
||||||
|
|
||||||
|
Subclasses of built-in types
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Officially, CPython has no rule at all for when exactly
|
||||||
|
overridden method of subclasses of built-in types get
|
||||||
|
implicitly called or not. As an approximation, these methods
|
||||||
|
are never called by other built-in methods of the same object.
|
||||||
|
For example, an overridden ``__getitem__()`` in a subclass of
|
||||||
|
``dict`` will not be called by e.g. the built-in ``get()``
|
||||||
|
method.
|
||||||
|
|
||||||
|
The above is true both in CPython and in PyPy. Differences
|
||||||
|
can occur about whether a built-in function or method will
|
||||||
|
call an overridden method of *another* object than ``self``.
|
||||||
|
In PyPy, they are generally always called, whereas not in
|
||||||
|
CPython. For example, in PyPy, ``dict1.update(dict2)``
|
||||||
|
considers that ``dict2`` is just a general mapping object, and
|
||||||
|
will thus call overridden ``keys()`` and ``__getitem__()``
|
||||||
|
methods on it. So the following code prints ``42`` on PyPy
|
||||||
|
but ``foo`` on CPython::
|
||||||
|
|
||||||
|
>>> class D(dict):
|
||||||
|
... def __getitem__(self, key):
|
||||||
|
... return 42
|
||||||
|
...
|
||||||
|
>>>
|
||||||
|
>>> d1 = {}
|
||||||
|
>>> d2 = D(a='foo')
|
||||||
|
>>> d1.update(d2)
|
||||||
|
>>> print(d1['a'])
|
||||||
|
42
|
||||||
31
interfaces/subclassing_builtins.rst
Normal file
31
interfaces/subclassing_builtins.rst
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
====================================
|
||||||
|
Subclassing built-in caveats
|
||||||
|
====================================
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
>>> class D1(dict):
|
||||||
|
... def __getitem__(self, key):
|
||||||
|
... return 42
|
||||||
|
...
|
||||||
|
>>> d1 = D1(a='foo')
|
||||||
|
>>> d1
|
||||||
|
{'a': 'foo'}
|
||||||
|
>>> d1['a']
|
||||||
|
42
|
||||||
|
>>> d1.get('a')
|
||||||
|
'foo'
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
>>> class D2(dict):
|
||||||
|
... def get(self, key):
|
||||||
|
... return 42
|
||||||
|
...
|
||||||
|
>>> d2 = D2(a='foo')
|
||||||
|
>>> d2
|
||||||
|
{'a': 'foo'}
|
||||||
|
>>> d2['a']
|
||||||
|
'foo'
|
||||||
|
>>> d2.get('a')
|
||||||
|
42
|
||||||
@@ -1,23 +1,26 @@
|
|||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
class Tombola(metaclass=ABCMeta): # <1>
|
class Tombola(ABC): # <1>
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def __init__(self, iterable): # <2>
|
def __init__(self, iterable): # <2>
|
||||||
raise NotImplementedError
|
"""New instance is loaded from an iterable."""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def load(self):
|
def load(self, iterable):
|
||||||
raise NotImplementedError
|
"""Add items from an iterable."""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def pop(self):
|
def pick(self): # <3>
|
||||||
raise NotImplementedError
|
"""Remove item at random, returning it.
|
||||||
|
|
||||||
def loaded(self): # <3>
|
This method should raise `LookupError` when the instance is empty.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def loaded(self): # <4>
|
||||||
try:
|
try:
|
||||||
item = self.pop()
|
item = self.pick()
|
||||||
except LookupError:
|
except LookupError:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -4,49 +4,30 @@ import doctest
|
|||||||
|
|
||||||
from tombola import Tombola
|
from tombola import Tombola
|
||||||
|
|
||||||
TESTS = 'tombola_tests.rst'
|
TEST_FILE = 'tombola_tests.rst'
|
||||||
|
MODULE_NAMES = 'bingo lotto tombolist drum'.split()
|
||||||
MODULES = 'bingo lotto tombolist drum'.split()
|
TEST_MSG = '{0:16} {1.attempted:2} tests, {1.failed:2} failed - {2}'
|
||||||
|
|
||||||
|
|
||||||
def test_module(module_name, verbose=False):
|
def test(cls, verbose=False):
|
||||||
|
|
||||||
module = importlib.import_module(module_name)
|
res = doctest.testfile(TEST_FILE,
|
||||||
|
globs={'TombolaUnderTest': cls},
|
||||||
tombola_class = None
|
|
||||||
for name in dir(module):
|
|
||||||
obj = getattr(module, name)
|
|
||||||
if (isinstance(obj, type) and
|
|
||||||
issubclass(obj, Tombola) and
|
|
||||||
obj.__module__ == module_name):
|
|
||||||
tombola_class = obj
|
|
||||||
break # stop at first matching class
|
|
||||||
|
|
||||||
if tombola_class is None:
|
|
||||||
print('ERROR: no Tombola subclass found in', module_name)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
res = doctest.testfile(TESTS,
|
|
||||||
globs={'TombolaUnderTest': tombola_class},
|
|
||||||
verbose=verbose,
|
verbose=verbose,
|
||||||
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)
|
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)
|
||||||
print('{:10} {}'.format(module_name, res))
|
tag = 'FAIL' if res.failed else 'OK'
|
||||||
|
print(TEST_MSG.format(cls.__name__, res, tag))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
args = sys.argv[:]
|
for name in MODULE_NAMES: # import modules to test, by name
|
||||||
if '-v' in args:
|
importlib.import_module(name)
|
||||||
args.remove('-v')
|
|
||||||
verbose = True
|
|
||||||
else:
|
|
||||||
verbose = False
|
|
||||||
|
|
||||||
if len(args) == 2:
|
verbose = '-v' in sys.argv
|
||||||
module_names = [args[1]]
|
|
||||||
else:
|
|
||||||
module_names = MODULES
|
|
||||||
|
|
||||||
for name in module_names:
|
real_subclasses = Tombola.__subclasses__()
|
||||||
print('*' * 60, name)
|
virtual_subclasses = list(Tombola._abc_registry)
|
||||||
test_module(name, verbose)
|
|
||||||
|
for cls in real_subclasses + virtual_subclasses:
|
||||||
|
test(cls, verbose)
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ Create and load instance from iterable::
|
|||||||
True
|
True
|
||||||
|
|
||||||
|
|
||||||
Pop and collect balls::
|
Pick and collect balls::
|
||||||
|
|
||||||
>>> picks = []
|
>>> picks = []
|
||||||
>>> picks.append(globe.pop())
|
>>> picks.append(globe.pick())
|
||||||
>>> picks.append(globe.pop())
|
>>> picks.append(globe.pick())
|
||||||
>>> picks.append(globe.pop())
|
>>> picks.append(globe.pick())
|
||||||
|
|
||||||
|
|
||||||
Check state and results::
|
Check state and results::
|
||||||
@@ -34,37 +34,44 @@ Reload::
|
|||||||
>>> globe.load(balls)
|
>>> globe.load(balls)
|
||||||
>>> globe.loaded()
|
>>> globe.loaded()
|
||||||
True
|
True
|
||||||
>>> picks = [globe.pop() for i in balls]
|
>>> picks = [globe.pick() for i in balls]
|
||||||
>>> globe.loaded()
|
>>> globe.loaded()
|
||||||
False
|
False
|
||||||
|
|
||||||
|
|
||||||
Load and pop 20 balls to verify that the order has changed::
|
Check that `LookupError` (or a subclass) is the exception
|
||||||
|
thrown when the device is empty::
|
||||||
>>> balls = list(range(20))
|
|
||||||
>>> globe = TombolaUnderTest(balls)
|
|
||||||
>>> picks = []
|
|
||||||
>>> while globe.loaded():
|
|
||||||
... picks.append(globe.pop())
|
|
||||||
>>> len(picks) == len(balls)
|
|
||||||
True
|
|
||||||
>>> picks != balls
|
|
||||||
True
|
|
||||||
|
|
||||||
|
|
||||||
Also check that the order is not simply reversed either::
|
|
||||||
|
|
||||||
>>> picks[::-1] != balls
|
|
||||||
True
|
|
||||||
|
|
||||||
Note: last 2 tests each have 1 chance in 20! (factorial) of failing even if the implementation is OK. 1/20!, or approximately 4.11e-19, is the probability of the 20 balls coming out, by chance, in the exact order the were loaded.
|
|
||||||
|
|
||||||
Check that `LookupError` (or a subclass) is the exception thrown when the device is empty::
|
|
||||||
|
|
||||||
>>> globe = TombolaUnderTest([])
|
>>> globe = TombolaUnderTest([])
|
||||||
>>> try:
|
>>> try:
|
||||||
... globe.pop()
|
... globe.pick()
|
||||||
... except LookupError as exc:
|
... except LookupError as exc:
|
||||||
... print('OK')
|
... print('OK')
|
||||||
OK
|
OK
|
||||||
|
|
||||||
|
|
||||||
|
Load and pick 100 balls to verify that they are all come out::
|
||||||
|
|
||||||
|
>>> balls = list(range(100))
|
||||||
|
>>> globe = TombolaUnderTest(balls)
|
||||||
|
>>> picks = []
|
||||||
|
>>> while globe.loaded():
|
||||||
|
... picks.append(globe.pick())
|
||||||
|
>>> len(picks) == len(balls)
|
||||||
|
True
|
||||||
|
>>> set(picks) == set(balls)
|
||||||
|
True
|
||||||
|
|
||||||
|
|
||||||
|
Check that the order has changed is not simply reversed either::
|
||||||
|
|
||||||
|
>>> picks != balls
|
||||||
|
True
|
||||||
|
>>> picks[::-1] != balls
|
||||||
|
True
|
||||||
|
|
||||||
|
Note: the previous 2 tests have a *very* small chance of failing
|
||||||
|
even if the implementation is OK. The probability of the 100
|
||||||
|
balls coming out, by chance, in the order they were loaded is
|
||||||
|
1/100!, or approximately 1.07e-158. It's much easier to win the
|
||||||
|
Lotto or to become a billionaire working as a programmer.
|
||||||
|
|||||||
@@ -2,18 +2,20 @@ from random import randrange
|
|||||||
|
|
||||||
from tombola import Tombola
|
from tombola import Tombola
|
||||||
|
|
||||||
|
@Tombola.register # <1>
|
||||||
|
class TomboList(list): # <2>
|
||||||
|
|
||||||
class TomboList(list): # <1>
|
def pick(self):
|
||||||
|
if self: # <3>
|
||||||
def pop(self):
|
position = randrange(len(self))
|
||||||
if self: # <2>
|
return super().pop(position) # <4>
|
||||||
return super().pop(randrange(len(self))) # <3>
|
|
||||||
else:
|
else:
|
||||||
raise LookupError('pop from empty TomboList')
|
raise LookupError('pop from empty TomboList')
|
||||||
|
|
||||||
def load(self, iterable): self.extend(iterable) # <4>
|
def load(self, iterable): self.extend(iterable) # <5>
|
||||||
|
|
||||||
def loaded(self): return bool(self) # <5>
|
def loaded(self): return bool(self) # <6>
|
||||||
|
|
||||||
|
"""
|
||||||
Tombola.register(TomboList) # <6>
|
Tombola.register(TomboList) # <- Python 3.2 or earlier
|
||||||
|
"""
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
"""
|
"""
|
||||||
# BEGIN CLASSIC_STRATEGY
|
# BEGIN CLASSIC_STRATEGY
|
||||||
|
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
Customer = namedtuple('Customer', 'name fidelity')
|
Customer = namedtuple('Customer', 'name fidelity')
|
||||||
@@ -69,7 +69,7 @@ class Order: # the Context
|
|||||||
return fmt.format(self.total(), self.due())
|
return fmt.format(self.total(), self.due())
|
||||||
|
|
||||||
|
|
||||||
class Promotion(metaclass=ABCMeta): # the Strategy
|
class Promotion(ABC): # the Strategy: an Abstract Base Class
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def discount(self, order):
|
def discount(self, order):
|
||||||
|
|||||||
Reference in New Issue
Block a user