updated contents from Atlas repo
This commit is contained in:
24
classes/mem_test.py
Normal file
24
classes/mem_test.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import importlib
|
||||
import sys
|
||||
import resource
|
||||
|
||||
NUM_VECTORS = 10**7
|
||||
|
||||
if len(sys.argv) == 2:
|
||||
module_name = sys.argv[1].replace('.py', '')
|
||||
module = importlib.import_module(module_name)
|
||||
else:
|
||||
print('Usage: {} <vector-module-to-test>'.format())
|
||||
sys.exit(1)
|
||||
|
||||
fmt = 'Selected Vector type: {.__name__}.{.__name__}'
|
||||
print(fmt.format(module, module.Vector))
|
||||
|
||||
mem_init = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
||||
print('Creating {:,} Vector instances'.format(NUM_VECTORS))
|
||||
|
||||
vectors = [module.Vector(3.0, 4.0) for i in range(NUM_VECTORS)]
|
||||
|
||||
mem_final = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
||||
print('Initial RAM usage: {:14,}'.format(mem_init))
|
||||
print(' Final RAM usage: {:14,}'.format(mem_final))
|
||||
60
classes/vector_v0.py
Normal file
60
classes/vector_v0.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
A 2-dimensional vector class
|
||||
|
||||
# BEGIN VECTOR_V0_DEMO
|
||||
|
||||
>>> v1 = Vector(3, 4)
|
||||
>>> x, y = v1 #<1>
|
||||
>>> x, y
|
||||
(3.0, 4.0)
|
||||
>>> v1 #<2>
|
||||
Vector(3.0, 4.0)
|
||||
>>> v1_clone = eval(repr(v1)) #<3>
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1) #<4>
|
||||
(3.0, 4.0)
|
||||
>>> octets = bytes(v1) #<5>
|
||||
>>> octets
|
||||
b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
|
||||
>>> abs(v1) #<6>
|
||||
5.0
|
||||
>>> bool(v1), bool(Vector(0, 0)) #<7>
|
||||
(True, False)
|
||||
|
||||
# END VECTOR_V0_DEMO
|
||||
"""
|
||||
|
||||
# BEGIN VECTOR_V0
|
||||
from array import array
|
||||
import math
|
||||
|
||||
|
||||
class Vector:
|
||||
typecode = 'd' # <1>
|
||||
|
||||
def __init__(self, x, y):
|
||||
self.x = float(x) # <2>
|
||||
self.y = float(y)
|
||||
|
||||
def __iter__(self):
|
||||
return (i for i in (self.x, self.y)) # <3>
|
||||
|
||||
def __repr__(self):
|
||||
return 'Vector({!r}, {!r})'.format(*self) # <4>
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self)) # <5>
|
||||
|
||||
def __bytes__(self):
|
||||
return bytes(array(Vector.typecode, self)) # <6>
|
||||
|
||||
def __eq__(self, other):
|
||||
return tuple(self) == tuple(other) # <7>
|
||||
|
||||
def __abs__(self):
|
||||
return math.hypot(self.x, self.y)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self)) # <8>
|
||||
# END VECTOR_V0
|
||||
86
classes/vector_v1.py
Normal file
86
classes/vector_v1.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
A 2-dimensional vector class
|
||||
|
||||
>>> v1 = Vector(3, 4)
|
||||
>>> x, y = v1 #<1>
|
||||
>>> x, y
|
||||
(3.0, 4.0)
|
||||
>>> v1 #<2>
|
||||
Vector(3.0, 4.0)
|
||||
>>> v1_clone = eval(repr(v1)) #<3>
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1) #<4>
|
||||
(3.0, 4.0)
|
||||
>>> octets = bytes(v1) #<5>
|
||||
>>> octets
|
||||
b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
|
||||
>>> abs(v1) #<6>
|
||||
5.0
|
||||
>>> bool(v1), bool(Vector(0, 0)) #<7>
|
||||
(True, False)
|
||||
|
||||
Test of .frombytes() class method:
|
||||
|
||||
>>> v1_clone = Vector.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector(3.0, 4.0)
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
So far, Vector instances are unhashable:
|
||||
|
||||
# BEGIN VECTOR_V1_UNHASHABLE_DEMO
|
||||
>>> v1 = Vector(3, 4)
|
||||
>>> hash(v1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: unhashable type: 'Vector'
|
||||
>>> set([v1])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: unhashable type: 'Vector'
|
||||
|
||||
# END VECTOR_V1_UNHASHABLE_DEMO
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import math
|
||||
|
||||
|
||||
class Vector:
|
||||
typecode = 'd'
|
||||
|
||||
def __init__(self, x, y):
|
||||
self.x = float(x)
|
||||
self.y = float(y)
|
||||
|
||||
def __iter__(self):
|
||||
return (i for i in (self.x, self.y))
|
||||
|
||||
def __repr__(self):
|
||||
return 'Vector({!r}, {!r})'.format(*self)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return bytes(array(Vector.typecode, self))
|
||||
|
||||
def __eq__(self, other):
|
||||
return tuple(self) == tuple(other)
|
||||
|
||||
def __abs__(self):
|
||||
return math.hypot(self.x, self.y)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
||||
# BEGIN VECTOR_V1
|
||||
@classmethod # <1>
|
||||
def frombytes(cls, octets): # <2>
|
||||
arr = array(Vector.typecode) # <3>
|
||||
arr.frombytes(octets) # <4>
|
||||
return cls(*arr) # <5>
|
||||
# END VECTOR_V1
|
||||
113
classes/vector_v2.py
Normal file
113
classes/vector_v2.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""
|
||||
A 2-dimensional vector class
|
||||
|
||||
>>> v1 = Vector(3, 4)
|
||||
>>> x, y = v1
|
||||
>>> x, y
|
||||
(3.0, 4.0)
|
||||
>>> v1
|
||||
Vector(3.0, 4.0)
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0)
|
||||
>>> octets = bytes(v1)
|
||||
>>> octets
|
||||
b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
|
||||
>>> abs(v1)
|
||||
5.0
|
||||
>>> bool(v1), bool(Vector(0, 0))
|
||||
(True, False)
|
||||
|
||||
Test of ``.frombytes()`` class method:
|
||||
|
||||
>>> v1_clone = Vector.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector(3.0, 4.0)
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
Tests of ``format()`` with rectangular coordinates:
|
||||
|
||||
>>> format(v1)
|
||||
'(3.0, 4.0)'
|
||||
>>> format(v1, '.2f')
|
||||
'(3.00, 4.00)'
|
||||
>>> format(v1, '.3e')
|
||||
'(3.000e+00, 4.000e+00)'
|
||||
|
||||
Tests of the ``angle`` method::
|
||||
|
||||
>>> Vector(0, 0).angle()
|
||||
0.0
|
||||
>>> Vector(1, 0).angle()
|
||||
0.0
|
||||
>>> epsilon = 10**-8
|
||||
>>> abs(Vector(0, 1).angle() - math.pi/2) < epsilon
|
||||
True
|
||||
>>> abs(Vector(1, 1).angle() - math.pi/4) < epsilon
|
||||
True
|
||||
|
||||
Tests of ``format()`` with polar coordinates:
|
||||
|
||||
>>> format(Vector(1, 1), 'p') # doctest:+ELLIPSIS
|
||||
'<1.414213..., 0.785398...>'
|
||||
>>> format(Vector(1, 1), '.3ep')
|
||||
'<1.414e+00, 7.854e-01>'
|
||||
>>> format(Vector(1, 1), '0.5fp')
|
||||
'<1.41421, 0.78540>'
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import math
|
||||
|
||||
|
||||
class Vector:
|
||||
typecode = 'd'
|
||||
|
||||
def __init__(self, x, y):
|
||||
self.x = float(x)
|
||||
self.y = float(y)
|
||||
|
||||
def __iter__(self):
|
||||
return (i for i in (self.x, self.y))
|
||||
|
||||
def __repr__(self):
|
||||
return 'Vector({!r}, {!r})'.format(*self)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return bytes(array(Vector.typecode, self))
|
||||
|
||||
def __eq__(self, other):
|
||||
return tuple(self) == tuple(other)
|
||||
|
||||
def __abs__(self):
|
||||
return math.hypot(self.x, self.y)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
||||
def angle(self):
|
||||
return math.atan2(self.y, self.x)
|
||||
|
||||
def __format__(self, fmt_spec=''):
|
||||
if fmt_spec.endswith('p'):
|
||||
fmt_spec = fmt_spec[:-1]
|
||||
coords = (abs(self), self.angle())
|
||||
outer_fmt = '<{}, {}>'
|
||||
else:
|
||||
coords = self
|
||||
outer_fmt = '({}, {})'
|
||||
components = (format(c, fmt_spec) for c in coords)
|
||||
return outer_fmt.format(*components)
|
||||
|
||||
@classmethod
|
||||
def frombytes(cls, octets):
|
||||
arr = array(Vector.typecode)
|
||||
arr.frombytes(octets)
|
||||
return cls(*arr)
|
||||
115
classes/vector_v2_fmt_snippet.py
Normal file
115
classes/vector_v2_fmt_snippet.py
Normal file
@@ -0,0 +1,115 @@
|
||||
"""
|
||||
A 2-dimensional vector class
|
||||
|
||||
>>> v1 = Vector(3, 4)
|
||||
>>> x, y = v1
|
||||
>>> x, y
|
||||
(3.0, 4.0)
|
||||
>>> v1
|
||||
Vector(3.0, 4.0)
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0)
|
||||
>>> octets = bytes(v1)
|
||||
>>> octets
|
||||
b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
|
||||
>>> abs(v1)
|
||||
5.0
|
||||
>>> bool(v1), bool(Vector(0, 0))
|
||||
(True, False)
|
||||
|
||||
Test of ``.frombytes()`` class method:
|
||||
|
||||
>>> v1_clone = Vector.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector(3.0, 4.0)
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
Tests of ``format()`` with rectangular coordinates:
|
||||
|
||||
>>> format(v1)
|
||||
'(3.0, 4.0)'
|
||||
>>> format(v1, '.2f')
|
||||
'(3.00, 4.00)'
|
||||
>>> format(v1, '.3e')
|
||||
'(3.000e+00, 4.000e+00)'
|
||||
|
||||
Tests of the ``angle`` method::
|
||||
|
||||
>>> Vector(0, 0).angle()
|
||||
0.0
|
||||
>>> Vector(1, 0).angle()
|
||||
0.0
|
||||
>>> epsilon = 10**-8
|
||||
>>> abs(Vector(0, 1).angle() - math.pi/2) < epsilon
|
||||
True
|
||||
>>> abs(Vector(1, 1).angle() - math.pi/4) < epsilon
|
||||
True
|
||||
|
||||
Tests of ``format()`` with polar coordinates:
|
||||
|
||||
>>> format(Vector(1, 1), 'p') # doctest:+ELLIPSIS
|
||||
'<1.414213..., 0.785398...>'
|
||||
>>> format(Vector(1, 1), '.3ep')
|
||||
'<1.414e+00, 7.854e-01>'
|
||||
>>> format(Vector(1, 1), '0.5fp')
|
||||
'<1.41421, 0.78540>'
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import math
|
||||
|
||||
|
||||
class Vector:
|
||||
typecode = 'd'
|
||||
|
||||
def __init__(self, x, y):
|
||||
self.x = float(x)
|
||||
self.y = float(y)
|
||||
|
||||
def __iter__(self):
|
||||
return (i for i in (self.x, self.y))
|
||||
|
||||
def __repr__(self):
|
||||
return 'Vector({!r}, {!r})'.format(*self)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return bytes(array(Vector.typecode, self))
|
||||
|
||||
def __eq__(self, other):
|
||||
return tuple(self) == tuple(other)
|
||||
|
||||
def __abs__(self):
|
||||
return math.hypot(self.x, self.y)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
||||
def angle(self):
|
||||
return math.atan2(self.y, self.x)
|
||||
|
||||
# BEGIN VECTOR_V2_FORMAT
|
||||
def __format__(self, fmt_spec=''):
|
||||
if fmt_spec.endswith('p'): # <1>
|
||||
fmt_spec = fmt_spec[:-1] # <2>
|
||||
coords = (abs(self), self.angle()) # <3>
|
||||
outer_fmt = '<{}, {}>' # <4>
|
||||
else:
|
||||
coords = self # <5>
|
||||
outer_fmt = '({}, {})' # <6>
|
||||
components = (format(c, fmt_spec) for c in coords) # <7>
|
||||
return outer_fmt.format(*components) # <8>
|
||||
# END VECTOR_V2_FORMAT
|
||||
|
||||
@classmethod
|
||||
def frombytes(cls, octets):
|
||||
arr = array(Vector.typecode)
|
||||
arr.frombytes(octets)
|
||||
return cls(*arr)
|
||||
123
classes/vector_v3.py
Normal file
123
classes/vector_v3.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
A 2-dimensional vector class
|
||||
|
||||
>>> v1 = Vector(3, 4)
|
||||
>>> x, y = v1 #<1>
|
||||
>>> x, y
|
||||
(3.0, 4.0)
|
||||
>>> v1 #<2>
|
||||
Vector(3.0, 4.0)
|
||||
>>> v1_clone = eval(repr(v1)) #<3>
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1) #<4>
|
||||
(3.0, 4.0)
|
||||
>>> octets = bytes(v1) #<5>
|
||||
>>> octets
|
||||
b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
|
||||
>>> abs(v1) #<6>
|
||||
5.0
|
||||
>>> bool(v1), bool(Vector(0, 0)) #<7>
|
||||
(True, False)
|
||||
|
||||
Test of .frombytes() class method:
|
||||
|
||||
>>> v1_clone = Vector.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector(3.0, 4.0)
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
# BEGIN VECTOR_V3_DEMO
|
||||
Test of `x` and `y` read-only properties:
|
||||
|
||||
>>> v1.x, v1.y
|
||||
(3.0, 4.0)
|
||||
>>> v1.x = 123
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: can't set attribute
|
||||
|
||||
# END VECTOR_V3_HASH_DEMO
|
||||
|
||||
# BEGIN VECTOR_V3_HASH_DEMO
|
||||
|
||||
>>> v1 = Vector(3, 4)
|
||||
>>> v2 = Vector(3.1, 4.2)
|
||||
>>> hash(v1), hash(v2)
|
||||
(7, 384307168202284039)
|
||||
>>> set([v1, v2])
|
||||
{Vector(3.1, 4.2), Vector(3.0, 4.0)}
|
||||
|
||||
# END VECTOR_V3_DEMO
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import math
|
||||
|
||||
# BEGIN VECTOR_V3
|
||||
class Vector:
|
||||
typecode = 'd'
|
||||
|
||||
def __init__(self, x, y):
|
||||
self.__x = float(x) # <1>
|
||||
self.__y = float(y)
|
||||
|
||||
@property # <2>
|
||||
def x(self): # <3>
|
||||
return self.__x # <4>
|
||||
|
||||
@property # <5>
|
||||
def y(self):
|
||||
return self.__y
|
||||
|
||||
def __iter__(self):
|
||||
return (i for i in (self.x, self.y)) # <6>
|
||||
|
||||
# remaining methods follow (omitted in book listing)
|
||||
# END VECTOR_V3
|
||||
|
||||
def __repr__(self):
|
||||
return 'Vector({!r}, {!r})'.format(*self)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return bytes(array(Vector.typecode, self))
|
||||
|
||||
def __eq__(self, other):
|
||||
return tuple(self) == tuple(other)
|
||||
|
||||
# BEGIN VECTOR_V3_HASH
|
||||
def __hash__(self):
|
||||
return hash(self.x) ^ hash(self.y)
|
||||
# END VECTOR_V3_HASH
|
||||
|
||||
def __abs__(self):
|
||||
return math.hypot(self.x, self.y)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
||||
def angle(self):
|
||||
return math.atan2(self.y, self.x)
|
||||
|
||||
def __format__(self, fmt_spec=''):
|
||||
if fmt_spec.endswith('p'):
|
||||
fmt_spec = fmt_spec[:-1]
|
||||
coords = (abs(self), self.angle())
|
||||
outer_fmt = '<{}, {}>'
|
||||
else:
|
||||
coords = self
|
||||
outer_fmt = '({}, {})'
|
||||
components = (format(c, fmt_spec) for c in coords)
|
||||
return outer_fmt.format(*components)
|
||||
|
||||
@classmethod
|
||||
def frombytes(cls, octets):
|
||||
arr = array(Vector.typecode)
|
||||
arr.frombytes(octets)
|
||||
return cls(*arr)
|
||||
125
classes/vector_v3_slots.py
Normal file
125
classes/vector_v3_slots.py
Normal file
@@ -0,0 +1,125 @@
|
||||
"""
|
||||
A 2-dimensional vector class
|
||||
|
||||
>>> v1 = Vector(3, 4)
|
||||
>>> x, y = v1 #<1>
|
||||
>>> x, y
|
||||
(3.0, 4.0)
|
||||
>>> v1 #<2>
|
||||
Vector(3.0, 4.0)
|
||||
>>> v1_clone = eval(repr(v1)) #<3>
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1) #<4>
|
||||
(3.0, 4.0)
|
||||
>>> octets = bytes(v1) #<5>
|
||||
>>> octets
|
||||
b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
|
||||
>>> abs(v1) #<6>
|
||||
5.0
|
||||
>>> bool(v1), bool(Vector(0, 0)) #<7>
|
||||
(True, False)
|
||||
|
||||
Test of .frombytes() class method:
|
||||
|
||||
>>> v1_clone = Vector.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector(3.0, 4.0)
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
# BEGIN VECTOR_V3_DEMO
|
||||
Test of `x` and `y` read-only properties:
|
||||
|
||||
>>> v1.x, v1.y
|
||||
(3.0, 4.0)
|
||||
>>> v1.x = 123
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: can't set attribute
|
||||
|
||||
# END VECTOR_V3_HASH_DEMO
|
||||
|
||||
# BEGIN VECTOR_V3_HASH_DEMO
|
||||
|
||||
>>> v1 = Vector(3, 4)
|
||||
>>> v2 = Vector(3.1, 4.2)
|
||||
>>> hash(v1), hash(v2)
|
||||
(7, 384307168202284039)
|
||||
>>> set([v1, v2])
|
||||
{Vector(3.1, 4.2), Vector(3.0, 4.0)}
|
||||
|
||||
# END VECTOR_V3_DEMO
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import math
|
||||
|
||||
# BEGIN VECTOR_V3_SLOTS
|
||||
class Vector:
|
||||
__slots__ = ('__x', '__y')
|
||||
|
||||
typecode = 'd'
|
||||
|
||||
# methods follow (omitted in book listing)
|
||||
# END VECTOR_V3_SLOTS
|
||||
|
||||
def __init__(self, x, y):
|
||||
self.__x = float(x)
|
||||
self.__y = float(y)
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
return self.__x
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
return self.__y
|
||||
|
||||
def __iter__(self):
|
||||
return (i for i in (self.x, self.y)) # <6>
|
||||
|
||||
def __repr__(self):
|
||||
return 'Vector({!r}, {!r})'.format(*self)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return bytes(array(Vector.typecode, self))
|
||||
|
||||
def __eq__(self, other):
|
||||
return tuple(self) == tuple(other)
|
||||
|
||||
# BEGIN VECTOR_V3_HASH
|
||||
def __hash__(self):
|
||||
return hash(self.x) ^ hash(self.y)
|
||||
# END VECTOR_V3_HASH
|
||||
|
||||
def __abs__(self):
|
||||
return math.hypot(self.x, self.y)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
||||
def angle(self):
|
||||
return math.atan2(self.y, self.x)
|
||||
|
||||
def __format__(self, fmt_spec=''):
|
||||
if fmt_spec.endswith('p'):
|
||||
fmt_spec = fmt_spec[:-1]
|
||||
coords = (abs(self), self.angle())
|
||||
outer_fmt = '<{}, {}>'
|
||||
else:
|
||||
coords = self
|
||||
outer_fmt = '({}, {})'
|
||||
components = (format(c, fmt_spec) for c in coords)
|
||||
return outer_fmt.format(*components)
|
||||
|
||||
@classmethod
|
||||
def frombytes(cls, octets):
|
||||
arr = array(Vector.typecode)
|
||||
arr.frombytes(octets)
|
||||
return cls(*arr)
|
||||
124
closures/global_x_local.rst
Normal file
124
closures/global_x_local.rst
Normal file
@@ -0,0 +1,124 @@
|
||||
>>> def f1(a):
|
||||
... print(a)
|
||||
... print(b)
|
||||
...
|
||||
>>> f1(3)
|
||||
3
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
File "<stdin>", line 3, in f1
|
||||
NameError: name 'b' is not defined
|
||||
>>> b = 6
|
||||
>>> f1(3)
|
||||
3
|
||||
6
|
||||
|
||||
>>> def f2(a):
|
||||
... print(a)
|
||||
... print(b)
|
||||
... b = 9
|
||||
...
|
||||
>>> f2(3)
|
||||
3
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
File "<stdin>", line 3, in f2
|
||||
UnboundLocalError: local variable 'b' referenced before assignment
|
||||
|
||||
|
||||
# BEGIN F1_DIS
|
||||
>>> from dis import dis
|
||||
>>> dis(f1)
|
||||
2 0 LOAD_GLOBAL 0 (print) <1>
|
||||
3 LOAD_FAST 0 (a) <2>
|
||||
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
|
||||
9 POP_TOP
|
||||
|
||||
3 10 LOAD_GLOBAL 0 (print)
|
||||
13 LOAD_GLOBAL 1 (b) <3>
|
||||
16 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
|
||||
19 POP_TOP
|
||||
20 LOAD_CONST 0 (None)
|
||||
23 RETURN_VALUE
|
||||
# END F1_DIS
|
||||
# BEGIN F2_DIS
|
||||
>>> dis(f2)
|
||||
2 0 LOAD_GLOBAL 0 (print)
|
||||
3 LOAD_FAST 0 (a)
|
||||
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
|
||||
9 POP_TOP
|
||||
|
||||
3 10 LOAD_GLOBAL 0 (print)
|
||||
13 LOAD_FAST 1 (b) <1>
|
||||
16 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
|
||||
19 POP_TOP
|
||||
|
||||
4 20 LOAD_CONST 1 (9)
|
||||
23 STORE_FAST 1 (b)
|
||||
26 LOAD_CONST 0 (None)
|
||||
29 RETURN_VALUE
|
||||
# END F2_DIS
|
||||
>>> def f3(a):
|
||||
... global b
|
||||
... print(a)
|
||||
... print(b)
|
||||
... b = 9
|
||||
...
|
||||
>>> f3(3)
|
||||
3
|
||||
6
|
||||
>>> b
|
||||
9
|
||||
# BEGIN F3_DIS
|
||||
>>> dis(f3)
|
||||
3 0 LOAD_GLOBAL 0 (print)
|
||||
3 LOAD_FAST 0 (a)
|
||||
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
|
||||
9 POP_TOP
|
||||
|
||||
4 10 LOAD_GLOBAL 0 (print)
|
||||
13 LOAD_GLOBAL 1 (b)
|
||||
16 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
|
||||
19 POP_TOP
|
||||
|
||||
5 20 LOAD_CONST 1 (9)
|
||||
23 STORE_GLOBAL 1 (b)
|
||||
26 LOAD_CONST 0 (None)
|
||||
29 RETURN_VALUE
|
||||
# END F3_DIS
|
||||
|
||||
>>> def f4(b):
|
||||
... def f5(a):
|
||||
... nonlocal b
|
||||
... print(a)
|
||||
... print(b)
|
||||
... b = 7
|
||||
... return f5
|
||||
...
|
||||
>>> f5 = f4(8)
|
||||
>>> f5(2)
|
||||
2
|
||||
8
|
||||
>>> b
|
||||
9
|
||||
>>> f5(3)
|
||||
3
|
||||
7????
|
||||
|
||||
>>> dis(f5)
|
||||
4 0 LOAD_GLOBAL 0 (print)
|
||||
3 LOAD_FAST 0 (a)
|
||||
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
|
||||
9 POP_TOP
|
||||
|
||||
5 10 LOAD_GLOBAL 0 (print)
|
||||
13 LOAD_DEREF 0 (b)
|
||||
16 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
|
||||
19 POP_TOP
|
||||
|
||||
6 20 LOAD_CONST 1 (7)
|
||||
23 STORE_DEREF 0 (b)
|
||||
26 LOAD_CONST 0 (None)
|
||||
29 RETURN_VALUE
|
||||
|
||||
|
||||
54
datamodel/frenchdeck.doctest
Normal file
54
datamodel/frenchdeck.doctest
Normal file
@@ -0,0 +1,54 @@
|
||||
>>> from frenchdeck import FrenchDeck, Card
|
||||
>>> beer_card = Card('7', 'diamonds')
|
||||
>>> beer_card
|
||||
Card(rank='7', suit='diamonds')
|
||||
>>> deck = FrenchDeck()
|
||||
>>> len(deck)
|
||||
52
|
||||
>>> deck[:3]
|
||||
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
|
||||
>>> deck[12::13]
|
||||
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]
|
||||
>>> Card('Q', 'hearts') in deck
|
||||
True
|
||||
>>> Card('Z', 'clubs') in deck
|
||||
False
|
||||
>>> for card in deck: # doctest: +ELLIPSIS
|
||||
... print(card)
|
||||
Card(rank='2', suit='spades')
|
||||
Card(rank='3', suit='spades')
|
||||
Card(rank='4', suit='spades')
|
||||
...
|
||||
>>> for card in reversed(deck): # doctest: +ELLIPSIS
|
||||
... print(card)
|
||||
Card(rank='A', suit='hearts')
|
||||
Card(rank='K', suit='hearts')
|
||||
Card(rank='Q', suit='hearts')
|
||||
...
|
||||
>>> for n, card in enumerate(deck, 1): # doctest: +ELLIPSIS
|
||||
... print(n, card)
|
||||
1 Card(rank='2', suit='spades')
|
||||
2 Card(rank='3', suit='spades')
|
||||
3 Card(rank='4', suit='spades')
|
||||
...
|
||||
>>> def alt_color_rank(card):
|
||||
... rank_value = FrenchDeck.ranks.index(card.rank)
|
||||
... suits = 'diamonds clubs hearts spades'.split()
|
||||
... return rank_value * len(suits) + suits.index(card.suit)
|
||||
|
||||
Rank test:
|
||||
|
||||
>>> alt_color_rank(Card('2', 'diamonds'))
|
||||
0
|
||||
>>> alt_color_rank(Card('A', 'spades'))
|
||||
51
|
||||
|
||||
>>> for card in sorted(deck, key=alt_color_rank): # doctest: +ELLIPSIS
|
||||
... print(card)
|
||||
Card(rank='2', suit='diamonds')
|
||||
Card(rank='2', suit='clubs')
|
||||
Card(rank='2', suit='hearts')
|
||||
...
|
||||
Card(rank='A', suit='clubs')
|
||||
Card(rank='A', suit='hearts')
|
||||
Card(rank='A', suit='spades')
|
||||
17
datamodel/frenchdeck.py
Normal file
17
datamodel/frenchdeck.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import collections
|
||||
|
||||
Card = collections.namedtuple('Card', ['rank', 'suit'])
|
||||
|
||||
class FrenchDeck:
|
||||
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
|
||||
suits = 'spades diamonds clubs hearts'.split()
|
||||
|
||||
def __init__(self):
|
||||
self._cards = [Card(rank, suit) for suit in self.suits
|
||||
for rank in self.ranks]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._cards)
|
||||
|
||||
def __getitem__(self, position):
|
||||
return self._cards[position]
|
||||
4
datamodel/tox.ini
Normal file
4
datamodel/tox.ini
Normal file
@@ -0,0 +1,4 @@
|
||||
[pep8]
|
||||
ignore = E127,E302
|
||||
# E127: continuation line over-indented for visual indent
|
||||
# E302: expected 2 blank lines, found 1
|
||||
24
datamodel/vector2d.py
Normal file
24
datamodel/vector2d.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from math import hypot
|
||||
|
||||
class Vector:
|
||||
|
||||
def __init__(self, x=0, y=0):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def __repr__(self):
|
||||
return 'Vector(%r, %r)' % (self.x, self.y)
|
||||
|
||||
def __abs__(self):
|
||||
return hypot(self.x, self.y)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
||||
def __add__(self, other):
|
||||
x = self.x + other.x
|
||||
y = self.y + other.y
|
||||
return Vector(x, y)
|
||||
|
||||
def __mul__(self, scalar):
|
||||
return Vector(self.x * scalar, self.y * scalar)
|
||||
33
decorators/average.py
Normal file
33
decorators/average.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
>>> avg = make_averager()
|
||||
>>> avg(10)
|
||||
10.0
|
||||
>>> avg(11)
|
||||
10.5
|
||||
>>> avg(12)
|
||||
11.0
|
||||
>>> avg.__code__.co_varnames
|
||||
('new_value', 'total')
|
||||
>>> avg.__code__.co_freevars
|
||||
('series',)
|
||||
>>> avg.__closure__ # doctest: +ELLIPSIS
|
||||
(<cell at 0x...: list object at 0x...>,)
|
||||
>>> avg.__closure__[0].cell_contents
|
||||
[10, 11, 12]
|
||||
"""
|
||||
|
||||
DEMO = """
|
||||
>>> avg.__closure__
|
||||
(<cell at 0x107a44f78: list object at 0x107a91a48>,)
|
||||
"""
|
||||
|
||||
|
||||
def make_averager():
|
||||
series = []
|
||||
|
||||
def averager(new_value):
|
||||
series.append(new_value)
|
||||
total = sum(series)
|
||||
return total/len(series)
|
||||
|
||||
return averager
|
||||
20
decorators/average_broken.py
Normal file
20
decorators/average_broken.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
>>> avg = make_averager()
|
||||
>>> avg(10)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
UnboundLocalError: local variable 'num_items' referenced before assignment
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def make_averager():
|
||||
num_items = 0
|
||||
total = 0
|
||||
|
||||
def averager(new_value):
|
||||
num_items += 1
|
||||
total += new_value
|
||||
return total / num_items
|
||||
|
||||
return averager
|
||||
45
decorators/average_fixed.py
Normal file
45
decorators/average_fixed.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
>>> avg = make_averager()
|
||||
>>> other_avg = make_averager()
|
||||
>>> avg(10)
|
||||
10.0
|
||||
>>> avg(11)
|
||||
10.5
|
||||
>>> avg(12)
|
||||
11.0
|
||||
>>> avg.__code__.co_varnames
|
||||
('new_value',)
|
||||
>>> avg.__code__.co_freevars
|
||||
('num_items', 'total')
|
||||
>>> avg.__closure__ # doctest: +ELLIPSIS
|
||||
(<cell at 0x...: int object at 0x...>, <cell at 0x...: int object at 0x...>)
|
||||
>>> avg.__closure__[0].cell_contents
|
||||
3
|
||||
>>> avg.__closure__[1].cell_contents
|
||||
33
|
||||
>>> other_avg(5)
|
||||
5.0
|
||||
>>> other_avg(10)
|
||||
7.5
|
||||
>>> other_avg(15)
|
||||
10.0
|
||||
"""
|
||||
|
||||
DEMO = """
|
||||
>>> avg.__closure__
|
||||
(<cell at 0x10fd24f78: int object at 0x10f6d3db0>,
|
||||
<cell at 0x10fd24d38: int object at 0x10f6d4170>)
|
||||
"""
|
||||
|
||||
|
||||
def make_averager():
|
||||
num_items = 0
|
||||
total = 0
|
||||
|
||||
def averager(new_value):
|
||||
nonlocal num_items, total
|
||||
num_items += 1
|
||||
total += new_value
|
||||
return total / num_items
|
||||
|
||||
return averager
|
||||
39
decorators/average_fixed_py2.py
Normal file
39
decorators/average_fixed_py2.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""
|
||||
>>> avg = make_averager()
|
||||
>>> avg(10)
|
||||
10.0
|
||||
>>> avg(11)
|
||||
10.5
|
||||
>>> avg(12)
|
||||
11.0
|
||||
>>> avg.__code__.co_varnames
|
||||
('new_value',)
|
||||
>>> avg.__code__.co_freevars
|
||||
('ns',)
|
||||
>>> avg.__closure__ # doctest: +ELLIPSIS
|
||||
(<cell at 0x...: Namespace object at 0x...>,)
|
||||
>>> avg.__closure__[0].cell_contents.__dict__
|
||||
{'total': 33, 'num_items': 3}
|
||||
"""
|
||||
|
||||
DEMO = """
|
||||
>>> avg.__closure__
|
||||
(<cell at 0x108df5980: Namespace object at 0x108e06790>,)
|
||||
"""
|
||||
|
||||
|
||||
class Namespace(object):
|
||||
pass
|
||||
|
||||
|
||||
def make_averager():
|
||||
ns = Namespace()
|
||||
ns.num_items = 0
|
||||
ns.total = 0
|
||||
|
||||
def averager(new_value):
|
||||
ns.num_items += 1
|
||||
ns.total += new_value
|
||||
return float(ns.total) / ns.num_items
|
||||
|
||||
return averager
|
||||
21
decorators/average_oo.py
Normal file
21
decorators/average_oo.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
>>> avg = Averager()
|
||||
>>> avg(10)
|
||||
10.0
|
||||
>>> avg(11)
|
||||
10.5
|
||||
>>> avg(12)
|
||||
11.0
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class Averager():
|
||||
|
||||
def __init__(self):
|
||||
self.series = []
|
||||
|
||||
def __call__(self, new_value):
|
||||
self.series.append(new_value)
|
||||
total = sum(self.series)
|
||||
return total/len(self.series)
|
||||
34
decorators/average_partial.py
Normal file
34
decorators/average_partial.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
>>> import functools
|
||||
>>> avg = functools.partial(averager, series=[])
|
||||
>>> avg(10)
|
||||
10.0
|
||||
>>> avg(11)
|
||||
10.5
|
||||
>>> avg(12)
|
||||
11.0
|
||||
>>> avg.args
|
||||
()
|
||||
>>> avg.keywords
|
||||
{'series': [10, 11, 12]}
|
||||
>>> avg.func # doctest: +ELLIPSIS
|
||||
<function averager at 0x...>
|
||||
>>> avg.func.__code__.co_varnames
|
||||
('new_value', 'series', 'total')
|
||||
"""
|
||||
|
||||
DEMO = """
|
||||
>>> avg.func
|
||||
<function averager at 0x1010c5560>
|
||||
>>> avg.func.__code__.co_varnames
|
||||
('new_value',)
|
||||
>>> avg.__code__.co_freevars
|
||||
('num_items', 'total')
|
||||
>>> avg.__closure__
|
||||
"""
|
||||
|
||||
def averager(new_value, series):
|
||||
series.append(new_value)
|
||||
total = sum(series)
|
||||
return float(total)/len(series)
|
||||
|
||||
28
decorators/average_py2.py
Normal file
28
decorators/average_py2.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
>>> avg = make_averager()
|
||||
>>> avg(10)
|
||||
10.0
|
||||
>>> avg(11)
|
||||
10.5
|
||||
>>> avg(12)
|
||||
11.0
|
||||
>>> avg.__code__.co_varnames
|
||||
('new_value', 'total')
|
||||
>>> avg.__code__.co_freevars
|
||||
('series',)
|
||||
>>> avg.__closure__ # doctest: +ELLIPSIS
|
||||
(<cell at 0x...: list object at 0x...>,)
|
||||
>>> avg.__closure__[0].cell_contents
|
||||
[10, 11, 12]
|
||||
"""
|
||||
|
||||
|
||||
def make_averager():
|
||||
series = []
|
||||
|
||||
def averager(new_value):
|
||||
series.append(new_value)
|
||||
total = sum(series)
|
||||
return float(total)/len(series)
|
||||
|
||||
return averager
|
||||
15
decorators/clockdeco.py
Normal file
15
decorators/clockdeco.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# clockdeco.py
|
||||
|
||||
import time
|
||||
|
||||
|
||||
def clock(func):
|
||||
def clocked(*args):
|
||||
t0 = time.time()
|
||||
result = func(*args)
|
||||
elapsed = time.time() - t0
|
||||
name = func.__name__
|
||||
arg_str = ', '.join(repr(arg) for arg in args)
|
||||
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
|
||||
return result
|
||||
return clocked
|
||||
23
decorators/clockdeco2.py
Normal file
23
decorators/clockdeco2.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# clockdeco2.py
|
||||
|
||||
import time
|
||||
import functools
|
||||
|
||||
|
||||
def clock(func):
|
||||
@functools.wraps(func)
|
||||
def clocked(*args, **kwargs):
|
||||
t0 = time.time()
|
||||
result = func(*args, **kwargs)
|
||||
elapsed = time.time() - t0
|
||||
name = func.__name__
|
||||
arg_lst = []
|
||||
if args:
|
||||
arg_lst.append(', '.join(repr(arg) for arg in args))
|
||||
if kwargs:
|
||||
pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
|
||||
arg_lst.append(', '.join(pairs))
|
||||
arg_str = ', '.join(arg_lst)
|
||||
print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result))
|
||||
return result
|
||||
return clocked
|
||||
31
decorators/clockdeco2_demo.py
Normal file
31
decorators/clockdeco2_demo.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# clockdec2o_demo.py
|
||||
|
||||
"""
|
||||
>>> pythagoras(3, 4) # doctest: +ELLIPSIS
|
||||
[0.0...s] pythagoras(3, 4) -> 5.0
|
||||
5.0
|
||||
>>> pythagoras(9, h=15) # doctest: +ELLIPSIS
|
||||
[0.0...s] pythagoras(9, h=15) -> 12.0
|
||||
12.0
|
||||
|
||||
"""
|
||||
import time
|
||||
import math
|
||||
from clockdeco2 import clock
|
||||
|
||||
|
||||
@clock
|
||||
def pythagoras(a, b=None, h=None):
|
||||
if b is None and h is None:
|
||||
raise TypeError('must provide second leg (b) or hypotenuse (h)')
|
||||
if h is None:
|
||||
return math.sqrt(a*a + b*b)
|
||||
else:
|
||||
return math.sqrt(h*h - a*a)
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
print('*' * 40, 'Calling pythagoras(3, 4)')
|
||||
pythagoras(3, 4)
|
||||
print('*' * 40, 'Calling pythagoras(9, h=15)')
|
||||
pythagoras(9, h=15)
|
||||
95
decorators/clockdeco2_tests.py
Normal file
95
decorators/clockdeco2_tests.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
>>> f_empty()
|
||||
[0.0...s] f_empty() -> None
|
||||
>>> f_args('spam', 3)
|
||||
[0.0...s] f_args('spam', 3) -> 'spamspamspam'
|
||||
'spamspamspam'
|
||||
>>> snooze(1234)
|
||||
[1...s] snooze(1234) -> None
|
||||
>>> average(1, 2, 3)
|
||||
[0.0...s] average(1, 2, 3) -> 2.0
|
||||
2.0
|
||||
>>> average(*range(10**3))
|
||||
[0.0...s] average(0, 1, ..., 999) -> 499.5
|
||||
499.5
|
||||
>>> factorial(10)
|
||||
[0.000...s] factorial(1) -> 1
|
||||
[0.000...s] factorial(2) -> 2
|
||||
[0.000...s] factorial(3) -> 6
|
||||
[0.000...s] factorial(4) -> 24
|
||||
[0.000...s] factorial(5) -> 120
|
||||
[0.000...s] factorial(6) -> 720
|
||||
[0.000...s] factorial(7) -> 5040
|
||||
[0.000...s] factorial(8) -> 40320
|
||||
[0.000...s] factorial(9) -> 362880
|
||||
[0.000...s] factorial(10) -> 3628800
|
||||
3628800
|
||||
>>> fibonacci(1)
|
||||
[0.000...s] fibonacci(1) -> 1
|
||||
1
|
||||
>>> fibonacci(5)
|
||||
[0.000...s] fibonacci(1) -> 1
|
||||
[0.000...s] fibonacci(0) -> 0
|
||||
[0.000...s] fibonacci(1) -> 1
|
||||
[0.000...s] fibonacci(2) -> 1
|
||||
[0.000...s] fibonacci(3) -> 2
|
||||
[0.000...s] fibonacci(0) -> 0
|
||||
[0.000...s] fibonacci(1) -> 1
|
||||
[0.000...s] fibonacci(2) -> 1
|
||||
[0.000...s] fibonacci(1) -> 1
|
||||
[0.000...s] fibonacci(0) -> 0
|
||||
[0.000...s] fibonacci(1) -> 1
|
||||
[0.000...s] fibonacci(2) -> 1
|
||||
[0.000...s] fibonacci(3) -> 2
|
||||
[0.000...s] fibonacci(4) -> 3
|
||||
[0.000...s] fibonacci(5) -> 5
|
||||
5
|
||||
>>> f_kwargs(3, 5, d='spam', c='eggs')
|
||||
[0.0...s] f_kwargs(3, 5, c='eggs', d='spam') -> 15
|
||||
15
|
||||
>>> f_args.__name__
|
||||
'f_args'
|
||||
>>> f_kwargs.__name__
|
||||
'f_kwargs'
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from clockdeco2 import clock
|
||||
|
||||
@clock
|
||||
def f_empty():
|
||||
pass
|
||||
|
||||
@clock
|
||||
def f_args(a, b):
|
||||
return a*b
|
||||
|
||||
@clock
|
||||
def snooze(milis):
|
||||
time.sleep(milis/1000)
|
||||
|
||||
@clock
|
||||
def average(*args):
|
||||
return sum(args) / len(args)
|
||||
|
||||
@clock
|
||||
def factorial(n):
|
||||
return 1 if n < 2 else n*factorial(n-1)
|
||||
|
||||
@clock
|
||||
def fibonacci(n):
|
||||
if n < 2:
|
||||
return n
|
||||
return fibonacci(n-2) + fibonacci(n-1)
|
||||
|
||||
@clock
|
||||
def f_kwargs(a, b, c=1, d='eggs'):
|
||||
from time import sleep
|
||||
sleep(0.001)
|
||||
return a*b
|
||||
|
||||
|
||||
|
||||
import doctest
|
||||
doctest.testmod(optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE)
|
||||
18
decorators/clockdeco_demo.py
Normal file
18
decorators/clockdeco_demo.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# clockdeco_demo.py
|
||||
|
||||
import time
|
||||
from clockdeco import clock
|
||||
|
||||
@clock
|
||||
def snooze(seconds):
|
||||
time.sleep(seconds)
|
||||
|
||||
@clock
|
||||
def factorial(n):
|
||||
return 1 if n < 2 else n*factorial(n-1)
|
||||
|
||||
if __name__=='__main__':
|
||||
print('*' * 40, 'Calling snooze(.123)')
|
||||
snooze(.123)
|
||||
print('*' * 40, 'Calling factorial(6)')
|
||||
print('6! =', factorial(6))
|
||||
22
decorators/clockdeco_demo2.py
Normal file
22
decorators/clockdeco_demo2.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from clockdeco import clock
|
||||
|
||||
import time
|
||||
|
||||
@clock
|
||||
def snooze(milis):
|
||||
time.sleep(milis/1000)
|
||||
|
||||
@clock
|
||||
def factorial(n):
|
||||
return 1 if n < 2 else n*factorial(n-1)
|
||||
|
||||
|
||||
@clock
|
||||
def fibonacci(n):
|
||||
if n < 2:
|
||||
return n
|
||||
return fibonacci(n-2) + fibonacci(n-1)
|
||||
|
||||
snooze(123)
|
||||
print(factorial(6))
|
||||
print(fibonacci(4))
|
||||
20
decorators/clockdeco_demo3.py
Normal file
20
decorators/clockdeco_demo3.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import functools
|
||||
from clockdeco import clock
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
@clock
|
||||
def factorial(n):
|
||||
return 1 if n < 2 else n*factorial(n-1)
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
@clock
|
||||
def fibonacci(n):
|
||||
if n < 2:
|
||||
return n
|
||||
return fibonacci(n-2) + fibonacci(n-1)
|
||||
|
||||
|
||||
print(factorial(6))
|
||||
print(fibonacci(6))
|
||||
40
decorators/clockdeco_param.py
Normal file
40
decorators/clockdeco_param.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# clockdeco_param.py
|
||||
|
||||
"""
|
||||
>>> snooze(.1) # doctest: +ELLIPSIS
|
||||
[0.101...s] snooze(0.1) -> None
|
||||
>>> clock('{name}: {elapsed}')(time.sleep)(.2) # doctest: +ELLIPSIS
|
||||
sleep: 0.20...
|
||||
>>> clock('{name}({args}) dt={elapsed:0.3f}s')(time.sleep)(.2)
|
||||
sleep(0.2) dt=0.201s
|
||||
"""
|
||||
|
||||
# BEGIN CLOCKDECO_PARAM
|
||||
import time
|
||||
|
||||
DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'
|
||||
|
||||
def clock(fmt=DEFAULT_FMT): # <1>
|
||||
def decorate(func): # <2>
|
||||
def clocked(*_args): # <3>
|
||||
t0 = time.time()
|
||||
_result = func(*_args) # <4>
|
||||
elapsed = time.time() - t0
|
||||
name = func.__name__
|
||||
args = ', '.join(repr(arg) for arg in _args) # <5>
|
||||
result = repr(_result) # <6>
|
||||
print(fmt.format(**locals())) # <7>
|
||||
return _result # <8>
|
||||
return clocked # <9>
|
||||
return decorate # <10>
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@clock() # <11>
|
||||
def snooze(seconds):
|
||||
time.sleep(seconds)
|
||||
|
||||
for i in range(3):
|
||||
snooze(.123)
|
||||
|
||||
# END CLOCKDECO_PARAM
|
||||
9
decorators/clockdeco_param_demo1.py
Normal file
9
decorators/clockdeco_param_demo1.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import time
|
||||
from clockdeco_param import clock
|
||||
|
||||
@clock('{name}: {elapsed}s')
|
||||
def snooze(seconds):
|
||||
time.sleep(seconds)
|
||||
|
||||
for i in range(3):
|
||||
snooze(.123)
|
||||
9
decorators/clockdeco_param_demo2.py
Normal file
9
decorators/clockdeco_param_demo2.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import time
|
||||
from clockdeco_param import clock
|
||||
|
||||
@clock('{name}({args}) dt={elapsed:0.3f}s')
|
||||
def snooze(seconds):
|
||||
time.sleep(seconds)
|
||||
|
||||
for i in range(3):
|
||||
snooze(.123)
|
||||
82
decorators/clockdeco_tests.py
Normal file
82
decorators/clockdeco_tests.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""
|
||||
>>> f_empty()
|
||||
[0.0...s] f_empty() -> None
|
||||
>>> f_args('spam', 3)
|
||||
[0.0...s] f_args('spam', 3) -> 'spamspamspam'
|
||||
'spamspamspam'
|
||||
>>> snooze(1234)
|
||||
[1...s] snooze(1234) -> None
|
||||
>>> average(1, 2, 3)
|
||||
[0.0...s] average(1, 2, 3) -> 2.0
|
||||
2.0
|
||||
>>> average(*range(10**3))
|
||||
[0.0...s] average(0, 1, ..., 999) -> 499.5
|
||||
499.5
|
||||
>>> factorial(10)
|
||||
[0.000...s] factorial(1) -> 1
|
||||
[0.000...s] factorial(2) -> 2
|
||||
[0.000...s] factorial(3) -> 6
|
||||
[0.000...s] factorial(4) -> 24
|
||||
[0.000...s] factorial(5) -> 120
|
||||
[0.000...s] factorial(6) -> 720
|
||||
[0.000...s] factorial(7) -> 5040
|
||||
[0.000...s] factorial(8) -> 40320
|
||||
[0.000...s] factorial(9) -> 362880
|
||||
[0.000...s] factorial(10) -> 3628800
|
||||
3628800
|
||||
>>> fibonacci(1)
|
||||
[0.000...s] fibonacci(1) -> 1
|
||||
1
|
||||
>>> fibonacci(5)
|
||||
[0.000...s] fibonacci(1) -> 1
|
||||
[0.000...s] fibonacci(0) -> 0
|
||||
[0.000...s] fibonacci(1) -> 1
|
||||
[0.000...s] fibonacci(2) -> 1
|
||||
[0.000...s] fibonacci(3) -> 2
|
||||
[0.000...s] fibonacci(0) -> 0
|
||||
[0.000...s] fibonacci(1) -> 1
|
||||
[0.000...s] fibonacci(2) -> 1
|
||||
[0.000...s] fibonacci(1) -> 1
|
||||
[0.000...s] fibonacci(0) -> 0
|
||||
[0.000...s] fibonacci(1) -> 1
|
||||
[0.000...s] fibonacci(2) -> 1
|
||||
[0.000...s] fibonacci(3) -> 2
|
||||
[0.000...s] fibonacci(4) -> 3
|
||||
[0.000...s] fibonacci(5) -> 5
|
||||
5
|
||||
>>> f_args.__name__
|
||||
'clocked'
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from clockdeco import clock
|
||||
|
||||
@clock
|
||||
def f_empty():
|
||||
pass
|
||||
|
||||
@clock
|
||||
def f_args(a, b):
|
||||
return a*b
|
||||
|
||||
@clock
|
||||
def snooze(milis):
|
||||
time.sleep(milis/1000)
|
||||
|
||||
@clock
|
||||
def average(*args):
|
||||
return sum(args) / len(args)
|
||||
|
||||
@clock
|
||||
def factorial(n):
|
||||
return 1 if n < 2 else n*factorial(n-1)
|
||||
|
||||
@clock
|
||||
def fibonacci(n):
|
||||
if n < 2:
|
||||
return n
|
||||
return fibonacci(n-2) + fibonacci(n-1)
|
||||
|
||||
import doctest
|
||||
doctest.testmod(optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE)
|
||||
50
decorators/currency.py
Normal file
50
decorators/currency.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# currency.py
|
||||
|
||||
"""
|
||||
>>> convert(1, 'BRL', 'USD')
|
||||
0.4591
|
||||
>>> convert(1, 'USD', 'BRL')
|
||||
2.1784
|
||||
>>> convert(1, 'EUR', 'USD')
|
||||
1.3482
|
||||
>>> convert(1, 'USD', 'EUR')
|
||||
0.7417
|
||||
>>> convert(1, 'EUR', 'BRL')
|
||||
2.9369
|
||||
>>> convert(1, 'BRL', 'EUR')
|
||||
0.3405
|
||||
|
||||
>>> from functools import partial
|
||||
>>> eur = partial(convert, cur_to='EUR')
|
||||
>>> eur(1, 'USD')
|
||||
0.7417
|
||||
>>> eur(1, 'BRL')
|
||||
0.3405
|
||||
>>> eur2brl = partial(convert, cur_from='EUR', cur_to='BRL')
|
||||
>>> eur2brl(100)
|
||||
293.6864
|
||||
>>> type(eur2brl)
|
||||
<class 'functools.partial'>
|
||||
"""
|
||||
|
||||
DEMO = """
|
||||
>>> eur2brl.func
|
||||
<function convert at 0x1010c5560>
|
||||
>>> eur2brl.args, eur2brl.keywords
|
||||
((), {'cur_from': 'EUR', 'cur_to': 'BRL'})
|
||||
"""
|
||||
|
||||
rates = {'BRL': 2.17836,
|
||||
'CAD': 1.03615,
|
||||
'CNY': 6.10562,
|
||||
'EUR': 0.74173,
|
||||
'GBP': 0.62814,
|
||||
'INR': 61.8685,
|
||||
'JPY': 98.6002,
|
||||
'USD': 1.0}
|
||||
|
||||
reference = rates['USD']
|
||||
|
||||
def convert(amount, cur_from, cur_to):
|
||||
ref_amount = reference / rates[cur_from] * amount
|
||||
return round(ref_amount * rates[cur_to], 4)
|
||||
10
decorators/fibo_demo.py
Normal file
10
decorators/fibo_demo.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from clockdeco import clock
|
||||
|
||||
@clock
|
||||
def fibonacci(n):
|
||||
if n < 2:
|
||||
return n
|
||||
return fibonacci(n-2) + fibonacci(n-1)
|
||||
|
||||
if __name__=='__main__':
|
||||
print(fibonacci(6))
|
||||
13
decorators/fibo_demo_lru.py
Normal file
13
decorators/fibo_demo_lru.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import functools
|
||||
|
||||
from clockdeco import clock
|
||||
|
||||
@functools.lru_cache() # <1>
|
||||
@clock # <2>
|
||||
def fibonacci(n):
|
||||
if n < 2:
|
||||
return n
|
||||
return fibonacci(n-2) + fibonacci(n-1)
|
||||
|
||||
if __name__=='__main__':
|
||||
print(fibonacci(6))
|
||||
60
decorators/fibonacci.py
Normal file
60
decorators/fibonacci.py
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
# source: http://oeis.org/A000045
|
||||
fibo_seq = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610,
|
||||
987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025,
|
||||
121393, 196418, 317811, 514229, 832040, 1346269, 2178309,
|
||||
3524578, 5702887, 9227465, 14930352, 24157817, 39088169]
|
||||
|
||||
from functools import lru_cache
|
||||
|
||||
def fibonacci(n):
|
||||
if n < 2:
|
||||
return n
|
||||
return fibonacci(n-2) + fibonacci(n-1)
|
||||
|
||||
@lru_cache()
|
||||
def fibonacci2(n):
|
||||
if n < 2:
|
||||
return n
|
||||
return fibonacci2(n-2) + fibonacci2(n-1)
|
||||
|
||||
def memoize(func):
|
||||
'''simplest memoizing decorator'''
|
||||
cache = {}
|
||||
def memoized(*args):
|
||||
if args not in cache:
|
||||
cache[args] = func(*args)
|
||||
return cache[args]
|
||||
return memoized
|
||||
|
||||
def test():
|
||||
for i, expected in enumerate(fibo_seq[:31]):
|
||||
print(i, expected)
|
||||
assert fibonacci(i) == expected
|
||||
|
||||
def chronograph():
|
||||
global fibonacci
|
||||
from time import time
|
||||
t0 = time()
|
||||
n = 32
|
||||
res = fibonacci(n)
|
||||
#res = [fibonacci(n) for n in range(30)]
|
||||
t1 = time()
|
||||
print(n, res, format(t1-t0, '0.6f'))
|
||||
|
||||
t0 = time()
|
||||
res = fibonacci2(n)
|
||||
#res = [fibonacci2(n) for n in range(30)]
|
||||
t1 = time()
|
||||
print(n, res, format(t1-t0, '0.6f'))
|
||||
|
||||
t0 = time()
|
||||
fibonacci = memoize(fibonacci)
|
||||
res = fibonacci(n)
|
||||
#res = [fibonacci2(n) for n in range(30)]
|
||||
t1 = time()
|
||||
print(n, res, format(t1-t0, '0.6f'))
|
||||
|
||||
if __name__=='__main__':
|
||||
#test()
|
||||
chronograph()
|
||||
49
decorators/generic.py
Normal file
49
decorators/generic.py
Normal file
@@ -0,0 +1,49 @@
|
||||
r"""
|
||||
htmlize(): generic function example
|
||||
|
||||
# BEGIN HTMLIZE_DEMO
|
||||
|
||||
>>> htmlize({1, 2, 3}) # <1>
|
||||
'<pre>{1, 2, 3}</pre>'
|
||||
>>> htmlize(abs)
|
||||
'<pre><built-in function abs></pre>'
|
||||
>>> htmlize('Heimlich & Co.\n- a game') # <2>
|
||||
'<p>Heimlich & Co.<br>\n- a game</p>'
|
||||
>>> htmlize(42) # <3>
|
||||
'<pre>42 (0x2a)</pre>'
|
||||
>>> print(htmlize(['alpha', 66, {3, 2, 1}])) # <4>
|
||||
<ul>
|
||||
<li><p>alpha</p></li>
|
||||
<li><pre>66 (0x42)</pre></li>
|
||||
<li><pre>{1, 2, 3}</pre></li>
|
||||
</ul>
|
||||
|
||||
# END HTMLIZE_DEMO
|
||||
"""
|
||||
|
||||
# BEGIN HTMLIZE
|
||||
|
||||
from functools import singledispatch
|
||||
import html
|
||||
|
||||
@singledispatch # <1>
|
||||
def htmlize(obj):
|
||||
content = html.escape(repr(obj))
|
||||
return '<pre>{}</pre>'.format(content)
|
||||
|
||||
@htmlize.register(str) # <2>
|
||||
def _(text): # <3>
|
||||
content = html.escape(text).replace('\n', '<br>\n')
|
||||
return '<p>{0}</p>'.format(content)
|
||||
|
||||
@htmlize.register(int) # <4>
|
||||
def _(n):
|
||||
return '<pre>{0} (0x{0:x})</pre>'.format(n)
|
||||
|
||||
@htmlize.register(list)
|
||||
def _(a_list):
|
||||
inner = '</li>\n<li>'.join(htmlize(item) for item in a_list)
|
||||
return '<ul>\n<li>' + inner + '</li>\n</ul>'
|
||||
|
||||
# END HTMLIZE
|
||||
|
||||
63
decorators/local_demo.py
Normal file
63
decorators/local_demo.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
>>> f1(3)
|
||||
>>> b = 8
|
||||
>>> f1(3)
|
||||
a = 3
|
||||
b = 8
|
||||
>>> f2(3)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
UnboundLocalError: local variable 'b' referenced before assignment
|
||||
>>> f3(3)
|
||||
a = 3
|
||||
b = 7
|
||||
b = 6
|
||||
>>> b = -5
|
||||
>>> ff = f4()
|
||||
>>> ff(3)
|
||||
a = 3
|
||||
b = 11
|
||||
b = 6
|
||||
>>> print('b =', b)
|
||||
b = -5
|
||||
"""
|
||||
|
||||
def f1(a):
|
||||
print('a =', a)
|
||||
print('b =', b)
|
||||
|
||||
def f2(a):
|
||||
print('a =', a)
|
||||
print('b =', b)
|
||||
b = a * 10
|
||||
print('b =', b)
|
||||
|
||||
def f3(a):
|
||||
global b
|
||||
print('a =', a)
|
||||
print('b =', b)
|
||||
b = a * 10
|
||||
print('b =', b)
|
||||
|
||||
def f3b(a):
|
||||
nonlocal b
|
||||
print('a =', a)
|
||||
print('b =', b)
|
||||
b = a * 10
|
||||
print('b =', b)
|
||||
|
||||
def f4():
|
||||
b = 11
|
||||
def f5(a):
|
||||
nonlocal b
|
||||
print('a =', a)
|
||||
print('b =', b)
|
||||
b = a * 2
|
||||
print('b =', b)
|
||||
return f5
|
||||
|
||||
import doctest
|
||||
doctest.testmod(optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE)
|
||||
|
||||
|
||||
|
||||
31
decorators/registration.py
Normal file
31
decorators/registration.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# BEGIN REGISTRATION
|
||||
|
||||
registry = [] # <1>
|
||||
|
||||
def register(func): # <2>
|
||||
print('running register(%s)' % func) # <3>
|
||||
registry.append(func) # <4>
|
||||
return func # <5>
|
||||
|
||||
@register # <6>
|
||||
def f1():
|
||||
print('running f1()')
|
||||
|
||||
@register
|
||||
def f2():
|
||||
print('running f2()')
|
||||
|
||||
def f3(): # <7>
|
||||
print('running f3()')
|
||||
|
||||
def main(): # <8>
|
||||
print('running main()')
|
||||
print('registry ->', registry)
|
||||
f1()
|
||||
f2()
|
||||
f3()
|
||||
|
||||
if __name__=='__main__':
|
||||
main() # <9>
|
||||
|
||||
# END REGISTRATION
|
||||
16
decorators/registration_abridged.py
Normal file
16
decorators/registration_abridged.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# BEGIN REGISTRATION_ABRIDGED
|
||||
registry = []
|
||||
|
||||
def register(func):
|
||||
print('running register(%s)' % func)
|
||||
registry.append(func)
|
||||
return func
|
||||
|
||||
@register
|
||||
def f1():
|
||||
print('running f1()')
|
||||
|
||||
print('running main()')
|
||||
print('registry ->', registry)
|
||||
f1()
|
||||
# END REGISTRATION_ABRIDGED
|
||||
29
decorators/registration_param.py
Normal file
29
decorators/registration_param.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# BEGIN REGISTRATION_PARAM
|
||||
|
||||
registry = set() # <1>
|
||||
|
||||
def register(active=True): # <2>
|
||||
def decorate(func): # <3>
|
||||
print('running register(active=%s)->decorate(%s)'
|
||||
% (active, func))
|
||||
if active: # <4>
|
||||
registry.add(func)
|
||||
else: # <5>
|
||||
if func in registry:
|
||||
registry.remove(func)
|
||||
|
||||
return func # <6>
|
||||
return decorate # <7>
|
||||
|
||||
@register(active=False) # <8>
|
||||
def f1():
|
||||
print('running f1()')
|
||||
|
||||
@register() # <9>
|
||||
def f2():
|
||||
print('running f2()')
|
||||
|
||||
def f3():
|
||||
print('running f3()')
|
||||
|
||||
# END REGISTRATION_PARAM
|
||||
27
decorators/stacked_demo.py
Normal file
27
decorators/stacked_demo.py
Normal file
@@ -0,0 +1,27 @@
|
||||
def d1(f):
|
||||
def wrapped():
|
||||
print('d1/wrapped')
|
||||
return f()
|
||||
return wrapped
|
||||
|
||||
|
||||
def d2(f):
|
||||
def wrapped():
|
||||
print('d2/wrapped')
|
||||
return f()
|
||||
return wrapped
|
||||
|
||||
|
||||
@d1
|
||||
@d2
|
||||
def f():
|
||||
print('f')
|
||||
|
||||
f()
|
||||
|
||||
def g():
|
||||
print('g')
|
||||
|
||||
g = d1(d2(g))
|
||||
|
||||
g()
|
||||
113
decorators/strategy_best4.py
Normal file
113
decorators/strategy_best4.py
Normal file
@@ -0,0 +1,113 @@
|
||||
# strategy_best4.py
|
||||
# Strategy pattern -- function-based implementation
|
||||
# selecting best promotion from list of functions
|
||||
# registered by a decorator
|
||||
|
||||
"""
|
||||
>>> joe = Customer('John Doe', 0)
|
||||
>>> ann = Customer('Ann Smith', 1100)
|
||||
>>> cart = [LineItem('banana', 4, .5),
|
||||
... LineItem('apple', 10, 1.5),
|
||||
... LineItem('watermellon', 5, 5.0)]
|
||||
>>> Order(joe, cart, fidelity)
|
||||
<Order total: 42.00 due: 42.00>
|
||||
>>> Order(ann, cart, fidelity)
|
||||
<Order total: 42.00 due: 39.90>
|
||||
>>> banana_cart = [LineItem('banana', 30, .5),
|
||||
... LineItem('apple', 10, 1.5)]
|
||||
>>> Order(joe, banana_cart, bulk_item)
|
||||
<Order total: 30.00 due: 28.50>
|
||||
>>> long_order = [LineItem(str(item_code), 1, 1.0)
|
||||
... for item_code in range(10)]
|
||||
>>> Order(joe, long_order, large_order)
|
||||
<Order total: 10.00 due: 9.30>
|
||||
>>> Order(joe, cart, large_order)
|
||||
<Order total: 42.00 due: 42.00>
|
||||
|
||||
# BEGIN STRATEGY_BEST_TESTS
|
||||
|
||||
>>> Order(joe, long_order, best_promo)
|
||||
<Order total: 10.00 due: 9.30>
|
||||
>>> Order(joe, banana_cart, best_promo)
|
||||
<Order total: 30.00 due: 28.50>
|
||||
>>> Order(ann, cart, best_promo)
|
||||
<Order total: 42.00 due: 39.90>
|
||||
|
||||
# END STRATEGY_BEST_TESTS
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
Customer = namedtuple('Customer', 'name fidelity')
|
||||
|
||||
|
||||
class LineItem:
|
||||
|
||||
def __init__(self, product, quantity, price):
|
||||
self.product = product
|
||||
self.quantity = quantity
|
||||
self.price = price
|
||||
|
||||
def total(self):
|
||||
return self.price * self.quantity
|
||||
|
||||
|
||||
class Order: # the Context
|
||||
|
||||
def __init__(self, customer, cart, promotion=None):
|
||||
self.customer = customer
|
||||
self.cart = list(cart)
|
||||
self.promotion = promotion
|
||||
|
||||
def total(self):
|
||||
if not hasattr(self, '__total'):
|
||||
self.__total = sum(item.total() for item in self.cart)
|
||||
return self.__total
|
||||
|
||||
def due(self):
|
||||
if self.promotion is None:
|
||||
discount = 0
|
||||
else:
|
||||
discount = self.promotion(self)
|
||||
return self.total() - discount
|
||||
|
||||
def __repr__(self):
|
||||
fmt = '<Order total: {:.2f} due: {:.2f}>'
|
||||
return fmt.format(self.total(), self.due())
|
||||
|
||||
# BEGIN STRATEGY_BEST4
|
||||
|
||||
promos = [] # <1>
|
||||
|
||||
def promotion(promo_func): # <2>
|
||||
promos.append(promo_func)
|
||||
return promo_func
|
||||
|
||||
@promotion # <3>
|
||||
def fidelity(order):
|
||||
"""5% discount for customers with 1000 or more fidelity points"""
|
||||
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
|
||||
|
||||
@promotion
|
||||
def bulk_item(order):
|
||||
"""10% discount for each LineItem with 20 or more units"""
|
||||
discount = 0
|
||||
for item in order.cart:
|
||||
if item.quantity >= 20:
|
||||
discount += item.total() * .1
|
||||
return discount
|
||||
|
||||
@promotion
|
||||
def large_order(order):
|
||||
"""7% discount for orders with 10 or more distinct items"""
|
||||
distinct_items = {item.product for item in order.cart}
|
||||
if len(distinct_items) >= 10:
|
||||
return order.total() * .07
|
||||
return 0
|
||||
|
||||
def best_promo(order): # <4>
|
||||
"""Select best discount available
|
||||
"""
|
||||
return max(promo(order) for promo in promos)
|
||||
|
||||
# END STRATEGY_BEST4
|
||||
55
dicts/container_perftest.py
Normal file
55
dicts/container_perftest.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
Container ``in`` operator performance test
|
||||
"""
|
||||
import sys
|
||||
import timeit
|
||||
|
||||
SETUP = '''
|
||||
import array
|
||||
selected = array.array('d')
|
||||
with open('selected.arr', 'rb') as fp:
|
||||
selected.fromfile(fp, {size})
|
||||
if {container_type} is dict:
|
||||
haystack = dict.fromkeys(selected, 1)
|
||||
else:
|
||||
haystack = {container_type}(selected)
|
||||
if {verbose}:
|
||||
print(type(haystack), end=' ')
|
||||
print('haystack: %10d' % len(haystack), end=' ')
|
||||
needles = array.array('d')
|
||||
with open('not_selected.arr', 'rb') as fp:
|
||||
needles.fromfile(fp, 500)
|
||||
needles.extend(selected[::{size}//500])
|
||||
if {verbose}:
|
||||
print(' needles: %10d' % len(needles), end=' ')
|
||||
'''
|
||||
|
||||
TEST = '''
|
||||
found = 0
|
||||
for n in needles:
|
||||
if n in haystack:
|
||||
found += 1
|
||||
if {verbose}:
|
||||
print(' found: %10d' % found)
|
||||
'''
|
||||
|
||||
def test(container_type, verbose):
|
||||
MAX_EXPONENT = 7
|
||||
for n in range(3, MAX_EXPONENT + 1):
|
||||
size = 10**n
|
||||
setup = SETUP.format(container_type=container_type,
|
||||
size=size, verbose=verbose)
|
||||
test = TEST.format(verbose=verbose)
|
||||
tt = timeit.repeat(stmt=test, setup=setup, repeat=5, number=1)
|
||||
print('|{:{}d}|{:f}'.format(size, MAX_EXPONENT + 1, min(tt)))
|
||||
|
||||
if __name__=='__main__':
|
||||
if '-v' in sys.argv:
|
||||
sys.argv.remove('-v')
|
||||
verbose = True
|
||||
else:
|
||||
verbose = False
|
||||
if len(sys.argv) != 2:
|
||||
print('Usage: %s <container_type>' % sys.argv[0])
|
||||
else:
|
||||
test(sys.argv[1], verbose)
|
||||
37
dicts/container_perftest_datagen.py
Normal file
37
dicts/container_perftest_datagen.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
Generate data for container performance test
|
||||
"""
|
||||
|
||||
import random
|
||||
import array
|
||||
|
||||
MAX_EXPONENT = 7
|
||||
HAYSTACK_LEN = 10 ** MAX_EXPONENT
|
||||
NEEDLES_LEN = 10 ** (MAX_EXPONENT - 1)
|
||||
SAMPLE_LEN = HAYSTACK_LEN + NEEDLES_LEN // 2
|
||||
|
||||
needles = array.array('d')
|
||||
|
||||
sample = {1/random.random() for i in range(SAMPLE_LEN)}
|
||||
print('initial sample: %d elements' % len(sample))
|
||||
|
||||
# complete sample, in case duplicate random numbers were discarded
|
||||
while len(sample) < SAMPLE_LEN:
|
||||
sample.add(1/random.random())
|
||||
|
||||
print('complete sample: %d elements' % len(sample))
|
||||
|
||||
sample = array.array('d', sample)
|
||||
random.shuffle(sample)
|
||||
|
||||
not_selected = sample[:NEEDLES_LEN // 2]
|
||||
print('not selected: %d samples' % len(not_selected))
|
||||
print(' writing not_selected.arr')
|
||||
with open('not_selected.arr', 'wb') as fp:
|
||||
not_selected.tofile(fp)
|
||||
|
||||
selected = sample[NEEDLES_LEN // 2:]
|
||||
print('selected: %d samples' % len(selected))
|
||||
print(' writing selected.arr')
|
||||
with open('selected.arr', 'wb') as fp:
|
||||
selected.tofile(fp)
|
||||
30
dicts/dialcodes.py
Normal file
30
dicts/dialcodes.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# BEGIN DIALCODES
|
||||
# dial codes of the top 10 most populous countries
|
||||
DIAL_CODES = [
|
||||
(86, 'China'),
|
||||
(91, 'India'),
|
||||
(1, 'United States'),
|
||||
(62, 'Indonesia'),
|
||||
(55, 'Brazil'),
|
||||
(92, 'Pakistan'),
|
||||
(880, 'Bangladesh'),
|
||||
(234, 'Nigeria'),
|
||||
(7, 'Russia'),
|
||||
(81, 'Japan'),
|
||||
]
|
||||
|
||||
d1 = dict(DIAL_CODES) # <1>
|
||||
print('d1:', d1.keys())
|
||||
d2 = dict(sorted(DIAL_CODES)) # <2>
|
||||
print('d2:', d2.keys())
|
||||
d3 = dict(sorted(DIAL_CODES, key=lambda x:x[1])) # <3>
|
||||
print('d3:', d3.keys())
|
||||
assert d1 == d2 and d2 == d3 # <4>
|
||||
# END DIALCODES
|
||||
"""
|
||||
# BEGIN DIALCODES_OUTPUT
|
||||
d1: dict_keys([880, 1, 86, 55, 7, 234, 91, 92, 62, 81])
|
||||
d2: dict_keys([880, 1, 91, 86, 81, 55, 234, 7, 92, 62])
|
||||
d3: dict_keys([880, 81, 1, 86, 55, 7, 234, 91, 92, 62])
|
||||
# END DIALCODES_OUTPUT
|
||||
"""
|
||||
34
dicts/dict_perftest.py
Normal file
34
dicts/dict_perftest.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
Dict performance test
|
||||
"""
|
||||
|
||||
import timeit
|
||||
|
||||
SETUP = '''
|
||||
import array
|
||||
selected = array.array('d')
|
||||
with open('selected.arr', 'rb') as fp:
|
||||
selected.fromfile(fp, {size})
|
||||
haystack = dict((n, n.as_integer_ratio()) for n in selected)
|
||||
print('haystack: %10d' % len(haystack), end=' ')
|
||||
needles = array.array('d')
|
||||
with open('not_selected.arr', 'rb') as fp:
|
||||
needles.fromfile(fp, 500)
|
||||
needles.extend(selected[:500])
|
||||
# print(' needles: %10d' % len(needles), end=' ')
|
||||
'''
|
||||
|
||||
TEST = '''
|
||||
found = 0
|
||||
for n in needles:
|
||||
if n in haystack:
|
||||
found += 1
|
||||
# print(' found: %10d' % found)
|
||||
'''
|
||||
|
||||
MAX_EXPONENT = 7
|
||||
for n in range(3, MAX_EXPONENT + 1):
|
||||
size = 10**n
|
||||
setup = SETUP.format(size=size)
|
||||
tt = timeit.repeat(stmt=TEST, setup=setup, repeat=5, number=1)
|
||||
print('|{:{}d}|{:f}'.format(size, MAX_EXPONENT + 1, min(tt)))
|
||||
20
dicts/hashdiff.py
Normal file
20
dicts/hashdiff.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import sys
|
||||
|
||||
MAX_BITS = len(format(sys.maxsize, 'b'))
|
||||
print('%s-bit Python build' % (MAX_BITS + 1))
|
||||
|
||||
def hash_diff(o1, o2):
|
||||
h1 = '{:>0{}b}'.format(hash(o1), MAX_BITS)
|
||||
h2 = '{:>0{}b}'.format(hash(o2), MAX_BITS)
|
||||
diff = ''.join('!' if b1 != b2 else ' ' for b1, b2 in zip(h1, h2))
|
||||
count = '!= {}'.format(diff.count('!'))
|
||||
width = max(len(repr(o1)), len(repr(o2)), 8)
|
||||
sep = '-' * (width * 2 + MAX_BITS)
|
||||
return '{!r:{width}} {}\n{:{width}} {} {}\n{!r:{width}} {}\n{}'.format(
|
||||
o1, h1, ' ' * width, diff, count, o2, h2, sep, width=width)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(hash_diff(1, 1.0))
|
||||
print(hash_diff(1.0, 1.0001))
|
||||
print(hash_diff(1.0001, 1.0002))
|
||||
print(hash_diff(1.0002, 1.0003))
|
||||
25
dicts/index.py
Normal file
25
dicts/index.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# adapted from Alex Martelli's example in "Re-learning Python"
|
||||
# http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf
|
||||
# (slide 41) Ex: lines-by-word file index
|
||||
|
||||
# BEGIN INDEX
|
||||
"""Build an index mapping word -> list of occurrences"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
|
||||
WORD_RE = re.compile('\w+')
|
||||
|
||||
index = {}
|
||||
with open(sys.argv[1], encoding='utf-8') as fp:
|
||||
for line_no, line in enumerate(fp, 1):
|
||||
for match in WORD_RE.finditer(line):
|
||||
word = match.group()
|
||||
column_no = match.start()+1
|
||||
location = (line_no, column_no)
|
||||
index.setdefault(word, []).append(location) # <1>
|
||||
|
||||
# print in alphabetical order
|
||||
for word in sorted(index, key=str.upper):
|
||||
print(word, index[word])
|
||||
# END INDEX
|
||||
28
dicts/index0.py
Normal file
28
dicts/index0.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# adapted from Alex Martelli's example in "Re-learning Python"
|
||||
# http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf
|
||||
# (slide 41) Ex: lines-by-word file index
|
||||
|
||||
# BEGIN INDEX0
|
||||
"""Build an index mapping word -> list of occurrences"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
|
||||
WORD_RE = re.compile('\w+')
|
||||
|
||||
index = {}
|
||||
with open(sys.argv[1], encoding='utf-8') as fp:
|
||||
for line_no, line in enumerate(fp, 1):
|
||||
for match in WORD_RE.finditer(line):
|
||||
word = match.group()
|
||||
column_no = match.start()+1
|
||||
location = (line_no, column_no)
|
||||
# this is ugly; coded like this to make a point
|
||||
occurrences = index.get(word, []) # <1>
|
||||
occurrences.append(location) # <2>
|
||||
index[word] = occurrences # <3>
|
||||
|
||||
# print in alphabetical order
|
||||
for word in sorted(index, key=str.upper):
|
||||
print(word, index[word])
|
||||
# END INDEX0
|
||||
22
dicts/index_alex.py
Normal file
22
dicts/index_alex.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# adapted from Alex Martelli's example in "Re-learning Python"
|
||||
# http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf
|
||||
# (slide 41) Ex: lines-by-word file index
|
||||
|
||||
|
||||
"""Build a map word -> list-of-line-numbers"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
|
||||
NONWORD_RE = re.compile('\W+')
|
||||
|
||||
idx = {}
|
||||
with open(sys.argv[1], encoding='utf-8') as fp:
|
||||
for n, line in enumerate(fp, 1):
|
||||
for word in NONWORD_RE.split(line):
|
||||
if word.strip():
|
||||
idx.setdefault(word, []).append(n)
|
||||
|
||||
# print in alphabetical order
|
||||
for word in sorted(idx, key=str.upper):
|
||||
print(word, idx[word])
|
||||
26
dicts/index_default.py
Normal file
26
dicts/index_default.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# adapted from Alex Martelli's example in "Re-learning Python"
|
||||
# http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf
|
||||
# (slide 41) Ex: lines-by-word file index
|
||||
|
||||
# BEGIN INDEX_DEFAULT
|
||||
"""Build an index mapping word -> list of occurrences"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
import collections
|
||||
|
||||
WORD_RE = re.compile('\w+')
|
||||
|
||||
index = collections.defaultdict(list) # <1>
|
||||
with open(sys.argv[1], encoding='utf-8') as fp:
|
||||
for line_no, line in enumerate(fp, 1):
|
||||
for match in WORD_RE.finditer(line):
|
||||
word = match.group()
|
||||
column_no = match.start()+1
|
||||
location = (line_no, column_no)
|
||||
index[word].append(location) # <2>
|
||||
|
||||
# print in alphabetical order
|
||||
for word in sorted(index, key=str.upper):
|
||||
print(word, index[word])
|
||||
# END INDEX_DEFAULT
|
||||
48
dicts/set_perftest.py
Normal file
48
dicts/set_perftest.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
Set performance test
|
||||
"""
|
||||
|
||||
import timeit
|
||||
|
||||
SETUP = '''
|
||||
import array
|
||||
selected = array.array('d')
|
||||
with open('selected.arr', 'rb') as fp:
|
||||
selected.fromfile(fp, {size})
|
||||
haystack = {type}(selected)
|
||||
# print('haystack: %10d' % len(haystack), end=' ')
|
||||
needles = array.array('d')
|
||||
with open('not_selected.arr', 'rb') as fp:
|
||||
needles.fromfile(fp, 500)
|
||||
needles.extend(selected[:500])
|
||||
needles = set(needles)
|
||||
# print(' needles: %10d' % len(needles), end=' ')
|
||||
'''
|
||||
|
||||
tests = [
|
||||
('FOR_LOOP_TEST', '''
|
||||
found = 0
|
||||
for n in needles:
|
||||
if n in haystack:
|
||||
found += 1
|
||||
assert found == 500
|
||||
'''),
|
||||
('SET_&_TEST', '''
|
||||
found = len(needles & haystack)
|
||||
assert found == 500
|
||||
'''
|
||||
)]
|
||||
|
||||
MAX_EXPONENT = 7
|
||||
for collection_type in 'dict.fromkeys set list'.split():
|
||||
if collection_type == 'set':
|
||||
available_tests = tests
|
||||
else:
|
||||
available_tests = tests[:1]
|
||||
for test_name, test in available_tests:
|
||||
print('*' * 25, collection_type, test_name)
|
||||
for n in range(3, MAX_EXPONENT + 1):
|
||||
size = 10**n
|
||||
setup = SETUP.format(type=collection_type, size=size)
|
||||
tt = timeit.repeat(stmt=test, setup=setup, repeat=5, number=1)
|
||||
print('|{:{}d}|{:9.6f}'.format(size, MAX_EXPONENT + 1, min(tt)))
|
||||
72
dicts/strkeydict.py
Normal file
72
dicts/strkeydict.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""StrKeyDict always converts non-string keys to `str`
|
||||
|
||||
Tests for item retrieval using `d[key]` notation::
|
||||
|
||||
>>> d = StrKeyDict([('2', 'two'), ('4', 'four')])
|
||||
>>> d['2']
|
||||
'two'
|
||||
>>> d[4]
|
||||
'four'
|
||||
>>> d[1]
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: '1'
|
||||
|
||||
Tests for the `in` operator::
|
||||
|
||||
>>> 2 in d
|
||||
True
|
||||
>>> 1 in d
|
||||
False
|
||||
|
||||
Test for item assignment using non-string key::
|
||||
|
||||
>>> d[0] = 'zero'
|
||||
>>> d['0']
|
||||
'zero'
|
||||
|
||||
Tests for update using a `dict` or a sequence of pairs::
|
||||
|
||||
>>> d.update({6:'six', '8':'eight'})
|
||||
>>> sorted(d.keys())
|
||||
['0', '2', '4', '6', '8']
|
||||
>>> d.update([(10, 'ten'), ('12', 'twelve')])
|
||||
>>> sorted(d.keys())
|
||||
['0', '10', '12', '2', '4', '6', '8']
|
||||
>>> d.update([1, 3, 5])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: 'int' object is not iterable
|
||||
|
||||
"""
|
||||
# BEGIN STRKEYDICT
|
||||
|
||||
import collections
|
||||
import collections.abc
|
||||
|
||||
|
||||
class StrKeyDict(collections.UserDict): # <1>
|
||||
|
||||
def __missing__(self, key): # <2>
|
||||
if isinstance(key, str):
|
||||
raise KeyError(key)
|
||||
return self[str(key)]
|
||||
|
||||
def __contains__(self, key):
|
||||
return str(key) in self.data # <3>
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
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
|
||||
39
dicts/strkeydict0.py
Normal file
39
dicts/strkeydict0.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""StrKeyDict0 converts non-string keys to `str` on lookup
|
||||
|
||||
# BEGIN STRKEYDICT0_TESTS
|
||||
|
||||
Tests for item retrieval using `d[key]` notation::
|
||||
|
||||
>>> d = StrKeyDict0([('2', 'two'), ('4', 'four')])
|
||||
>>> d['2']
|
||||
'two'
|
||||
>>> d[4]
|
||||
'four'
|
||||
>>> d[1]
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: '1'
|
||||
|
||||
Tests for the `in` operator::
|
||||
|
||||
>>> 2 in d
|
||||
True
|
||||
>>> 1 in d
|
||||
False
|
||||
|
||||
# END STRKEYDICT0_TESTS
|
||||
"""
|
||||
|
||||
# BEGIN STRKEYDICT0
|
||||
|
||||
class StrKeyDict0(dict): # <1>
|
||||
|
||||
def __missing__(self, key):
|
||||
if isinstance(key, str): # <2>
|
||||
raise KeyError(key)
|
||||
return self[str(key)] # <3>
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.keys() or str(key) in self.keys() # <4>
|
||||
|
||||
# END STRKEYDICT0
|
||||
25
functions/attrgetter_demo.py
Normal file
25
functions/attrgetter_demo.py
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
metro_data = [
|
||||
('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
|
||||
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
|
||||
('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
|
||||
('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
|
||||
('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
|
||||
]
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
LatLong = namedtuple('LatLong', 'lat long')
|
||||
Metropolis = namedtuple('Metropolis', 'name cc pop coord')
|
||||
|
||||
metro_areas = [Metropolis(name, cc, pop, LatLong(lat, long_))
|
||||
for name, cc, pop, (lat, long_) in metro_data]
|
||||
|
||||
metro_areas[0]
|
||||
metro_areas[0].coord.lat
|
||||
|
||||
from operator import attrgetter
|
||||
name_lat = attrgetter('name', 'coord.lat')
|
||||
|
||||
for city in sorted(metro_areas, key=attrgetter('coord.lat')):
|
||||
print(name_lat(city))
|
||||
31
functions/attrgetter_demo.rst
Normal file
31
functions/attrgetter_demo.rst
Normal file
@@ -0,0 +1,31 @@
|
||||
>>> metro_data = [
|
||||
... ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
|
||||
... ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
|
||||
... ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
|
||||
... ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
|
||||
... ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
|
||||
... ]
|
||||
# BEGIN ATTRGETTER_DEMO
|
||||
|
||||
>>> from collections import namedtuple
|
||||
>>> LatLong = namedtuple('LatLong', 'lat long') # <1>
|
||||
>>> Metropolis = namedtuple('Metropolis', 'name cc pop coord') # <2>
|
||||
>>> metro_areas = [Metropolis(name, cc, pop, LatLong(lat, long_)) # <3>
|
||||
... for name, cc, pop, (lat, long_) in metro_data]
|
||||
>>> metro_areas[0]
|
||||
Metropolis(name='Tokyo', cc='JP', pop=36.933, coord=LatLong(lat=35.689722, long=139.691667))
|
||||
>>> metro_areas[0].coord.lat # <4>
|
||||
35.689722
|
||||
>>> from operator import attrgetter
|
||||
>>> name_lat = attrgetter('name', 'coord.lat') # <5>
|
||||
>>>
|
||||
>>> for city in sorted(metro_areas, key=attrgetter('coord.lat')): # <6>
|
||||
... print(name_lat(city)) # <7>
|
||||
...
|
||||
('Sao Paulo', -23.547778)
|
||||
('Mexico City', 19.433333)
|
||||
('Delhi NCR', 28.613889)
|
||||
('Tokyo', 35.689722)
|
||||
('New York-Newark', 40.808611)
|
||||
|
||||
# END ATTRGETTER_DEMO
|
||||
30
functions/bingo.py
Normal file
30
functions/bingo.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
# BEGIN BINGO_DEMO
|
||||
|
||||
>>> bingo = BingoCage(range(3))
|
||||
>>> bingo()
|
||||
2
|
||||
>>> bingo()
|
||||
0
|
||||
>>> callable(bingo)
|
||||
True
|
||||
# END BINGO_DEMO
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN BINGO
|
||||
|
||||
import random
|
||||
|
||||
class BingoCage:
|
||||
|
||||
def __init__(self, items):
|
||||
self._items = list(items) # <1>
|
||||
random.shuffle(self._items) # <2>
|
||||
|
||||
def __call__(self):
|
||||
if not self._items: # <3>
|
||||
raise IndexError('pop from empty BingoCage')
|
||||
return self._items.pop()
|
||||
|
||||
# END BINGO
|
||||
38
functions/clip.py
Normal file
38
functions/clip.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
>>> clip('banana ', 6)
|
||||
'banana'
|
||||
>>> clip('banana ', 7)
|
||||
'banana'
|
||||
>>> clip('banana ', 5)
|
||||
'banana'
|
||||
>>> clip('banana split', 6)
|
||||
'banana'
|
||||
>>> clip('banana split', 7)
|
||||
'banana'
|
||||
>>> clip('banana split', 10)
|
||||
'banana'
|
||||
>>> clip('banana split', 11)
|
||||
'banana'
|
||||
>>> clip('banana split', 12)
|
||||
'banana split'
|
||||
"""
|
||||
|
||||
# BEGIN CLIP
|
||||
|
||||
def clip(text, max_len=80):
|
||||
"""Return text clipped at the last space before or after max_len
|
||||
"""
|
||||
end = None
|
||||
if len(text) > max_len:
|
||||
space_before = text.rfind(' ', 0, max_len)
|
||||
if space_before >= 0:
|
||||
end = space_before
|
||||
else:
|
||||
space_after = text.rfind(' ', max_len)
|
||||
if space_after >= 0:
|
||||
end = space_after
|
||||
if end is None: # no spaces were found
|
||||
end = len(text)
|
||||
return text[:end].rstrip()
|
||||
|
||||
# END CLIP
|
||||
38
functions/clip_annot.py
Normal file
38
functions/clip_annot.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
>>> clip('banana ', 6)
|
||||
'banana'
|
||||
>>> clip('banana ', 7)
|
||||
'banana'
|
||||
>>> clip('banana ', 5)
|
||||
'banana'
|
||||
>>> clip('banana split', 6)
|
||||
'banana'
|
||||
>>> clip('banana split', 7)
|
||||
'banana'
|
||||
>>> clip('banana split', 10)
|
||||
'banana'
|
||||
>>> clip('banana split', 11)
|
||||
'banana'
|
||||
>>> clip('banana split', 12)
|
||||
'banana split'
|
||||
"""
|
||||
|
||||
# BEGIN CLIP_ANNOT
|
||||
|
||||
def clip(text:str, max_len:'int > 0'=80) -> str:
|
||||
"""Return text clipped at the last space before or after max_len
|
||||
"""
|
||||
end = None
|
||||
if len(text) > max_len:
|
||||
space_before = text.rfind(' ', 0, max_len)
|
||||
if space_before >= 0:
|
||||
end = space_before
|
||||
else:
|
||||
space_after = text.rfind(' ', max_len)
|
||||
if space_after >= 0:
|
||||
end = space_after
|
||||
if end is None: # no spaces were found
|
||||
end = len(text)
|
||||
return text[:end].rstrip()
|
||||
|
||||
# END CLIP_ANNOT
|
||||
10
functions/clip_annot_signature.rst
Normal file
10
functions/clip_annot_signature.rst
Normal file
@@ -0,0 +1,10 @@
|
||||
>>> from clip_annot import clip
|
||||
>>> from inspect import signature
|
||||
>>> sig = signature(clip)
|
||||
>>> sig.return_annotation
|
||||
<class 'str'>
|
||||
>>> for param in sig.parameters.values():
|
||||
... note = repr(param.annotation).ljust(13)
|
||||
... print(note, ':', param.name, '=', param.default)
|
||||
<class 'str'> : text = <class 'inspect._empty'>
|
||||
'int > 0' : max_len = 80
|
||||
9
functions/clip_introspection.rst
Normal file
9
functions/clip_introspection.rst
Normal file
@@ -0,0 +1,9 @@
|
||||
>>> from clip import clip
|
||||
>>> clip.__defaults__
|
||||
(80,)
|
||||
>>> clip.__code__ # doctest: +ELLIPSIS
|
||||
<code object clip at 0x...>
|
||||
>>> clip.__code__.co_varnames
|
||||
('text', 'max_len', 'end', 'space_before', 'space_after')
|
||||
>>> clip.__code__.co_argcount
|
||||
2
|
||||
12
functions/clip_signature.rst
Normal file
12
functions/clip_signature.rst
Normal file
@@ -0,0 +1,12 @@
|
||||
>>> from clip import clip
|
||||
>>> from inspect import signature
|
||||
>>> sig = signature(clip)
|
||||
>>> sig # doctest: +ELLIPSIS
|
||||
<inspect.Signature object at 0x...>
|
||||
>>> str(sig)
|
||||
'(text, max_len=80)'
|
||||
>>> for name, param in sig.parameters.items():
|
||||
... print(param.kind, ':', name, '=', param.default)
|
||||
...
|
||||
POSITIONAL_OR_KEYWORD : text = <class 'inspect._empty'>
|
||||
POSITIONAL_OR_KEYWORD : max_len = 80
|
||||
5
functions/hello.py
Normal file
5
functions/hello.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import bobo
|
||||
|
||||
@bobo.query('/')
|
||||
def hello(person):
|
||||
return 'Hello %s!' % person
|
||||
76
functions/strkeydict2.py
Normal file
76
functions/strkeydict2.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""StrKeyDict always converts non-string keys to `str`
|
||||
|
||||
Tests for item retrieval using `d[key]` notation::
|
||||
|
||||
>>> d = StrKeyDict([('2', 'two'), ('4', 'four')])
|
||||
>>> d['2']
|
||||
'two'
|
||||
>>> d[4]
|
||||
'four'
|
||||
>>> d[1]
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: '1'
|
||||
|
||||
Tests for the `in` operator::
|
||||
|
||||
>>> 2 in d
|
||||
True
|
||||
>>> 1 in d
|
||||
False
|
||||
|
||||
Test for item assignment using non-string key::
|
||||
|
||||
>>> d[0] = 'zero'
|
||||
>>> d['0']
|
||||
'zero'
|
||||
|
||||
Tests for update using a `dict` or a sequence of pairs::
|
||||
|
||||
>>> d.update({6:'six', '8':'eight'})
|
||||
>>> sorted(d.keys())
|
||||
['0', '2', '4', '6', '8']
|
||||
>>> d.update([(10, 'ten'), ('12', 'twelve')])
|
||||
>>> sorted(d.keys())
|
||||
['0', '10', '12', '2', '4', '6', '8']
|
||||
>>> d.update([1, 3, 5])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: 'int' object is not iterable
|
||||
|
||||
"""
|
||||
# BEGIN STRKEYDICT
|
||||
|
||||
import collections
|
||||
import collections.abc
|
||||
|
||||
|
||||
class StrKeyDict(collections.UserDict): # <1>
|
||||
|
||||
def __init__(self, args, normalize=str, **kwargs):
|
||||
super().__init__(self, *args, **kwargs)
|
||||
self.normalize = normalize
|
||||
|
||||
def __missing__(self, key): # <2>
|
||||
if self.normalize(key) == key:
|
||||
raise KeyError(key)
|
||||
return self[self.normalize(key)]
|
||||
|
||||
def __contains__(self, key):
|
||||
return self.normalize(key) in self.data # <3>
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
self.data[self.normalize(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
|
||||
44
functions/tagger.py
Normal file
44
functions/tagger.py
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
|
||||
"""
|
||||
# BEGIN TAG_DEMO
|
||||
>>> tag('br') # <1>
|
||||
'<br />'
|
||||
>>> tag('p', 'hello') # <2>
|
||||
'<p>hello</p>'
|
||||
>>> print(tag('p', 'hello', 'world'))
|
||||
<p>hello</p>
|
||||
<p>world</p>
|
||||
>>> tag('p', 'hello', id=33) # <3>
|
||||
'<p id="33">hello</p>'
|
||||
>>> print(tag('p', 'hello', 'world', cls='sidebar')) # <4>
|
||||
<p class="sidebar">hello</p>
|
||||
<p class="sidebar">world</p>
|
||||
>>> tag(content='testing', name="img") # <5>
|
||||
'<img content="testing" />'
|
||||
>>> my_tag = {'name': 'img', 'title': 'Sunset Boulevard',
|
||||
... 'src': 'sunset.jpg', 'cls': 'framed'}
|
||||
>>> tag(**my_tag) # <6>
|
||||
'<img class="framed" src="sunset.jpg" title="Sunset Boulevard" />'
|
||||
|
||||
# END TAG_DEMO
|
||||
"""
|
||||
|
||||
|
||||
# BEGIN TAG_FUNC
|
||||
def tag(name, *content, cls=None, **attrs):
|
||||
"""Generate one or more HTML tags"""
|
||||
if cls is not None:
|
||||
attrs['class'] = cls
|
||||
if attrs:
|
||||
attr_str = ''.join(' %s="%s"' % (attr, value)
|
||||
for attr, value
|
||||
in sorted(attrs.items()))
|
||||
else:
|
||||
attr_str = ''
|
||||
if content:
|
||||
return '\n'.join('<%s%s>%s</%s>' %
|
||||
(name, attr_str, c, name) for c in content)
|
||||
else:
|
||||
return '<%s%s />' % (name, attr_str)
|
||||
# END TAG_FUNC
|
||||
19
interfaces/bingo.py
Normal file
19
interfaces/bingo.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import random
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
|
||||
class BingoCage(Tombola): # <1>
|
||||
|
||||
def __init__(self, items):
|
||||
self._balls = list(items) # <2>
|
||||
|
||||
def load(self, items):
|
||||
self._balls.extend(items)
|
||||
|
||||
def pop(self):
|
||||
try:
|
||||
position = random.randrange(len(self._balls)) # <4>
|
||||
except ValueError:
|
||||
raise LookupError('pop from empty BingoCage')
|
||||
return self._balls.pop(position)
|
||||
17
interfaces/drum.py
Normal file
17
interfaces/drum.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from random import shuffle
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
|
||||
class TumblingDrum(Tombola):
|
||||
|
||||
def __init__(self, iterable):
|
||||
self._balls = []
|
||||
self.load(iterable)
|
||||
|
||||
def load(self, iterable):
|
||||
self._balls.extend(iterable)
|
||||
shuffle(self._balls)
|
||||
|
||||
def pop(self):
|
||||
return self._balls.pop()
|
||||
61
interfaces/exceptions-tree.txt
Normal file
61
interfaces/exceptions-tree.txt
Normal file
@@ -0,0 +1,61 @@
|
||||
BaseException
|
||||
├── SystemExit
|
||||
├── KeyboardInterrupt
|
||||
├── GeneratorExit
|
||||
└── Exception
|
||||
├── StopIteration
|
||||
├── ArithmeticError
|
||||
│ ├── FloatingPointError
|
||||
│ ├── OverflowError
|
||||
│ └── ZeroDivisionError
|
||||
├── AssertionError
|
||||
├── AttributeError
|
||||
├── BufferError
|
||||
├── EOFError
|
||||
├── ImportError
|
||||
├── LookupError
|
||||
│ ├── IndexError
|
||||
│ └── KeyError
|
||||
├── MemoryError
|
||||
├── NameError
|
||||
│ └── UnboundLocalError
|
||||
├── OSError
|
||||
│ ├── BlockingIOError
|
||||
│ ├── ChildProcessError
|
||||
│ ├── ConnectionError
|
||||
│ │ ├── BrokenPipeError
|
||||
│ │ ├── ConnectionAbortedError
|
||||
│ │ ├── ConnectionRefusedError
|
||||
│ │ └── ConnectionResetError
|
||||
│ ├── FileExistsError
|
||||
│ ├── FileNotFoundError
|
||||
│ ├── InterruptedError
|
||||
│ ├── IsADirectoryError
|
||||
│ ├── NotADirectoryError
|
||||
│ ├── PermissionError
|
||||
│ ├── ProcessLookupError
|
||||
│ └── TimeoutError
|
||||
├── ReferenceError
|
||||
├── RuntimeError
|
||||
│ └── NotImplementedError
|
||||
├── SyntaxError
|
||||
│ └── IndentationError
|
||||
│ └── TabError
|
||||
├── SystemError
|
||||
├── TypeError
|
||||
├── ValueError
|
||||
│ └── UnicodeError
|
||||
│ ├── UnicodeDecodeError
|
||||
│ ├── UnicodeEncodeError
|
||||
│ └── UnicodeTranslateError
|
||||
└── Warning
|
||||
├── DeprecationWarning
|
||||
├── PendingDeprecationWarning
|
||||
├── RuntimeWarning
|
||||
├── SyntaxWarning
|
||||
├── UserWarning
|
||||
├── FutureWarning
|
||||
├── ImportWarning
|
||||
├── UnicodeWarning
|
||||
├── BytesWarning
|
||||
└── ResourceWarning
|
||||
24
interfaces/lotto.py
Normal file
24
interfaces/lotto.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import random
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
|
||||
class LotteryBlower(Tombola):
|
||||
|
||||
def __init__(self, iterable):
|
||||
self.randomizer = random.SystemRandom() # <1>
|
||||
self.clear()
|
||||
self.load(iterable)
|
||||
|
||||
def clear(self):
|
||||
self._balls = []
|
||||
|
||||
def load(self, iterable):
|
||||
self._balls.extend(iterable)
|
||||
self.randomizer.shuffle(self._balls) # <2>
|
||||
|
||||
def pop(self):
|
||||
return self._balls.pop() # <3>
|
||||
|
||||
def loaded(self): # <4>
|
||||
return len(self._balls) > 0
|
||||
25
interfaces/tombola.py
Normal file
25
interfaces/tombola.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
|
||||
class Tombola(metaclass=ABCMeta): # <1>
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, iterable): # <2>
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def load(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def pop(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def loaded(self): # <3>
|
||||
try:
|
||||
item = self.pop()
|
||||
except LookupError:
|
||||
return False
|
||||
else:
|
||||
self.load([item]) # put it back
|
||||
return True
|
||||
52
interfaces/tombola_runner.py
Normal file
52
interfaces/tombola_runner.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import sys
|
||||
import importlib
|
||||
import doctest
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
TESTS = 'tombola_tests.rst'
|
||||
|
||||
MODULES = 'bingo lotto tombolist drum'.split()
|
||||
|
||||
|
||||
def test_module(module_name, verbose=False):
|
||||
|
||||
module = importlib.import_module(module_name)
|
||||
|
||||
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,
|
||||
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)
|
||||
print('{:10} {}'.format(module_name, res))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
args = sys.argv[:]
|
||||
if '-v' in args:
|
||||
args.remove('-v')
|
||||
verbose = True
|
||||
else:
|
||||
verbose = False
|
||||
|
||||
if len(args) == 2:
|
||||
module_names = [args[1]]
|
||||
else:
|
||||
module_names = MODULES
|
||||
|
||||
for name in module_names:
|
||||
print('*' * 60, name)
|
||||
test_module(name, verbose)
|
||||
70
interfaces/tombola_tests.rst
Normal file
70
interfaces/tombola_tests.rst
Normal file
@@ -0,0 +1,70 @@
|
||||
==============
|
||||
Tombola tests
|
||||
==============
|
||||
|
||||
Every concrete subclass of Tombola should pass these tests.
|
||||
|
||||
|
||||
Create and load instance from iterable::
|
||||
|
||||
>>> balls = list(range(3))
|
||||
>>> globe = TombolaUnderTest(balls)
|
||||
>>> globe.loaded()
|
||||
True
|
||||
|
||||
|
||||
Pop and collect balls::
|
||||
|
||||
>>> picks = []
|
||||
>>> picks.append(globe.pop())
|
||||
>>> picks.append(globe.pop())
|
||||
>>> picks.append(globe.pop())
|
||||
|
||||
|
||||
Check state and results::
|
||||
|
||||
>>> globe.loaded()
|
||||
False
|
||||
>>> sorted(picks) == balls
|
||||
True
|
||||
|
||||
|
||||
Reload::
|
||||
|
||||
>>> globe.load(balls)
|
||||
>>> globe.loaded()
|
||||
True
|
||||
>>> picks = [globe.pop() for i in balls]
|
||||
>>> globe.loaded()
|
||||
False
|
||||
|
||||
|
||||
Load and pop 20 balls to verify that the order has changed::
|
||||
|
||||
>>> 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([])
|
||||
>>> try:
|
||||
... globe.pop()
|
||||
... except LookupError as exc:
|
||||
... print('OK')
|
||||
OK
|
||||
|
||||
19
interfaces/tombolist.py
Normal file
19
interfaces/tombolist.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from random import randrange
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
|
||||
class TomboList(list): # <1>
|
||||
|
||||
def pop(self):
|
||||
if self: # <2>
|
||||
return super().pop(randrange(len(self))) # <3>
|
||||
else:
|
||||
raise LookupError('pop from empty TomboList')
|
||||
|
||||
def load(self, iterable): self.extend(iterable) # <4>
|
||||
|
||||
def loaded(self): return bool(self) # <5>
|
||||
|
||||
|
||||
Tombola.register(TomboList) # <6>
|
||||
5
iterables/CACM/citation.txt
Normal file
5
iterables/CACM/citation.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
Examples in this directory were adapted from:
|
||||
|
||||
Erik Meijer. 2014. The curse of the excluded middle.
|
||||
Commun. ACM 57, 6 (June 2014), 50-55. DOI=10.1145/2605176
|
||||
http://doi.acm.org/10.1145/2605176
|
||||
10
iterables/CACM/closed_file.py
Normal file
10
iterables/CACM/closed_file.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
Erik Meijer. 2014. The curse of the excluded middle.
|
||||
Commun. ACM 57, 6 (June 2014), 50-55. DOI=10.1145/2605176
|
||||
http://doi.acm.org/10.1145/2605176
|
||||
"""
|
||||
|
||||
with open('citation.txt', encoding='ascii') as fp:
|
||||
get_contents = lambda: fp.read()
|
||||
|
||||
print(get_contents())
|
||||
17
iterables/CACM/haha.py
Normal file
17
iterables/CACM/haha.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
Erik Meijer. 2014. The curse of the excluded middle.
|
||||
Commun. ACM 57, 6 (June 2014), 50-55. DOI=10.1145/2605176
|
||||
http://doi.acm.org/10.1145/2605176
|
||||
"""
|
||||
|
||||
def ha():
|
||||
ha = 'Ha'
|
||||
print(ha)
|
||||
return ha
|
||||
|
||||
# prints 'Ha' twice before showing result
|
||||
print('result ->', ha() + ha())
|
||||
|
||||
# prints 'Ha' only once before showing reult
|
||||
ha_res = ha() # common subexpression elimination
|
||||
print('result ->', ha_res + ha_res)
|
||||
22
iterables/CACM/less_more.py
Normal file
22
iterables/CACM/less_more.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
Meijer, Erik - The Curse of the Excluded Middle
|
||||
DOI:10.1145/2605176
|
||||
CACM vol.57 no.06
|
||||
"""
|
||||
|
||||
def less_than_30(n):
|
||||
check = n < 30
|
||||
print('%d < 30 : %s' % (n, check))
|
||||
return check
|
||||
|
||||
def more_than_20(n):
|
||||
check = n > 20
|
||||
print('%d > 20 : %s' % (n, check))
|
||||
return check
|
||||
|
||||
l = [1, 25, 40, 5, 23]
|
||||
q0 = (n for n in l if less_than_30(n))
|
||||
q1 = (n for n in q0 if more_than_20(n))
|
||||
|
||||
for n in q1:
|
||||
print('-> %d' % n)
|
||||
14
iterables/CACM/zero_div.py
Normal file
14
iterables/CACM/zero_div.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
Erik Meijer. 2014. The curse of the excluded middle.
|
||||
Commun. ACM 57, 6 (June 2014), 50-55. DOI=10.1145/2605176
|
||||
http://doi.acm.org/10.1145/2605176
|
||||
"""
|
||||
|
||||
l = range(10, -1, -1)
|
||||
try:
|
||||
res = (1/x for x in l)
|
||||
except ZeroDivisionError:
|
||||
res = []
|
||||
|
||||
for z in res:
|
||||
print(z)
|
||||
40
iterables/sentence_gen.doctest
Normal file
40
iterables/sentence_gen.doctest
Normal file
@@ -0,0 +1,40 @@
|
||||
>>> from sentence_gen import Sentence
|
||||
>>> s = Sentence('The time has come')
|
||||
>>> s
|
||||
Sentence('The time has come')
|
||||
>>> list(s)
|
||||
['The', 'time', 'has', 'come']
|
||||
>>> it = iter(s)
|
||||
>>> next(it)
|
||||
'The'
|
||||
>>> next(it)
|
||||
'time'
|
||||
>>> next(it)
|
||||
'has'
|
||||
>>> next(it)
|
||||
'come'
|
||||
>>> next(it)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
StopIteration
|
||||
|
||||
>>> s = Sentence('"The time has come," the Walrus said,')
|
||||
>>> s
|
||||
Sentence('"The time ha... Walrus said,')
|
||||
>>> list(s)
|
||||
['The', 'time', 'has', 'come', 'the', 'Walrus', 'said']
|
||||
|
||||
|
||||
>>> s = Sentence('''"The time has come," the Walrus said,
|
||||
... "To talk of many things:"''')
|
||||
>>> s
|
||||
Sentence('"The time ha...many things:"')
|
||||
>>> list(s)
|
||||
['The', 'time', 'has', 'come', 'the', 'Walrus', 'said', 'To', 'talk', 'of', 'many', 'things']
|
||||
|
||||
|
||||
>>> s = Sentence('Agora vou-me. Ou me vão?')
|
||||
>>> s
|
||||
Sentence('Agora vou-me. Ou me vão?')
|
||||
>>> list(s)
|
||||
['Agora', 'vou', 'me', 'Ou', 'me', 'vão']
|
||||
23
iterables/sentence_gen.py
Normal file
23
iterables/sentence_gen.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Sentence: iterate over words using a generator function
|
||||
"""
|
||||
|
||||
import re
|
||||
import reprlib
|
||||
|
||||
RE_WORD = re.compile('\w+')
|
||||
|
||||
|
||||
class Sentence:
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
|
||||
def __repr__(self):
|
||||
return 'Sentence(%s)' % reprlib.repr(self.text)
|
||||
|
||||
def __iter__(self):
|
||||
for match in RE_WORD.finditer(self.text): # <1>
|
||||
yield match.group() # <2>
|
||||
|
||||
# done! <3>
|
||||
40
iterables/sentence_genexp.doctest
Normal file
40
iterables/sentence_genexp.doctest
Normal file
@@ -0,0 +1,40 @@
|
||||
>>> from sentence_genexp import Sentence
|
||||
>>> s = Sentence('The time has come')
|
||||
>>> s
|
||||
Sentence('The time has come')
|
||||
>>> list(s)
|
||||
['The', 'time', 'has', 'come']
|
||||
>>> it = iter(s)
|
||||
>>> next(it)
|
||||
'The'
|
||||
>>> next(it)
|
||||
'time'
|
||||
>>> next(it)
|
||||
'has'
|
||||
>>> next(it)
|
||||
'come'
|
||||
>>> next(it)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
StopIteration
|
||||
|
||||
>>> s = Sentence('"The time has come," the Walrus said,')
|
||||
>>> s
|
||||
Sentence('"The time ha... Walrus said,')
|
||||
>>> list(s)
|
||||
['The', 'time', 'has', 'come', 'the', 'Walrus', 'said']
|
||||
|
||||
|
||||
>>> s = Sentence('''"The time has come," the Walrus said,
|
||||
... "To talk of many things:"''')
|
||||
>>> s
|
||||
Sentence('"The time ha...many things:"')
|
||||
>>> list(s)
|
||||
['The', 'time', 'has', 'come', 'the', 'Walrus', 'said', 'To', 'talk', 'of', 'many', 'things']
|
||||
|
||||
|
||||
>>> s = Sentence('Agora vou-me. Ou me vão?')
|
||||
>>> s
|
||||
Sentence('Agora vou-me. Ou me vão?')
|
||||
>>> list(s)
|
||||
['Agora', 'vou', 'me', 'Ou', 'me', 'vão']
|
||||
44
iterables/sentence_genexp.py
Normal file
44
iterables/sentence_genexp.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
Sentence: iterate over words using a generator expression
|
||||
"""
|
||||
|
||||
# BEGIN SENTENCE_GENEXP
|
||||
import re
|
||||
import reprlib
|
||||
|
||||
RE_WORD = re.compile('\w+')
|
||||
|
||||
|
||||
class Sentence:
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
|
||||
def __repr__(self):
|
||||
return 'Sentence(%s)' % reprlib.repr(self.text)
|
||||
|
||||
def __iter__(self):
|
||||
return (match.group() for match in RE_WORD.finditer(self.text))
|
||||
# END SENTENCE_GENEXP
|
||||
|
||||
|
||||
def main():
|
||||
import sys
|
||||
import warnings
|
||||
try:
|
||||
filename = sys.argv[1]
|
||||
word_number = int(sys.argv[2])
|
||||
except (IndexError, ValueError):
|
||||
print('Usage: %s <file-name> <word-number>' % sys.argv[0])
|
||||
sys.exit(1)
|
||||
with open(filename, 'rt', encoding='utf-8') as text_file:
|
||||
s = Sentence(text_file.read())
|
||||
for n, word in enumerate(s, 1):
|
||||
if n == word_number:
|
||||
print(word)
|
||||
break
|
||||
else:
|
||||
warnings.warn('last word is #%d, "%s"' % (n, word))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
40
iterables/sentence_iter.doctest
Normal file
40
iterables/sentence_iter.doctest
Normal file
@@ -0,0 +1,40 @@
|
||||
>>> from sentence_iter import Sentence
|
||||
>>> s = Sentence('The time has come')
|
||||
>>> s
|
||||
Sentence('The time has come')
|
||||
>>> list(s)
|
||||
['The', 'time', 'has', 'come']
|
||||
>>> it = iter(s)
|
||||
>>> next(it)
|
||||
'The'
|
||||
>>> next(it)
|
||||
'time'
|
||||
>>> next(it)
|
||||
'has'
|
||||
>>> next(it)
|
||||
'come'
|
||||
>>> next(it)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
StopIteration
|
||||
|
||||
>>> s = Sentence('"The time has come," the Walrus said,')
|
||||
>>> s
|
||||
Sentence('"The time ha... Walrus said,')
|
||||
>>> list(s)
|
||||
['The', 'time', 'has', 'come', 'the', 'Walrus', 'said']
|
||||
|
||||
|
||||
>>> s = Sentence('''"The time has come," the Walrus said,
|
||||
... "To talk of many things:"''')
|
||||
>>> s
|
||||
Sentence('"The time ha...many things:"')
|
||||
>>> list(s)
|
||||
['The', 'time', 'has', 'come', 'the', 'Walrus', 'said', 'To', 'talk', 'of', 'many', 'things']
|
||||
|
||||
|
||||
>>> s = Sentence('Agora vou-me. Ou me vão?')
|
||||
>>> s
|
||||
Sentence('Agora vou-me. Ou me vão?')
|
||||
>>> list(s)
|
||||
['Agora', 'vou', 'me', 'Ou', 'me', 'vão']
|
||||
65
iterables/sentence_iter.py
Normal file
65
iterables/sentence_iter.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""
|
||||
Sentence: iterate over words using the Iterator Pattern, take #1
|
||||
|
||||
WARNING: the Iterator Pattern is much simpler in idiomatic Python;
|
||||
see: sentence_gen*.py.
|
||||
"""
|
||||
|
||||
# BEGIN SENTENCE_ITER
|
||||
import re
|
||||
import reprlib
|
||||
|
||||
RE_WORD = re.compile('\w+')
|
||||
|
||||
|
||||
class Sentence:
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
self.words = RE_WORD.findall(text)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Sentence(%s)' % reprlib.repr(self.text)
|
||||
|
||||
def __iter__(self): # <1>
|
||||
return SentenceIter(self.words) # <2>
|
||||
|
||||
|
||||
class SentenceIter:
|
||||
|
||||
def __init__(self, words):
|
||||
self.words = words # <3>
|
||||
self.index = 0 # <4>
|
||||
|
||||
def __next__(self):
|
||||
try:
|
||||
word = self.words[self.index] # <5>
|
||||
except IndexError:
|
||||
raise StopIteration() # <6>
|
||||
self.index += 1 # <7>
|
||||
return word # <8>
|
||||
|
||||
def __iter__(self): # <9>
|
||||
return self
|
||||
# END SENTENCE_ITER
|
||||
|
||||
def main():
|
||||
import sys
|
||||
import warnings
|
||||
try:
|
||||
filename = sys.argv[1]
|
||||
word_number = int(sys.argv[2])
|
||||
except (IndexError, ValueError):
|
||||
print('Usage: %s <file-name> <word-number>' % sys.argv[0])
|
||||
sys.exit(1)
|
||||
with open(filename, 'rt', encoding='utf-8') as text_file:
|
||||
s = Sentence(text_file.read())
|
||||
for n, word in enumerate(s, 1):
|
||||
if n == word_number:
|
||||
print(word)
|
||||
break
|
||||
else:
|
||||
warnings.warn('last word is #%d, "%s"' % (n, word))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
40
iterables/sentence_iter2.doctest
Normal file
40
iterables/sentence_iter2.doctest
Normal file
@@ -0,0 +1,40 @@
|
||||
>>> from sentence_iter2 import Sentence
|
||||
>>> s = Sentence('The time has come')
|
||||
>>> s
|
||||
Sentence('The time has come')
|
||||
>>> list(s)
|
||||
['The', 'time', 'has', 'come']
|
||||
>>> it = iter(s)
|
||||
>>> next(it)
|
||||
'The'
|
||||
>>> next(it)
|
||||
'time'
|
||||
>>> next(it)
|
||||
'has'
|
||||
>>> next(it)
|
||||
'come'
|
||||
>>> next(it)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
StopIteration
|
||||
|
||||
>>> s = Sentence('"The time has come," the Walrus said,')
|
||||
>>> s
|
||||
Sentence('"The time ha... Walrus said,')
|
||||
>>> list(s)
|
||||
['The', 'time', 'has', 'come', 'the', 'Walrus', 'said']
|
||||
|
||||
|
||||
>>> s = Sentence('''"The time has come," the Walrus said,
|
||||
... "To talk of many things:"''')
|
||||
>>> s
|
||||
Sentence('"The time ha...many things:"')
|
||||
>>> list(s)
|
||||
['The', 'time', 'has', 'come', 'the', 'Walrus', 'said', 'To', 'talk', 'of', 'many', 'things']
|
||||
|
||||
|
||||
>>> s = Sentence('Agora vou-me. Ou me vão?')
|
||||
>>> s
|
||||
Sentence('Agora vou-me. Ou me vão?')
|
||||
>>> list(s)
|
||||
['Agora', 'vou', 'me', 'Ou', 'me', 'vão']
|
||||
37
iterables/sentence_iter2.py
Normal file
37
iterables/sentence_iter2.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
Sentence: iterate over words using the Iterator Pattern, take #2
|
||||
|
||||
WARNING: the Iterator Pattern is much simpler in idiomatic Python;
|
||||
see: sentence_gen*.py.
|
||||
"""
|
||||
|
||||
import re
|
||||
import reprlib
|
||||
|
||||
RE_WORD = re.compile('\w+')
|
||||
|
||||
|
||||
class Sentence:
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
|
||||
def __repr__(self):
|
||||
return 'Sentence(%s)' % reprlib.repr(self.text)
|
||||
|
||||
def __iter__(self):
|
||||
word_iter = RE_WORD.finditer(self.text) # <1>
|
||||
return SentenceIter(word_iter) # <2>
|
||||
|
||||
|
||||
class SentenceIter():
|
||||
|
||||
def __init__(self, word_iter):
|
||||
self.word_iter = word_iter # <3>
|
||||
|
||||
def __next__(self):
|
||||
match = next(self.word_iter) # <4>
|
||||
return match.group() # <5>
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
42
iterables/simplest_generators.doctest
Normal file
42
iterables/simplest_generators.doctest
Normal file
@@ -0,0 +1,42 @@
|
||||
>>> def gen_123():
|
||||
... yield 1
|
||||
... yield 2
|
||||
... yield 3
|
||||
...
|
||||
>>> gen_123 # doctest: +ELLIPSIS
|
||||
<function gen_123 at 0x...>
|
||||
>>> gen_123() # doctest: +ELLIPSIS
|
||||
<generator object gen_123 at 0x...>
|
||||
>>> for i in gen_123():
|
||||
... print(i)
|
||||
1
|
||||
2
|
||||
3
|
||||
>>> g = gen_123()
|
||||
>>> next(g)
|
||||
1
|
||||
>>> next(g)
|
||||
2
|
||||
>>> next(g)
|
||||
3
|
||||
>>> next(g)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
StopIteration
|
||||
|
||||
|
||||
>>> def gen_AB():
|
||||
... print('start')
|
||||
... yield 'A'
|
||||
... print('continue')
|
||||
... yield 'B'
|
||||
... print('end.')
|
||||
...
|
||||
>>> for c in gen_AB():
|
||||
... print('--->', c)
|
||||
...
|
||||
start
|
||||
---> A
|
||||
continue
|
||||
---> B
|
||||
end.
|
||||
103
iterables/vector.py
Normal file
103
iterables/vector.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
|
||||
The `+` operator produces a `Vector` result.
|
||||
|
||||
>>> v1 = Vector(2, 4)
|
||||
>>> v2 = Vector(2, 1)
|
||||
>>> v1 + v2
|
||||
Vector(4, 5)
|
||||
|
||||
We can also implemement the `*` operator to perform scalar multiplication
|
||||
or elementwise multiplication.
|
||||
|
||||
>>> v = Vector(3, 4)
|
||||
>>> abs(v)
|
||||
5.0
|
||||
>>> v * 3
|
||||
Vector(9, 12)
|
||||
>>> abs(v * 3)
|
||||
15.0
|
||||
>>> v25 = Vector(2, 5)
|
||||
>>> v71 = Vector(7, 1)
|
||||
>>> v71 * v25
|
||||
Vector(14, 5)
|
||||
|
||||
A vector can be used in a boolean context, where it will be considered
|
||||
_falsy_ if it has magnitude zero, otherwise it is _truthy_::
|
||||
|
||||
>>> bool(v)
|
||||
True
|
||||
>>> bool(Vector(0, 0))
|
||||
False
|
||||
|
||||
Vectors can have n-dimensions::
|
||||
|
||||
>>> v3 = Vector(1, 2, 3)
|
||||
>>> len(v3)
|
||||
3
|
||||
>>> v3
|
||||
Vector(1, 2, 3)
|
||||
>>> abs(v3) # doctest:+ELLIPSIS
|
||||
3.74165738...
|
||||
>>> v3 + Vector(4, 5, 6)
|
||||
Vector(5, 7, 9)
|
||||
>>> v3 * 5
|
||||
Vector(5, 10, 15)
|
||||
>>> v2 + v3
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Addition applies only to vectors of equal dimensions.
|
||||
|
||||
The `repr` of a Vector is produced with the help of the `reprlib.repr`
|
||||
function, limiting the size of the output string:
|
||||
|
||||
>>> Vector(*range(100))
|
||||
Vector(0, 1, 2, 3, 4, 5, ...)
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN VECTOR_ITER
|
||||
import math
|
||||
import numbers
|
||||
import reprlib
|
||||
|
||||
EQ_DIMENSIONS_MSG = '%s applies only to vectors of equal dimensions.'
|
||||
|
||||
class Vector:
|
||||
"""An n-dimensional vector"""
|
||||
|
||||
def __init__(self, *components): # <1>
|
||||
self._components = tuple(components) # <2>
|
||||
|
||||
def __repr__(self):
|
||||
return 'Vector' + (reprlib.repr(self._components)) # <3>
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._components) # <4>
|
||||
|
||||
def __abs__(self):
|
||||
return math.sqrt(sum(comp*comp for comp in self)) # <5>
|
||||
|
||||
def __len__(self):
|
||||
return len(self._components) # <6>
|
||||
|
||||
def __add__(self, other):
|
||||
if len(self) != len(other):
|
||||
raise ValueError(EQ_DIMENSIONS_MSG % 'Addition')
|
||||
return Vector(*(a+b for a, b in zip(self, other))) # <7>
|
||||
|
||||
def __mul__(self, other):
|
||||
if isinstance(other, numbers.Number):
|
||||
return Vector(*(comp*other for comp in self)) # <8>
|
||||
else:
|
||||
return self.elementwise_mul(other) # <9>
|
||||
|
||||
def elementwise_mul(self, other):
|
||||
if len(self) != len(other):
|
||||
raise ValueError(EQ_DIMENSIONS_MSG %
|
||||
'Elementwise multiplication')
|
||||
return Vector(*(a*b for a, b in zip(self, other))) # <10>
|
||||
|
||||
def __bool__(self):
|
||||
return any(self) # <11>
|
||||
# END VECTOR_ITER
|
||||
43
iterables/vector_flex_init.py
Normal file
43
iterables/vector_flex_init.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""
|
||||
A more flexible `__init__` for the Vector class
|
||||
|
||||
A Vector can be built from an iterable:
|
||||
|
||||
>>> Vector([10, 20, 30])
|
||||
Vector(10, 20, 30)
|
||||
>>> Vector(range(1, 5))
|
||||
Vector(1, 2, 3, 4)
|
||||
|
||||
Or from two more arguments:
|
||||
|
||||
>>> Vector(100, 200)
|
||||
Vector(100, 200)
|
||||
>>> Vector(1, 2, 3, 4, 5)
|
||||
Vector(1, 2, 3, 4, 5)
|
||||
|
||||
One-dimensional vectors are not supported:
|
||||
|
||||
>>> Vector(99)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Vector() takes one iterable argument or at least 2 scalar arguments
|
||||
|
||||
"""
|
||||
|
||||
import reprlib
|
||||
|
||||
|
||||
class Vector:
|
||||
"""An n-dimensional vector"""
|
||||
|
||||
def __init__(self, first, *rest): # <1>
|
||||
if rest:
|
||||
self._components = (first,) + tuple(rest) # <3>
|
||||
else:
|
||||
try:
|
||||
self._components = tuple(first) # <2>
|
||||
except TypeError:
|
||||
raise TypeError('Vector() takes one iterable argument or at least 2 scalar arguments')
|
||||
|
||||
def __repr__(self):
|
||||
return 'Vector' + reprlib.repr(self._components)
|
||||
@@ -7,4 +7,4 @@ __pycache__
|
||||
.DS_Store
|
||||
*.arr
|
||||
README.rst
|
||||
LICENCE
|
||||
LICENSE
|
||||
|
||||
61
objects/attr_list.py
Normal file
61
objects/attr_list.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import sys
|
||||
|
||||
from collections import Counter
|
||||
|
||||
def fn():
|
||||
pass
|
||||
|
||||
class Class():
|
||||
pass
|
||||
|
||||
# int, str,
|
||||
sample_types = [object, list, Class, type(Class), type(fn)]
|
||||
if '-' in sys.argv:
|
||||
del sample_types[0] # exlude `object`
|
||||
sample_objs = [type_() for type_ in sample_types[:-2]] + [Class, fn]
|
||||
sample_oids = [id(obj) for obj in sample_objs]
|
||||
|
||||
fmt = '{attr:17}' + '|{:8}' * len(sample_types)
|
||||
|
||||
headings = [t.__name__ for t in sample_types]
|
||||
headings[headings.index('Class')] = 'instance'
|
||||
headings[headings.index('type')] = 'class'
|
||||
|
||||
common_attrs = set()
|
||||
for obj in sample_objs:
|
||||
for attr_name in dir(obj):
|
||||
common_attrs.add(attr_name)
|
||||
|
||||
print(fmt.format(*headings, attr=''))
|
||||
|
||||
counter = Counter()
|
||||
for attr_name in sorted(common_attrs):
|
||||
if not attr_name.startswith('__'):
|
||||
continue
|
||||
flags = []
|
||||
found = 0
|
||||
for obj in sample_objs:
|
||||
try:
|
||||
attr = getattr(obj, attr_name)
|
||||
if type(attr) == type:
|
||||
flag = 'type'
|
||||
elif callable(attr):
|
||||
flag = 'method'
|
||||
else:
|
||||
flag = 'data'
|
||||
counter[id(obj)] += 1
|
||||
found += 1
|
||||
except AttributeError:
|
||||
flag = ''
|
||||
flags.append(flag)
|
||||
if '-' in sys.argv:
|
||||
include = found < len(sample_objs)
|
||||
else:
|
||||
include = found == len(sample_objs)
|
||||
if include:
|
||||
print(fmt.format(*flags, attr=attr_name))
|
||||
|
||||
counts = [counter[oid] for oid in sample_oids]
|
||||
print(fmt.format(*counts, attr='TOTALS'))
|
||||
|
||||
print(sys.argv)
|
||||
28
objects/attrs_not_in_object.py
Normal file
28
objects/attrs_not_in_object.py
Normal file
@@ -0,0 +1,28 @@
|
||||
|instance|list |function|class
|
||||
__add__ | |method | |
|
||||
__annotations__ | | |data |
|
||||
__call__ | | |method |method
|
||||
__closure__ | | |data |
|
||||
__code__ | | |data |
|
||||
__contains__ | |method | |
|
||||
__defaults__ | | |data |
|
||||
__delitem__ | |method | |
|
||||
__dict__ |data | |data |data
|
||||
__get__ | | |method |
|
||||
__getitem__ | |method | |
|
||||
__globals__ | | |data |
|
||||
__iadd__ | |method | |
|
||||
__imul__ | |method | |
|
||||
__iter__ | |method | |
|
||||
__kwdefaults__ | | |data |
|
||||
__len__ | |method | |
|
||||
__module__ |data | |data |data
|
||||
__mul__ | |method | |
|
||||
__name__ | | |data |data
|
||||
__qualname__ | | |data |data
|
||||
__reversed__ | |method | |
|
||||
__rmul__ | |method | |
|
||||
__setitem__ | |method | |
|
||||
__weakref__ |data | | |data
|
||||
TOTALS | 25| 34| 34| 28
|
||||
['attr_list.py', '-']
|
||||
29
objects/bus.py
Normal file
29
objects/bus.py
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
"""
|
||||
>>> import copy
|
||||
>>> bus1 = Bus(['Alice', 'Bill', 'Claire', 'David'])
|
||||
>>> bus2 = copy.copy(bus1)
|
||||
>>> bus3 = copy.deepcopy(bus1)
|
||||
>>> bus1.drop('Bill')
|
||||
>>> bus2.passengers
|
||||
['Alice', 'Claire', 'David']
|
||||
>>> bus3.passengers
|
||||
['Alice', 'Bill', 'Claire', 'David']
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN BUS_CLASS
|
||||
class Bus:
|
||||
|
||||
def __init__(self, passengers=None):
|
||||
if passengers is None:
|
||||
self.passengers = []
|
||||
else:
|
||||
self.passengers = list(passengers)
|
||||
|
||||
def pick(self, name):
|
||||
self.passengers.append(name)
|
||||
|
||||
def drop(self, name):
|
||||
self.passengers.remove(name)
|
||||
# END BUS_CLASS
|
||||
54
objects/cards.py
Normal file
54
objects/cards.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
|
||||
Spadille is the nickname for the Ace of Spades in some games
|
||||
(see `Webster 1913`_)
|
||||
|
||||
>>> beer_card = Card('7', Suite.diamonds)
|
||||
>>> beer_card
|
||||
Card('7', Suite.diamonds)
|
||||
>>> spadille = Card('A', Suite.spades, long_rank='Ace')
|
||||
>>> spadille
|
||||
Card('A', Suite.spades)
|
||||
>>> print(spadille)
|
||||
Ace of spades
|
||||
>>> bytes(spadille)
|
||||
b'A\\x01'
|
||||
>>> charles = Card('K', Suite.hearts)
|
||||
>>> bytes(charles)
|
||||
b'K\\x04'
|
||||
>>> big_cassino = Card('10', Suite.diamonds)
|
||||
>>> bytes(big_cassino)
|
||||
b'T\\x02'
|
||||
|
||||
__ http://machaut.uchicago.edu/cgi-bin/WEBSTER.sh?WORD=spadille
|
||||
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
Suite = Enum('Suite', 'spades diamonds clubs hearts')
|
||||
|
||||
class Card:
|
||||
|
||||
def __init__(self, rank, suite, *, long_rank=None):
|
||||
self.rank = rank
|
||||
if long_rank is None:
|
||||
self.long_rank = self.rank
|
||||
else:
|
||||
self.long_rank = long_rank
|
||||
self.suite = suite
|
||||
|
||||
def __str__(self):
|
||||
return '{long_rank} of {suite.name}'.format(**self.__dict__)
|
||||
|
||||
def __repr__(self):
|
||||
constructor = '{cls.__name__}({args})'
|
||||
args = '{0.rank!r}, Suite.{0.suite.name}'.format(self)
|
||||
return constructor.format(cls=self.__class__, args=args)
|
||||
|
||||
def __bytes__(self):
|
||||
if self.rank == '10':
|
||||
rank_byte = b'T'
|
||||
else:
|
||||
rank_byte = bytes([ord(self.rank)])
|
||||
return rank_byte + bytes([self.suite.value])
|
||||
122
objects/cards_format.py
Normal file
122
objects/cards_format.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""
|
||||
Test Suite formatting:
|
||||
|
||||
>>> Suite.spades
|
||||
<Suite.spades: 0>
|
||||
>>> print(Suite.spades)
|
||||
Suite.spades
|
||||
>>> format(Suite.spades)
|
||||
'spades'
|
||||
>>> format(Suite.spades, 's')
|
||||
'spades'
|
||||
>>> format(Suite.spades, 'S')
|
||||
'Spades'
|
||||
>>> format(Suite.spades, 'p')
|
||||
'♠'
|
||||
>>> format(Suite.spades, 'z')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid format spec 'z' for object of type 'Suite'
|
||||
>>> bytes(Suite.spades), bytes(Suite.clubs)
|
||||
(b'\\x00', b'\\x03')
|
||||
|
||||
Spadille is the nickname for the Ace of Spades in some games
|
||||
(see `Webster 1913`_)
|
||||
|
||||
>>> spadille = Card('A', Suite.spades, long_rank='Ace')
|
||||
>>> spadille
|
||||
Card('A', 'spades')
|
||||
>>> print(spadille)
|
||||
Ace of spades
|
||||
>>> format(spadille)
|
||||
'A-spades'
|
||||
>>> format(spadille, 'r/p')
|
||||
'A/♠'
|
||||
>>> format(spadille, 'R of S')
|
||||
'Ace of Spades'
|
||||
>>> bytes(spadille)
|
||||
b'A\\x00'
|
||||
>>> beer_card = Card('7', Suite.diamonds)
|
||||
>>> bytes(beer_card)
|
||||
b'7\\x02'
|
||||
>>> big_cassino = Card('10', Suite.diamonds)
|
||||
>>> bytes(big_cassino)
|
||||
b'10\\x02'
|
||||
|
||||
__ http://machaut.uchicago.edu/cgi-bin/WEBSTER.sh?WORD=spadille
|
||||
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from operator import attrgetter
|
||||
import re
|
||||
|
||||
spades diamonds clubs hearts
|
||||
|
||||
class Suite(Enum):
|
||||
spades = '\u2660' # U+2660 ♠ BLACK SPADE SUIT
|
||||
diamonds = '\u2662' # U+2662 ♢ WHITE DIAMOND SUIT
|
||||
clubs = '\u2663' # U+2663 ♣ BLACK CLUB SUIT
|
||||
hearts = '\u2661' # U+2661 ♡ WHITE HEART SUIT
|
||||
|
||||
def format_p(self):
|
||||
return chr(0x2660 + self.value)
|
||||
|
||||
def format_s(self):
|
||||
return self.name
|
||||
|
||||
def format_S(self):
|
||||
return self.name.capitalize()
|
||||
|
||||
def __bytes__(self):
|
||||
return bytes([self.value])
|
||||
|
||||
def __format__(self, format_spec):
|
||||
use_spec = 's' if format_spec == '' else format_spec
|
||||
format_method = getattr(self, 'format_' + use_spec, None)
|
||||
if format_method:
|
||||
return format_method()
|
||||
|
||||
msg = "Invalid format spec {!r} for object of type 'Suite'"
|
||||
raise ValueError(msg.format(format_spec))
|
||||
|
||||
class Card:
|
||||
|
||||
def __init__(self, rank, suite, *, long_rank=None):
|
||||
self.rank = rank
|
||||
if long_rank is None:
|
||||
self.long_rank = self.rank
|
||||
else:
|
||||
self.long_rank = long_rank
|
||||
self.suite = suite
|
||||
|
||||
def __str__(self):
|
||||
return '{long_rank} of {suite.name}'.format(**self.__dict__)
|
||||
|
||||
def __repr__(self):
|
||||
template = '{cls.__name__}({rank!r}, {suite.name!r})'
|
||||
return template.format(cls=self.__class__, **self.__dict__)
|
||||
|
||||
def __bytes__(self):
|
||||
rank_bytes = bytes(ord(char) for char in self.rank)
|
||||
return rank_bytes + bytes(self.suite)
|
||||
|
||||
rank_codes = {
|
||||
'r': attrgetter('rank'),
|
||||
'R': attrgetter('long_rank'),
|
||||
}
|
||||
|
||||
def __format__(self, format_spec):
|
||||
if not format_spec:
|
||||
format_spec = 'r-s'
|
||||
result = []
|
||||
for code in format_spec:
|
||||
if code in Card.rank_codes:
|
||||
result.append(Card.rank_codes[code](self))
|
||||
else:
|
||||
try:
|
||||
result.append(format(self.suite, code))
|
||||
except ValueError:
|
||||
result.append(code)
|
||||
return ''.join(result)
|
||||
|
||||
28
objects/cheese.py
Normal file
28
objects/cheese.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
>>> import weakref
|
||||
>>> stock = weakref.WeakValueDictionary()
|
||||
>>> catalog = [Cheese('Red Leicester'), Cheese('Tilsit'),
|
||||
... Cheese('Brie'), Cheese('Parmesan')]
|
||||
...
|
||||
>>> for cheese in catalog:
|
||||
... stock[cheese.kind] = cheese
|
||||
...
|
||||
>>> sorted(stock.keys())
|
||||
['Brie', 'Parmesan', 'Red Leicester', 'Tilsit']
|
||||
>>> del catalog
|
||||
>>> sorted(stock.keys())
|
||||
['Parmesan']
|
||||
>>> del cheese
|
||||
>>> sorted(stock.keys())
|
||||
[]
|
||||
"""
|
||||
|
||||
# BEGIN CHEESE_CLASS
|
||||
class Cheese:
|
||||
|
||||
def __init__(self, kind):
|
||||
self.kind = kind
|
||||
|
||||
def __repr__(self):
|
||||
return 'Cheese(%r)' % self.kind
|
||||
# END CHEESE_CLASS
|
||||
25
objects/common_attrs.txt
Normal file
25
objects/common_attrs.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
|object |instance|list |function|class
|
||||
__class__ |type |type |type |type |type
|
||||
__delattr__ |method |method |method |method |method
|
||||
__dir__ |method |method |method |method |method
|
||||
__doc__ |data |data |data |data |data
|
||||
__eq__ |method |method |method |method |method
|
||||
__format__ |method |method |method |method |method
|
||||
__ge__ |method |method |method |method |method
|
||||
__getattribute__ |method |method |method |method |method
|
||||
__gt__ |method |method |method |method |method
|
||||
__hash__ |method |method |data |method |method
|
||||
__init__ |method |method |method |method |method
|
||||
__le__ |method |method |method |method |method
|
||||
__lt__ |method |method |method |method |method
|
||||
__ne__ |method |method |method |method |method
|
||||
__new__ |method |method |method |method |method
|
||||
__reduce__ |method |method |method |method |method
|
||||
__reduce_ex__ |method |method |method |method |method
|
||||
__repr__ |method |method |method |method |method
|
||||
__setattr__ |method |method |method |method |method
|
||||
__sizeof__ |method |method |method |method |method
|
||||
__str__ |method |method |method |method |method
|
||||
__subclasshook__ |method |method |method |method |method
|
||||
TOTALS | 22| 25| 34| 34| 28
|
||||
['attr_list.py', '+']
|
||||
47
objects/haunted_bus.py
Normal file
47
objects/haunted_bus.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
>>> bus1 = HountedBus(['Alice', 'Bill'])
|
||||
>>> bus1.passengers
|
||||
['Alice', 'Bill']
|
||||
>>> bus1.pick('Charlie')
|
||||
>>> bus1.drop('Alice')
|
||||
>>> bus1.passengers
|
||||
['Bill', 'Charlie']
|
||||
>>> bus2 = HountedBus()
|
||||
>>> bus2.pick('Carrie')
|
||||
>>> bus2.passengers
|
||||
['Carrie']
|
||||
>>> bus3 = HountedBus()
|
||||
>>> bus3.passengers
|
||||
['Carrie']
|
||||
>>> bus3.pick('Dave')
|
||||
>>> bus2.passengers
|
||||
['Carrie', 'Dave']
|
||||
>>> bus2.passengers is bus3.passengers
|
||||
True
|
||||
>>> bus1.passengers
|
||||
['Bill', 'Charlie']
|
||||
|
||||
|
||||
>>> dir(HountedBus.__init__) # doctest: +ELLIPSIS
|
||||
['__annotations__', '__call__', ..., '__defaults__', ...]
|
||||
>>> HountedBus.__init__.__defaults__
|
||||
(['Carrie', 'Dave'],)
|
||||
>>> HountedBus.__init__.__defaults__[0] is bus2.passengers
|
||||
True
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN HAUNTED_BUS_CLASS
|
||||
class HountedBus:
|
||||
"""A bus model hounted by ghost passengers"""
|
||||
|
||||
def __init__(self, passengers=[]): # <1>
|
||||
self.passengers = passengers # <2>
|
||||
|
||||
def pick(self, name):
|
||||
self.passengers.append(name) # <3>
|
||||
|
||||
def drop(self, name):
|
||||
self.passengers.remove(name)
|
||||
# END HAUNTED_BUS_CLASS
|
||||
|
||||
28
objects/not_so_common_attrs.txt
Normal file
28
objects/not_so_common_attrs.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
|object |instance|list |function|class
|
||||
__add__ | | |method | |
|
||||
__annotations__ | | | |data |
|
||||
__call__ | | | |method |method
|
||||
__closure__ | | | |data |
|
||||
__code__ | | | |data |
|
||||
__contains__ | | |method | |
|
||||
__defaults__ | | | |data |
|
||||
__delitem__ | | |method | |
|
||||
__dict__ | |data | |data |data
|
||||
__get__ | | | |method |
|
||||
__getitem__ | | |method | |
|
||||
__globals__ | | | |data |
|
||||
__iadd__ | | |method | |
|
||||
__imul__ | | |method | |
|
||||
__iter__ | | |method | |
|
||||
__kwdefaults__ | | | |data |
|
||||
__len__ | | |method | |
|
||||
__module__ | |data | |data |data
|
||||
__mul__ | | |method | |
|
||||
__name__ | | | |data |data
|
||||
__qualname__ | | | |data |data
|
||||
__reversed__ | | |method | |
|
||||
__rmul__ | | |method | |
|
||||
__setitem__ | | |method | |
|
||||
__weakref__ | |data | | |data
|
||||
TOTALS | 22| 25| 34| 34| 28
|
||||
['attr_list.py']
|
||||
26
objects/twilight_bus.py
Normal file
26
objects/twilight_bus.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
>>> basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat']
|
||||
>>> bus = TwilightBus(basketball_team)
|
||||
>>> bus.drop('Tina')
|
||||
>>> bus.drop('Pat')
|
||||
>>> basketball_team
|
||||
['Sue', 'Maya', 'Diana']
|
||||
"""
|
||||
|
||||
# BEGIN TWILIGHT_BUS_CLASS
|
||||
class TwilightBus:
|
||||
"""A bus model that makes passengers vanish"""
|
||||
|
||||
def __init__(self, passengers=None):
|
||||
if passengers is None:
|
||||
self.passengers = [] # <1>
|
||||
else:
|
||||
self.passengers = passengers #<2>
|
||||
|
||||
def pick(self, name):
|
||||
self.passengers.append(name)
|
||||
|
||||
def drop(self, name):
|
||||
self.passengers.remove(name) # <3>
|
||||
# END TWILIGHT_BUS_CLASS
|
||||
|
||||
140
operator/vector.py
Normal file
140
operator/vector.py
Normal file
@@ -0,0 +1,140 @@
|
||||
"""
|
||||
|
||||
The `+` operator produces a `Vector` result.
|
||||
|
||||
>>> v1 = Vector(2, 4)
|
||||
>>> v2 = Vector(2, 1)
|
||||
>>> v1 + v2
|
||||
Vector(4, 5)
|
||||
|
||||
We can also implemement the `*` operator to perform scalar multiplication
|
||||
or elementwise multiplication.
|
||||
|
||||
>>> v = Vector(3, 4)
|
||||
>>> abs(v)
|
||||
5.0
|
||||
>>> v * 3
|
||||
Vector(9, 12)
|
||||
>>> abs(v * 3)
|
||||
15.0
|
||||
>>> v25 = Vector(2, 5)
|
||||
>>> v71 = Vector(7, 1)
|
||||
>>> v71 * v25
|
||||
Vector(14, 5)
|
||||
|
||||
A vector can be used in a boolean context, where it will be considered
|
||||
_falsy_ if it has magnitude zero, otherwise it is _truthy_::
|
||||
|
||||
>>> bool(v)
|
||||
True
|
||||
>>> bool(Vector(0, 0))
|
||||
False
|
||||
|
||||
Vectors can have n-dimensions::
|
||||
|
||||
>>> v3 = Vector(1, 2, 3)
|
||||
>>> len(v3)
|
||||
3
|
||||
>>> v3
|
||||
Vector(1, 2, 3)
|
||||
>>> abs(v3) # doctest:+ELLIPSIS
|
||||
3.74165738...
|
||||
>>> v3 + Vector(4, 5, 6)
|
||||
Vector(5, 7, 9)
|
||||
>>> v3 * 5
|
||||
Vector(5, 10, 15)
|
||||
>>> v2 + v3
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Addition applies only to vectors of equal dimensions.
|
||||
|
||||
|
||||
The `repr` of a Vector is produced with the help of the `reprlib.repr`
|
||||
function, limiting the size of the output string:
|
||||
|
||||
>>> Vector(*range(100))
|
||||
Vector(0, 1, 2, 3, 4, 5, ...)
|
||||
|
||||
|
||||
Dot product is a scalar: the sum of the products of the corresponding
|
||||
components of two vectors.
|
||||
|
||||
>>> v25 = Vector(2, 5)
|
||||
>>> v71 = Vector(7, 1)
|
||||
>>> v25.dot(v71)
|
||||
19
|
||||
>>> Vector(1, 2, 3).dot(Vector(4, 5, 6))
|
||||
32
|
||||
>>> Vector(1, 2, 3).dot(Vector(-2, 0, 5))
|
||||
13
|
||||
|
||||
|
||||
As described in PEP 465, starting with Python 3.5, `__matmul__` is
|
||||
the special method for the new ``@`` operator, to be used the dot
|
||||
product of vectors or matrix multiplication (as opposed to ``*``
|
||||
which is intended for scalar or elementwise multiplication):
|
||||
|
||||
>>> # skip these tests on Python < 3.5
|
||||
>>> v25 @ v71 # doctest:+SKIP
|
||||
19
|
||||
>>> v71 * v25
|
||||
Vector(14, 5)
|
||||
>>> Vector(1, 2, 3) @ Vector(-2, 0, 5) # doctest:+SKIP
|
||||
13
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN VECTOR_OPS
|
||||
import math
|
||||
import numbers
|
||||
import reprlib
|
||||
|
||||
EQ_DIMENSIONS_MSG = '%s applies only to vectors of equal dimensions.'
|
||||
|
||||
class Vector:
|
||||
"""An n-dimensional vector"""
|
||||
|
||||
def __init__(self, *components): # <1>
|
||||
self._components = tuple(components) # <2>
|
||||
|
||||
def __repr__(self):
|
||||
return 'Vector' + (reprlib.repr(self._components)) # <3>
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._components) # <4>
|
||||
|
||||
def __abs__(self):
|
||||
return math.sqrt(sum(comp*comp for comp in self)) # <5>
|
||||
|
||||
def __len__(self):
|
||||
return len(self._components) # <6>
|
||||
|
||||
def __add__(self, other):
|
||||
if len(self) != len(other):
|
||||
raise ValueError(EQ_DIMENSIONS_MSG % 'Addition')
|
||||
return Vector(*(a+b for a, b in zip(self, other))) # <7>
|
||||
|
||||
def __mul__(self, other):
|
||||
if isinstance(other, numbers.Number):
|
||||
return Vector(*(comp*other for comp in self)) # <8>
|
||||
else:
|
||||
return self.elementwise_mul(other) # <9>
|
||||
|
||||
def elementwise_mul(self, other):
|
||||
if len(self) != len(other):
|
||||
raise ValueError(EQ_DIMENSIONS_MSG %
|
||||
'Elementwise multiplication')
|
||||
return Vector(*(a*b for a, b in zip(self, other))) # <10>
|
||||
|
||||
def __bool__(self):
|
||||
return any(self) # <11>
|
||||
|
||||
def dot(self, other):
|
||||
if len(self) != len(other):
|
||||
raise ValueError(EQ_DIMENSIONS_MSG %
|
||||
'Dot product')
|
||||
return sum(a*b for a, b in zip(self, other)) # <12>
|
||||
|
||||
__matmul__ = dot # support @ operator in Python 3.5
|
||||
|
||||
# END VECTOR_OPS
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user