removed 1st edition code
This commit is contained in:
parent
1be97eea3e
commit
506d343843
@ -1,4 +0,0 @@
|
||||
Sample code for Chapter 5 - "First-class functions"
|
||||
|
||||
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
|
||||
http://shop.oreilly.com/product/0636920032519.do
|
@ -1,35 +0,0 @@
|
||||
"""
|
||||
# BEGIN BINGO_DEMO
|
||||
|
||||
>>> bingo = BingoCage(range(3))
|
||||
>>> bingo.pick()
|
||||
1
|
||||
>>> 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 pick(self): # <3>
|
||||
try:
|
||||
return self._items.pop()
|
||||
except IndexError:
|
||||
raise LookupError('pick from empty BingoCage') # <4>
|
||||
|
||||
def __call__(self): # <5>
|
||||
return self.pick()
|
||||
|
||||
# END BINGO
|
@ -1,38 +0,0 @@
|
||||
"""
|
||||
>>> 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
|
@ -1,38 +0,0 @@
|
||||
"""
|
||||
>>> 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: # <1>
|
||||
"""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
|
@ -1,10 +0,0 @@
|
||||
>>> 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
|
@ -1,9 +0,0 @@
|
||||
>>> 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
|
@ -1,12 +0,0 @@
|
||||
>>> 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
|
@ -1,44 +0,0 @@
|
||||
|
||||
|
||||
"""
|
||||
# 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
|
@ -1,4 +0,0 @@
|
||||
Sample code for Chapter 6 - "Design patterns with first class functions"
|
||||
|
||||
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
|
||||
http://shop.oreilly.com/product/0636920032519.do
|
@ -1,106 +0,0 @@
|
||||
# classic_strategy.py
|
||||
# Strategy pattern -- classic implementation
|
||||
|
||||
"""
|
||||
# BEGIN CLASSIC_STRATEGY_TESTS
|
||||
|
||||
>>> joe = Customer('John Doe', 0) # <1>
|
||||
>>> ann = Customer('Ann Smith', 1100)
|
||||
>>> cart = [LineItem('banana', 4, .5), # <2>
|
||||
... LineItem('apple', 10, 1.5),
|
||||
... LineItem('watermellon', 5, 5.0)]
|
||||
>>> Order(joe, cart, FidelityPromo()) # <3>
|
||||
<Order total: 42.00 due: 42.00>
|
||||
>>> Order(ann, cart, FidelityPromo()) # <4>
|
||||
<Order total: 42.00 due: 39.90>
|
||||
>>> banana_cart = [LineItem('banana', 30, .5), # <5>
|
||||
... LineItem('apple', 10, 1.5)]
|
||||
>>> Order(joe, banana_cart, BulkItemPromo()) # <6>
|
||||
<Order total: 30.00 due: 28.50>
|
||||
>>> long_order = [LineItem(str(item_code), 1, 1.0) # <7>
|
||||
... for item_code in range(10)]
|
||||
>>> Order(joe, long_order, LargeOrderPromo()) # <8>
|
||||
<Order total: 10.00 due: 9.30>
|
||||
>>> Order(joe, cart, LargeOrderPromo())
|
||||
<Order total: 42.00 due: 42.00>
|
||||
|
||||
# END CLASSIC_STRATEGY_TESTS
|
||||
"""
|
||||
# BEGIN CLASSIC_STRATEGY
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
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.discount(self)
|
||||
return self.total() - discount
|
||||
|
||||
def __repr__(self):
|
||||
fmt = '<Order total: {:.2f} due: {:.2f}>'
|
||||
return fmt.format(self.total(), self.due())
|
||||
|
||||
|
||||
class Promotion(ABC): # the Strategy: an Abstract Base Class
|
||||
|
||||
@abstractmethod
|
||||
def discount(self, order):
|
||||
"""Return discount as a positive dollar amount"""
|
||||
|
||||
|
||||
class FidelityPromo(Promotion): # first Concrete Strategy
|
||||
"""5% discount for customers with 1000 or more fidelity points"""
|
||||
|
||||
def discount(self, order):
|
||||
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
|
||||
|
||||
|
||||
class BulkItemPromo(Promotion): # second Concrete Strategy
|
||||
"""10% discount for each LineItem with 20 or more units"""
|
||||
|
||||
def discount(self, order):
|
||||
discount = 0
|
||||
for item in order.cart:
|
||||
if item.quantity >= 20:
|
||||
discount += item.total() * .1
|
||||
return discount
|
||||
|
||||
|
||||
class LargeOrderPromo(Promotion): # third Concrete Strategy
|
||||
"""7% discount for orders with 10 or more distinct items"""
|
||||
|
||||
def discount(self, order):
|
||||
distinct_items = {item.product for item in order.cart}
|
||||
if len(distinct_items) >= 10:
|
||||
return order.total() * .07
|
||||
return 0
|
||||
|
||||
# END CLASSIC_STRATEGY
|
@ -1,20 +0,0 @@
|
||||
|
||||
def fidelity_promo(order):
|
||||
"""5% discount for customers with 1000 or more fidelity points"""
|
||||
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
|
||||
|
||||
|
||||
def bulk_item_promo(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
|
||||
|
||||
def large_order_promo(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
|
@ -1,93 +0,0 @@
|
||||
# strategy.py
|
||||
# Strategy pattern -- function-based implementation
|
||||
|
||||
"""
|
||||
# BEGIN STRATEGY_TESTS
|
||||
|
||||
>>> joe = Customer('John Doe', 0) # <1>
|
||||
>>> ann = Customer('Ann Smith', 1100)
|
||||
>>> cart = [LineItem('banana', 4, .5),
|
||||
... LineItem('apple', 10, 1.5),
|
||||
... LineItem('watermellon', 5, 5.0)]
|
||||
>>> Order(joe, cart, fidelity_promo) # <2>
|
||||
<Order total: 42.00 due: 42.00>
|
||||
>>> Order(ann, cart, fidelity_promo)
|
||||
<Order total: 42.00 due: 39.90>
|
||||
>>> banana_cart = [LineItem('banana', 30, .5),
|
||||
... LineItem('apple', 10, 1.5)]
|
||||
>>> Order(joe, banana_cart, bulk_item_promo) # <3>
|
||||
<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_promo)
|
||||
<Order total: 10.00 due: 9.30>
|
||||
>>> Order(joe, cart, large_order_promo)
|
||||
<Order total: 42.00 due: 42.00>
|
||||
|
||||
# END STRATEGY_TESTS
|
||||
"""
|
||||
# BEGIN STRATEGY
|
||||
|
||||
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) # <1>
|
||||
return self.total() - discount
|
||||
|
||||
def __repr__(self):
|
||||
fmt = '<Order total: {:.2f} due: {:.2f}>'
|
||||
return fmt.format(self.total(), self.due())
|
||||
|
||||
# <2>
|
||||
|
||||
def fidelity_promo(order): # <3>
|
||||
"""5% discount for customers with 1000 or more fidelity points"""
|
||||
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
|
||||
|
||||
|
||||
def bulk_item_promo(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
|
||||
|
||||
|
||||
def large_order_promo(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
|
||||
|
||||
# END STRATEGY
|
@ -1,108 +0,0 @@
|
||||
# strategy_best.py
|
||||
# Strategy pattern -- function-based implementation
|
||||
# selecting best promotion from static list of functions
|
||||
|
||||
"""
|
||||
>>> 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_promo)
|
||||
<Order total: 42.00 due: 42.00>
|
||||
>>> Order(ann, cart, fidelity_promo)
|
||||
<Order total: 42.00 due: 39.90>
|
||||
>>> banana_cart = [LineItem('banana', 30, .5),
|
||||
... LineItem('apple', 10, 1.5)]
|
||||
>>> Order(joe, banana_cart, bulk_item_promo)
|
||||
<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_promo)
|
||||
<Order total: 10.00 due: 9.30>
|
||||
>>> Order(joe, cart, large_order_promo)
|
||||
<Order total: 42.00 due: 42.00>
|
||||
|
||||
# BEGIN STRATEGY_BEST_TESTS
|
||||
|
||||
>>> Order(joe, long_order, best_promo) # <1>
|
||||
<Order total: 10.00 due: 9.30>
|
||||
>>> Order(joe, banana_cart, best_promo) # <2>
|
||||
<Order total: 30.00 due: 28.50>
|
||||
>>> Order(ann, cart, best_promo) # <3>
|
||||
<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())
|
||||
|
||||
|
||||
def fidelity_promo(order):
|
||||
"""5% discount for customers with 1000 or more fidelity points"""
|
||||
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
|
||||
|
||||
|
||||
def bulk_item_promo(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
|
||||
|
||||
|
||||
def large_order_promo(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
|
||||
|
||||
# BEGIN STRATEGY_BEST
|
||||
|
||||
promos = [fidelity_promo, bulk_item_promo, large_order_promo] # <1>
|
||||
|
||||
def best_promo(order): # <2>
|
||||
"""Select best discount available
|
||||
"""
|
||||
return max(promo(order) for promo in promos) # <3>
|
||||
|
||||
# END STRATEGY_BEST
|
@ -1,110 +0,0 @@
|
||||
# strategy_best2.py
|
||||
# Strategy pattern -- function-based implementation
|
||||
# selecting best promotion from current module globals
|
||||
|
||||
"""
|
||||
>>> 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_promo)
|
||||
<Order total: 42.00 due: 42.00>
|
||||
>>> Order(ann, cart, fidelity_promo)
|
||||
<Order total: 42.00 due: 39.90>
|
||||
>>> banana_cart = [LineItem('banana', 30, .5),
|
||||
... LineItem('apple', 10, 1.5)]
|
||||
>>> Order(joe, banana_cart, bulk_item_promo)
|
||||
<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_promo)
|
||||
<Order total: 10.00 due: 9.30>
|
||||
>>> Order(joe, cart, large_order_promo)
|
||||
<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())
|
||||
|
||||
|
||||
def fidelity_promo(order):
|
||||
"""5% discount for customers with 1000 or more fidelity points"""
|
||||
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
|
||||
|
||||
|
||||
def bulk_item_promo(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
|
||||
|
||||
|
||||
def large_order_promo(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
|
||||
|
||||
# BEGIN STRATEGY_BEST2
|
||||
|
||||
promos = [globals()[name] for name in globals() # <1>
|
||||
if name.endswith('_promo') # <2>
|
||||
and name != 'best_promo'] # <3>
|
||||
|
||||
def best_promo(order):
|
||||
"""Select best discount available
|
||||
"""
|
||||
return max(promo(order) for promo in promos) # <4>
|
||||
|
||||
# END STRATEGY_BEST2
|
@ -1,93 +0,0 @@
|
||||
# strategy_best3.py
|
||||
# Strategy pattern -- function-based implementation
|
||||
# selecting best promotion from imported module
|
||||
|
||||
"""
|
||||
>>> from promotions import *
|
||||
>>> 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_promo)
|
||||
<Order total: 42.00 due: 42.00>
|
||||
>>> Order(ann, cart, fidelity_promo)
|
||||
<Order total: 42.00 due: 39.90>
|
||||
>>> banana_cart = [LineItem('banana', 30, .5),
|
||||
... LineItem('apple', 10, 1.5)]
|
||||
>>> Order(joe, banana_cart, bulk_item_promo)
|
||||
<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_promo)
|
||||
<Order total: 10.00 due: 9.30>
|
||||
>>> Order(joe, cart, large_order_promo)
|
||||
<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
|
||||
import inspect
|
||||
|
||||
import promotions
|
||||
|
||||
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_BEST3
|
||||
|
||||
promos = [func for name, func in
|
||||
inspect.getmembers(promotions, inspect.isfunction)]
|
||||
|
||||
def best_promo(order):
|
||||
"""Select best discount available
|
||||
"""
|
||||
return max(promo(order) for promo in promos)
|
||||
|
||||
# END STRATEGY_BEST3
|
||||
|
||||
|
@ -1,4 +0,0 @@
|
||||
Sample code for Chapter 7 - "Closures and decorators"
|
||||
|
||||
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
|
||||
http://shop.oreilly.com/product/0636920032519.do
|
@ -1,33 +0,0 @@
|
||||
"""
|
||||
>>> 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
|
@ -1,21 +0,0 @@
|
||||
"""
|
||||
>>> 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)
|
@ -1,15 +0,0 @@
|
||||
# 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
|
@ -1,43 +0,0 @@
|
||||
# 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_CLS
|
||||
import time
|
||||
|
||||
DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'
|
||||
|
||||
class clock:
|
||||
|
||||
def __init__(self, fmt=DEFAULT_FMT):
|
||||
self.fmt = fmt
|
||||
|
||||
def __call__(self, func):
|
||||
def clocked(*_args):
|
||||
t0 = time.time()
|
||||
_result = func(*_args)
|
||||
elapsed = time.time() - t0
|
||||
name = func.__name__
|
||||
args = ', '.join(repr(arg) for arg in _args)
|
||||
result = repr(_result)
|
||||
print(self.fmt.format(**locals()))
|
||||
return _result
|
||||
return clocked
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@clock()
|
||||
def snooze(seconds):
|
||||
time.sleep(seconds)
|
||||
|
||||
for i in range(3):
|
||||
snooze(.123)
|
||||
|
||||
# END CLOCKDECO_CLS
|
@ -1,18 +0,0 @@
|
||||
# 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))
|
@ -1,40 +0,0 @@
|
||||
# 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
|
@ -1,9 +0,0 @@
|
||||
import time
|
||||
from clockdeco_param import clock
|
||||
|
||||
@clock('{name}: {elapsed}s')
|
||||
def snooze(seconds):
|
||||
time.sleep(seconds)
|
||||
|
||||
for i in range(3):
|
||||
snooze(.123)
|
@ -1,9 +0,0 @@
|
||||
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)
|
@ -1,10 +0,0 @@
|
||||
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))
|
@ -1,13 +0,0 @@
|
||||
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))
|
@ -1,52 +0,0 @@
|
||||
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
|
||||
from collections import abc
|
||||
import numbers
|
||||
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(numbers.Integral) # <4>
|
||||
def _(n):
|
||||
return '<pre>{0} (0x{0:x})</pre>'.format(n)
|
||||
|
||||
@htmlize.register(tuple) # <5>
|
||||
@htmlize.register(abc.MutableSequence)
|
||||
def _(seq):
|
||||
inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
|
||||
return '<ul>\n<li>' + inner + '</li>\n</ul>'
|
||||
|
||||
# END HTMLIZE
|
||||
|
@ -1,124 +0,0 @@
|
||||
>>> 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
|
||||
|
||||
|
@ -1,31 +0,0 @@
|
||||
# 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
|
@ -1,16 +0,0 @@
|
||||
# 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
|
@ -1,28 +0,0 @@
|
||||
# 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:
|
||||
registry.discard(func) # <5>
|
||||
|
||||
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
|
@ -1,113 +0,0 @@
|
||||
# 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
|
@ -1 +0,0 @@
|
||||
== Type Hints in Function Definitions
|
@ -1,4 +0,0 @@
|
||||
Sample code for Chapter 8 - "Object references, mutability and recycling"
|
||||
|
||||
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
|
||||
http://shop.oreilly.com/product/0636920032519.do
|
@ -1,29 +0,0 @@
|
||||
|
||||
"""
|
||||
>>> 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
|
@ -1,28 +0,0 @@
|
||||
"""
|
||||
>>> 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
|
@ -1,47 +0,0 @@
|
||||
"""
|
||||
>>> bus1 = HauntedBus(['Alice', 'Bill'])
|
||||
>>> bus1.passengers
|
||||
['Alice', 'Bill']
|
||||
>>> bus1.pick('Charlie')
|
||||
>>> bus1.drop('Alice')
|
||||
>>> bus1.passengers
|
||||
['Bill', 'Charlie']
|
||||
>>> bus2 = HauntedBus()
|
||||
>>> bus2.pick('Carrie')
|
||||
>>> bus2.passengers
|
||||
['Carrie']
|
||||
>>> bus3 = HauntedBus()
|
||||
>>> bus3.passengers
|
||||
['Carrie']
|
||||
>>> bus3.pick('Dave')
|
||||
>>> bus2.passengers
|
||||
['Carrie', 'Dave']
|
||||
>>> bus2.passengers is bus3.passengers
|
||||
True
|
||||
>>> bus1.passengers
|
||||
['Bill', 'Charlie']
|
||||
|
||||
|
||||
>>> dir(HauntedBus.__init__) # doctest: +ELLIPSIS
|
||||
['__annotations__', '__call__', ..., '__defaults__', ...]
|
||||
>>> HauntedBus.__init__.__defaults__
|
||||
(['Carrie', 'Dave'],)
|
||||
>>> HauntedBus.__init__.__defaults__[0] is bus2.passengers
|
||||
True
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN HAUNTED_BUS_CLASS
|
||||
class HauntedBus:
|
||||
"""A bus model haunted 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
|
||||
|
@ -1,26 +0,0 @@
|
||||
"""
|
||||
>>> 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
|
||||
|
@ -1,4 +0,0 @@
|
||||
Sample code for Chapter 9 - "Pythonic objects"
|
||||
|
||||
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
|
||||
http://shop.oreilly.com/product/0636920032519.do
|
@ -1,24 +0,0 @@
|
||||
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 Vector2d type: {.__name__}.{.__name__}'
|
||||
print(fmt.format(module, module.Vector2d))
|
||||
|
||||
mem_init = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
||||
print('Creating {:,} Vector2d instances'.format(NUM_VECTORS))
|
||||
|
||||
vectors = [module.Vector2d(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))
|
@ -1,8 +0,0 @@
|
||||
public class Confidential {
|
||||
|
||||
private String secret = "";
|
||||
|
||||
public Confidential(String text) {
|
||||
secret = text.toUpperCase();
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class Expose {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Confidential message = new Confidential("top secret text");
|
||||
Field secretField = null;
|
||||
try {
|
||||
secretField = Confidential.class.getDeclaredField("secret");
|
||||
}
|
||||
catch (NoSuchFieldException e) {
|
||||
System.err.println(e);
|
||||
System.exit(1);
|
||||
}
|
||||
secretField.setAccessible(true); // break the lock!
|
||||
try {
|
||||
String wasHidden = (String) secretField.get(message);
|
||||
System.out.println("message.secret = " + wasHidden);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
// this will not happen after setAcessible(true)
|
||||
System.err.println(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import Confidential
|
||||
|
||||
message = Confidential('top secret text')
|
||||
secret_field = Confidential.getDeclaredField('secret')
|
||||
secret_field.setAccessible(True) # break the lock!
|
||||
print 'message.secret =', secret_field.get(message)
|
@ -1,11 +0,0 @@
|
||||
from java.lang.reflect import Modifier
|
||||
import Confidential
|
||||
|
||||
message = Confidential('top secret text')
|
||||
fields = Confidential.getDeclaredFields()
|
||||
for field in fields:
|
||||
# list private fields only
|
||||
if Modifier.isPrivate(field.getModifiers()):
|
||||
field.setAccessible(True) # break the lock
|
||||
print 'field:', field
|
||||
print '\t', field.getName(), '=', field.get(message)
|
@ -1,17 +0,0 @@
|
||||
|
||||
"""
|
||||
In the Jython registry file there is this line:
|
||||
|
||||
python.security.respectJavaAccessibility = true
|
||||
|
||||
Set this to false and Jython provides access to non-public
|
||||
fields, methods, and constructors of Java objects.
|
||||
"""
|
||||
|
||||
import Confidential
|
||||
|
||||
message = Confidential('top secret text')
|
||||
for name in dir(message):
|
||||
attr = getattr(message, name)
|
||||
if not callable(attr): # non-methods only
|
||||
print name + '\t=', attr
|
@ -1,64 +0,0 @@
|
||||
"""
|
||||
A 2-dimensional vector class
|
||||
|
||||
# BEGIN VECTOR2D_V0_DEMO
|
||||
|
||||
>>> v1 = Vector2d(3, 4)
|
||||
>>> print(v1.x, v1.y) # <1>
|
||||
3.0 4.0
|
||||
>>> x, y = v1 # <2>
|
||||
>>> x, y
|
||||
(3.0, 4.0)
|
||||
>>> v1 # <3>
|
||||
Vector2d(3.0, 4.0)
|
||||
>>> v1_clone = eval(repr(v1)) # <4>
|
||||
>>> v1 == v1_clone # <5>
|
||||
True
|
||||
>>> print(v1) # <6>
|
||||
(3.0, 4.0)
|
||||
>>> octets = bytes(v1) # <7>
|
||||
>>> octets
|
||||
b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
|
||||
>>> abs(v1) # <8>
|
||||
5.0
|
||||
>>> bool(v1), bool(Vector2d(0, 0)) # <9>
|
||||
(True, False)
|
||||
|
||||
# END VECTOR2D_V0_DEMO
|
||||
"""
|
||||
|
||||
# BEGIN VECTOR2D_V0
|
||||
from array import array
|
||||
import math
|
||||
|
||||
|
||||
class Vector2d:
|
||||
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):
|
||||
class_name = type(self).__name__
|
||||
return '{}({!r}, {!r})'.format(class_name, *self) # <4>
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self)) # <5>
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes([ord(self.typecode)]) + # <6>
|
||||
bytes(array(self.typecode, self))) # <7>
|
||||
|
||||
def __eq__(self, other):
|
||||
return tuple(self) == tuple(other) # <8>
|
||||
|
||||
def __abs__(self):
|
||||
return math.hypot(self.x, self.y) # <9>
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self)) # <10>
|
||||
# END VECTOR2D_V0
|
@ -1,76 +0,0 @@
|
||||
"""
|
||||
A 2-dimensional vector class
|
||||
|
||||
>>> v1 = Vector2d(3, 4)
|
||||
>>> print(v1.x, v1.y)
|
||||
3.0 4.0
|
||||
>>> x, y = v1
|
||||
>>> x, y
|
||||
(3.0, 4.0)
|
||||
>>> v1
|
||||
Vector2d(3.0, 4.0)
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0)
|
||||
>>> octets = bytes(v1)
|
||||
>>> octets
|
||||
b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
|
||||
>>> abs(v1)
|
||||
5.0
|
||||
>>> bool(v1), bool(Vector2d(0, 0))
|
||||
(True, False)
|
||||
|
||||
|
||||
Test of ``.frombytes()`` class method:
|
||||
|
||||
>>> v1_clone = Vector2d.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector2d(3.0, 4.0)
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import math
|
||||
|
||||
|
||||
class Vector2d:
|
||||
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):
|
||||
class_name = type(self).__name__
|
||||
return '{}({!r}, {!r})'.format(class_name, *self)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes([ord(self.typecode)]) +
|
||||
bytes(array(self.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 VECTOR2D_V1
|
||||
@classmethod # <1>
|
||||
def frombytes(cls, octets): # <2>
|
||||
typecode = chr(octets[0]) # <3>
|
||||
memv = memoryview(octets[1:]).cast(typecode) # <4>
|
||||
return cls(*memv) # <5>
|
||||
# END VECTOR2D_V1
|
@ -1,123 +0,0 @@
|
||||
"""
|
||||
A 2-dimensional vector class
|
||||
|
||||
>>> v1 = Vector2d(3, 4)
|
||||
>>> print(v1.x, v1.y)
|
||||
3.0 4.0
|
||||
>>> x, y = v1
|
||||
>>> x, y
|
||||
(3.0, 4.0)
|
||||
>>> v1
|
||||
Vector2d(3.0, 4.0)
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0)
|
||||
>>> octets = bytes(v1)
|
||||
>>> octets
|
||||
b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
|
||||
>>> abs(v1)
|
||||
5.0
|
||||
>>> bool(v1), bool(Vector2d(0, 0))
|
||||
(True, False)
|
||||
|
||||
|
||||
Test of ``.frombytes()`` class method:
|
||||
|
||||
>>> v1_clone = Vector2d.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector2d(3.0, 4.0)
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
|
||||
Tests of ``format()`` with Cartesian 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::
|
||||
|
||||
>>> Vector2d(0, 0).angle()
|
||||
0.0
|
||||
>>> Vector2d(1, 0).angle()
|
||||
0.0
|
||||
>>> epsilon = 10**-8
|
||||
>>> abs(Vector2d(0, 1).angle() - math.pi/2) < epsilon
|
||||
True
|
||||
>>> abs(Vector2d(1, 1).angle() - math.pi/4) < epsilon
|
||||
True
|
||||
|
||||
|
||||
Tests of ``format()`` with polar coordinates:
|
||||
|
||||
>>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS
|
||||
'<1.414213..., 0.785398...>'
|
||||
>>> format(Vector2d(1, 1), '.3ep')
|
||||
'<1.414e+00, 7.854e-01>'
|
||||
>>> format(Vector2d(1, 1), '0.5fp')
|
||||
'<1.41421, 0.78540>'
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN VECTOR2D_V2
|
||||
from array import array
|
||||
import math
|
||||
|
||||
|
||||
class Vector2d:
|
||||
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):
|
||||
class_name = type(self).__name__
|
||||
return '{}({!r}, {!r})'.format(class_name, *self)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes([ord(self.typecode)]) +
|
||||
bytes(array(self.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):
|
||||
typecode = chr(octets[0])
|
||||
memv = memoryview(octets[1:]).cast(typecode)
|
||||
return cls(*memv)
|
||||
# END VECTOR2D_V2
|
@ -1,119 +0,0 @@
|
||||
"""
|
||||
A 2-dimensional vector class
|
||||
|
||||
>>> v1 = Vector2d(3, 4)
|
||||
>>> x, y = v1
|
||||
>>> x, y
|
||||
(3.0, 4.0)
|
||||
>>> v1
|
||||
Vector2d(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(Vector2d(0, 0))
|
||||
(True, False)
|
||||
|
||||
|
||||
Test of ``.frombytes()`` class method:
|
||||
|
||||
>>> v1_clone = Vector2d.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector2d(3.0, 4.0)
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
|
||||
Tests of ``format()`` with Cartesian 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::
|
||||
|
||||
>>> Vector2d(0, 0).angle()
|
||||
0.0
|
||||
>>> Vector2d(1, 0).angle()
|
||||
0.0
|
||||
>>> epsilon = 10**-8
|
||||
>>> abs(Vector2d(0, 1).angle() - math.pi/2) < epsilon
|
||||
True
|
||||
>>> abs(Vector2d(1, 1).angle() - math.pi/4) < epsilon
|
||||
True
|
||||
|
||||
|
||||
Tests of ``format()`` with polar coordinates:
|
||||
|
||||
>>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS
|
||||
'<1.414213..., 0.785398...>'
|
||||
>>> format(Vector2d(1, 1), '.3ep')
|
||||
'<1.414e+00, 7.854e-01>'
|
||||
>>> format(Vector2d(1, 1), '0.5fp')
|
||||
'<1.41421, 0.78540>'
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import math
|
||||
|
||||
|
||||
class Vector2d:
|
||||
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):
|
||||
class_name = type(self).__name__
|
||||
return '{}({!r}, {!r})'.format(class_name, *self)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return bytes(array(Vector2d.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 VECTOR2D_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 VECTOR2D_V2_FORMAT
|
||||
|
||||
@classmethod
|
||||
def frombytes(cls, octets):
|
||||
memv = memoryview(octets).cast(cls.typecode)
|
||||
return cls(*memv)
|
@ -1,151 +0,0 @@
|
||||
"""
|
||||
A 2-dimensional vector class
|
||||
|
||||
>>> v1 = Vector2d(3, 4)
|
||||
>>> print(v1.x, v1.y)
|
||||
3.0 4.0
|
||||
>>> x, y = v1
|
||||
>>> x, y
|
||||
(3.0, 4.0)
|
||||
>>> v1
|
||||
Vector2d(3.0, 4.0)
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0)
|
||||
>>> octets = bytes(v1)
|
||||
>>> octets
|
||||
b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
|
||||
>>> abs(v1)
|
||||
5.0
|
||||
>>> bool(v1), bool(Vector2d(0, 0))
|
||||
(True, False)
|
||||
|
||||
|
||||
Test of ``.frombytes()`` class method:
|
||||
|
||||
>>> v1_clone = Vector2d.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector2d(3.0, 4.0)
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
|
||||
Tests of ``format()`` with Cartesian 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::
|
||||
|
||||
>>> Vector2d(0, 0).angle()
|
||||
0.0
|
||||
>>> Vector2d(1, 0).angle()
|
||||
0.0
|
||||
>>> epsilon = 10**-8
|
||||
>>> abs(Vector2d(0, 1).angle() - math.pi/2) < epsilon
|
||||
True
|
||||
>>> abs(Vector2d(1, 1).angle() - math.pi/4) < epsilon
|
||||
True
|
||||
|
||||
|
||||
Tests of ``format()`` with polar coordinates:
|
||||
|
||||
>>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS
|
||||
'<1.414213..., 0.785398...>'
|
||||
>>> format(Vector2d(1, 1), '.3ep')
|
||||
'<1.414e+00, 7.854e-01>'
|
||||
>>> format(Vector2d(1, 1), '0.5fp')
|
||||
'<1.41421, 0.78540>'
|
||||
|
||||
|
||||
Tests 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
|
||||
|
||||
|
||||
Tests of hashing:
|
||||
|
||||
>>> v1 = Vector2d(3, 4)
|
||||
>>> v2 = Vector2d(3.1, 4.2)
|
||||
>>> hash(v1) != hash(v2)
|
||||
True
|
||||
>>> len(set([v1, v2]))
|
||||
2
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import math
|
||||
|
||||
class Vector2d:
|
||||
typecode = 'd'
|
||||
|
||||
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))
|
||||
|
||||
def __repr__(self):
|
||||
class_name = type(self).__name__
|
||||
return '{}({!r}, {!r})'.format(class_name, *self)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes([ord(self.typecode)]) +
|
||||
bytes(array(self.typecode, self)))
|
||||
|
||||
def __eq__(self, other):
|
||||
return tuple(self) == tuple(other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.x, self.y))
|
||||
|
||||
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):
|
||||
typecode = chr(octets[0])
|
||||
memv = memoryview(octets[1:]).cast(typecode)
|
||||
return cls(*memv)
|
@ -1,161 +0,0 @@
|
||||
"""
|
||||
A 2-dimensional vector class
|
||||
|
||||
>>> v1 = Vector2d(3, 4)
|
||||
>>> print(v1.x, v1.y)
|
||||
3.0 4.0
|
||||
>>> x, y = v1
|
||||
>>> x, y
|
||||
(3.0, 4.0)
|
||||
>>> v1
|
||||
Vector2d(3.0, 4.0)
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0)
|
||||
>>> octets = bytes(v1)
|
||||
>>> octets
|
||||
b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
|
||||
>>> abs(v1)
|
||||
5.0
|
||||
>>> bool(v1), bool(Vector2d(0, 0))
|
||||
(True, False)
|
||||
|
||||
|
||||
Test of ``.frombytes()`` class method:
|
||||
|
||||
>>> v1_clone = Vector2d.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector2d(3.0, 4.0)
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
|
||||
Tests of ``format()`` with Cartesian 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::
|
||||
|
||||
>>> Vector2d(0, 0).angle()
|
||||
0.0
|
||||
>>> Vector2d(1, 0).angle()
|
||||
0.0
|
||||
>>> epsilon = 10**-8
|
||||
>>> abs(Vector2d(0, 1).angle() - math.pi/2) < epsilon
|
||||
True
|
||||
>>> abs(Vector2d(1, 1).angle() - math.pi/4) < epsilon
|
||||
True
|
||||
|
||||
|
||||
Tests of ``format()`` with polar coordinates:
|
||||
|
||||
>>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS
|
||||
'<1.414213..., 0.785398...>'
|
||||
>>> format(Vector2d(1, 1), '.3ep')
|
||||
'<1.414e+00, 7.854e-01>'
|
||||
>>> format(Vector2d(1, 1), '0.5fp')
|
||||
'<1.41421, 0.78540>'
|
||||
|
||||
# BEGIN VECTOR2D_V3_DEMO
|
||||
Tests 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 VECTOR2D_V3_HASH_DEMO
|
||||
|
||||
Tests of hashing:
|
||||
# BEGIN VECTOR2D_V3_HASH_DEMO
|
||||
|
||||
>>> v1 = Vector2d(3, 4)
|
||||
>>> v2 = Vector2d(3.1, 4.2)
|
||||
>>> hash(v1) != hash(v2)
|
||||
True
|
||||
>>> len(set([v1, v2]))
|
||||
2
|
||||
|
||||
# END VECTOR2D_V3_DEMO
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import math
|
||||
|
||||
# BEGIN VECTOR2D_V3_PROP
|
||||
class Vector2d:
|
||||
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 VECTOR2D_V3_PROP
|
||||
|
||||
def __repr__(self):
|
||||
class_name = type(self).__name__
|
||||
return '{}({!r}, {!r})'.format(class_name, *self)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes([ord(self.typecode)]) +
|
||||
bytes(array(self.typecode, self)))
|
||||
|
||||
def __eq__(self, other):
|
||||
return tuple(self) == tuple(other)
|
||||
|
||||
# BEGIN VECTOR_V3_HASH
|
||||
def __hash__(self):
|
||||
return hash((self.x, 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):
|
||||
typecode = chr(octets[0])
|
||||
memv = memoryview(octets[1:]).cast(typecode)
|
||||
return cls(*memv)
|
@ -1,158 +0,0 @@
|
||||
"""
|
||||
A 2-dimensional vector class
|
||||
|
||||
>>> v1 = Vector2d(3, 4)
|
||||
>>> print(v1.x, v1.y)
|
||||
3.0 4.0
|
||||
>>> x, y = v1
|
||||
>>> x, y
|
||||
(3.0, 4.0)
|
||||
>>> v1
|
||||
Vector2d(3.0, 4.0)
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0)
|
||||
>>> octets = bytes(v1)
|
||||
>>> octets
|
||||
b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
|
||||
>>> abs(v1)
|
||||
5.0
|
||||
>>> bool(v1), bool(Vector2d(0, 0))
|
||||
(True, False)
|
||||
|
||||
|
||||
Test of ``.frombytes()`` class method:
|
||||
|
||||
>>> v1_clone = Vector2d.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector2d(3.0, 4.0)
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
|
||||
Tests of ``format()`` with Cartesian 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::
|
||||
|
||||
>>> Vector2d(0, 0).angle()
|
||||
0.0
|
||||
>>> Vector2d(1, 0).angle()
|
||||
0.0
|
||||
>>> epsilon = 10**-8
|
||||
>>> abs(Vector2d(0, 1).angle() - math.pi/2) < epsilon
|
||||
True
|
||||
>>> abs(Vector2d(1, 1).angle() - math.pi/4) < epsilon
|
||||
True
|
||||
|
||||
|
||||
Tests of ``format()`` with polar coordinates:
|
||||
|
||||
>>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS
|
||||
'<1.414213..., 0.785398...>'
|
||||
>>> format(Vector2d(1, 1), '.3ep')
|
||||
'<1.414e+00, 7.854e-01>'
|
||||
>>> format(Vector2d(1, 1), '0.5fp')
|
||||
'<1.41421, 0.78540>'
|
||||
|
||||
|
||||
Tests 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
|
||||
|
||||
Tests of hashing:
|
||||
|
||||
>>> v1 = Vector2d(3, 4)
|
||||
>>> v2 = Vector2d(3.1, 4.2)
|
||||
>>> hash(v1) != hash(v2)
|
||||
True
|
||||
>>> len(set([v1, v2]))
|
||||
2
|
||||
|
||||
# END VECTOR2D_V3_DEMO
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import math
|
||||
|
||||
# BEGIN VECTOR2D_V3_SLOTS
|
||||
class Vector2d:
|
||||
__slots__ = ('__x', '__y')
|
||||
|
||||
typecode = 'd'
|
||||
|
||||
# methods follow (omitted in book listing)
|
||||
# END VECTOR2D_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))
|
||||
|
||||
def __repr__(self):
|
||||
class_name = type(self).__name__
|
||||
return '{}({!r}, {!r})'.format(class_name, *self)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes([ord(self.typecode)]) +
|
||||
bytes(array(self.typecode, self)))
|
||||
|
||||
def __eq__(self, other):
|
||||
return tuple(self) == tuple(other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.x, self.y))
|
||||
|
||||
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):
|
||||
typecode = chr(octets[0])
|
||||
memv = memoryview(octets[1:]).cast(typecode)
|
||||
return cls(*memv)
|
@ -1,4 +0,0 @@
|
||||
Sample code for Chapter 10 - "Sequence hacking, hashing and slicing"
|
||||
|
||||
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
|
||||
http://shop.oreilly.com/product/0636920032519.do
|
@ -1,126 +0,0 @@
|
||||
"""
|
||||
A multi-dimensional ``Vector`` class, take 1
|
||||
|
||||
A ``Vector`` is built from an iterable of numbers::
|
||||
|
||||
>>> Vector([3.1, 4.2])
|
||||
Vector([3.1, 4.2])
|
||||
>>> Vector((3, 4, 5))
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> Vector(range(10))
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
|
||||
|
||||
Tests with 2-dimensions (same results as ``vector2d_v1.py``)::
|
||||
|
||||
>>> 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'd\\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 with 3-dimensions::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> x, y, z = v1
|
||||
>>> x, y, z
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> v1
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> abs(v1) # doctest:+ELLIPSIS
|
||||
7.071067811...
|
||||
>>> bool(v1), bool(Vector([0, 0, 0]))
|
||||
(True, False)
|
||||
|
||||
|
||||
Tests with many dimensions::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
>>> abs(v7) # doctest:+ELLIPSIS
|
||||
9.53939201...
|
||||
|
||||
|
||||
Test of ``.__bytes__`` and ``.frombytes()`` methods::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> v1_clone = Vector.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN VECTOR_V1
|
||||
from array import array
|
||||
import reprlib
|
||||
import math
|
||||
|
||||
|
||||
class Vector:
|
||||
typecode = 'd'
|
||||
|
||||
def __init__(self, components):
|
||||
self._components = array(self.typecode, components) # <1>
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._components) # <2>
|
||||
|
||||
def __repr__(self):
|
||||
components = reprlib.repr(self._components) # <3>
|
||||
components = components[components.find('['):-1] # <4>
|
||||
return 'Vector({})'.format(components)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes([ord(self.typecode)]) +
|
||||
bytes(self._components)) # <5>
|
||||
|
||||
def __eq__(self, other):
|
||||
return tuple(self) == tuple(other)
|
||||
|
||||
def __abs__(self):
|
||||
return math.sqrt(sum(x * x for x in self)) # <6>
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
||||
@classmethod
|
||||
def frombytes(cls, octets):
|
||||
typecode = chr(octets[0])
|
||||
memv = memoryview(octets[1:]).cast(typecode)
|
||||
return cls(memv) # <7>
|
||||
# END VECTOR_V1
|
@ -1,167 +0,0 @@
|
||||
"""
|
||||
A multi-dimensional ``Vector`` class, take 2
|
||||
|
||||
A ``Vector`` is built from an iterable of numbers::
|
||||
|
||||
>>> Vector([3.1, 4.2])
|
||||
Vector([3.1, 4.2])
|
||||
>>> Vector((3, 4, 5))
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> Vector(range(10))
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
|
||||
|
||||
Tests with 2-dimensions (same results as ``vector2d_v1.py``)::
|
||||
|
||||
>>> 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'd\\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 with 3-dimensions::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> x, y, z = v1
|
||||
>>> x, y, z
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> v1
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> abs(v1) # doctest:+ELLIPSIS
|
||||
7.071067811...
|
||||
>>> bool(v1), bool(Vector([0, 0, 0]))
|
||||
(True, False)
|
||||
|
||||
|
||||
Tests with many dimensions::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
>>> abs(v7) # doctest:+ELLIPSIS
|
||||
9.53939201...
|
||||
|
||||
|
||||
Test of ``.__bytes__`` and ``.frombytes()`` methods::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> v1_clone = Vector.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
|
||||
Tests of sequence behavior::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> len(v1)
|
||||
3
|
||||
>>> v1[0], v1[len(v1)-1], v1[-1]
|
||||
(3.0, 5.0, 5.0)
|
||||
|
||||
|
||||
Test of slicing::
|
||||
|
||||
# BEGIN VECTOR_V2_DEMO
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7[-1] # <1>
|
||||
6.0
|
||||
>>> v7[1:4] # <2>
|
||||
Vector([1.0, 2.0, 3.0])
|
||||
>>> v7[-1:] # <3>
|
||||
Vector([6.0])
|
||||
>>> v7[1,2] # <4>
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Vector indices must be integers
|
||||
|
||||
# END VECTOR_V2_DEMO
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import reprlib
|
||||
import math
|
||||
import numbers
|
||||
|
||||
|
||||
class Vector:
|
||||
typecode = 'd'
|
||||
|
||||
def __init__(self, components):
|
||||
self._components = array(self.typecode, components)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._components)
|
||||
|
||||
def __repr__(self):
|
||||
components = reprlib.repr(self._components)
|
||||
components = components[components.find('['):-1]
|
||||
return 'Vector({})'.format(components)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes([ord(self.typecode)]) +
|
||||
bytes(self._components))
|
||||
|
||||
def __eq__(self, other):
|
||||
return tuple(self) == tuple(other)
|
||||
|
||||
def __abs__(self):
|
||||
return math.sqrt(sum(x * x for x in self))
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
||||
# BEGIN VECTOR_V2
|
||||
def __len__(self):
|
||||
return len(self._components)
|
||||
|
||||
def __getitem__(self, index):
|
||||
cls = type(self) # <1>
|
||||
if isinstance(index, slice): # <2>
|
||||
return cls(self._components[index]) # <3>
|
||||
elif isinstance(index, numbers.Integral): # <4>
|
||||
return self._components[index] # <5>
|
||||
else:
|
||||
msg = '{cls.__name__} indices must be integers'
|
||||
raise TypeError(msg.format(cls=cls)) # <6>
|
||||
# END VECTOR_V2
|
||||
|
||||
@classmethod
|
||||
def frombytes(cls, octets):
|
||||
typecode = chr(octets[0])
|
||||
memv = memoryview(octets[1:]).cast(typecode)
|
||||
return cls(memv)
|
@ -1,238 +0,0 @@
|
||||
"""
|
||||
A multi-dimensional ``Vector`` class, take 3
|
||||
|
||||
A ``Vector`` is built from an iterable of numbers::
|
||||
|
||||
>>> Vector([3.1, 4.2])
|
||||
Vector([3.1, 4.2])
|
||||
>>> Vector((3, 4, 5))
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> Vector(range(10))
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
|
||||
|
||||
Tests with 2-dimensions (same results as ``vector2d_v1.py``)::
|
||||
|
||||
>>> 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'd\\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 with 3-dimensions::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> x, y, z = v1
|
||||
>>> x, y, z
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> v1
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> abs(v1) # doctest:+ELLIPSIS
|
||||
7.071067811...
|
||||
>>> bool(v1), bool(Vector([0, 0, 0]))
|
||||
(True, False)
|
||||
|
||||
|
||||
Tests with many dimensions::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
>>> abs(v7) # doctest:+ELLIPSIS
|
||||
9.53939201...
|
||||
|
||||
|
||||
Test of ``.__bytes__`` and ``.frombytes()`` methods::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> v1_clone = Vector.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
|
||||
Tests of sequence behavior::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> len(v1)
|
||||
3
|
||||
>>> v1[0], v1[len(v1)-1], v1[-1]
|
||||
(3.0, 5.0, 5.0)
|
||||
|
||||
|
||||
Test of slicing::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7[-1]
|
||||
6.0
|
||||
>>> v7[1:4]
|
||||
Vector([1.0, 2.0, 3.0])
|
||||
>>> v7[-1:]
|
||||
Vector([6.0])
|
||||
>>> v7[1,2]
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Vector indices must be integers
|
||||
|
||||
|
||||
Tests of dynamic attribute access::
|
||||
|
||||
>>> v7 = Vector(range(10))
|
||||
>>> v7.x
|
||||
0.0
|
||||
>>> v7.y, v7.z, v7.t
|
||||
(1.0, 2.0, 3.0)
|
||||
|
||||
|
||||
Dynamic attribute lookup failures::
|
||||
|
||||
>>> v7.k
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 'k'
|
||||
>>> v3 = Vector(range(3))
|
||||
>>> v3.t
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 't'
|
||||
>>> v3.spam
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 'spam'
|
||||
|
||||
|
||||
Tests of preventing attributes from 'a' to 'z'::
|
||||
|
||||
>>> v1.x = 7
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: readonly attribute 'x'
|
||||
>>> v1.w = 7
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: can't set attributes 'a' to 'z' in 'Vector'
|
||||
|
||||
Other attributes can be set::
|
||||
|
||||
>>> v1.X = 'albatross'
|
||||
>>> v1.X
|
||||
'albatross'
|
||||
>>> v1.ni = 'Ni!'
|
||||
>>> v1.ni
|
||||
'Ni!'
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import reprlib
|
||||
import math
|
||||
import numbers
|
||||
|
||||
|
||||
class Vector:
|
||||
typecode = 'd'
|
||||
|
||||
def __init__(self, components):
|
||||
self._components = array(self.typecode, components)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._components)
|
||||
|
||||
def __repr__(self):
|
||||
components = reprlib.repr(self._components)
|
||||
components = components[components.find('['):-1]
|
||||
return 'Vector({})'.format(components)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes([ord(self.typecode)]) +
|
||||
bytes(self._components))
|
||||
|
||||
def __eq__(self, other):
|
||||
return tuple(self) == tuple(other)
|
||||
|
||||
def __abs__(self):
|
||||
return math.sqrt(sum(x * x for x in self))
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
||||
def __len__(self):
|
||||
return len(self._components)
|
||||
|
||||
def __getitem__(self, index):
|
||||
cls = type(self)
|
||||
if isinstance(index, slice):
|
||||
return cls(self._components[index])
|
||||
elif isinstance(index, numbers.Integral):
|
||||
return self._components[index]
|
||||
else:
|
||||
msg = '{.__name__} indices must be integers'
|
||||
raise TypeError(msg.format(cls))
|
||||
|
||||
# BEGIN VECTOR_V3_GETATTR
|
||||
shortcut_names = 'xyzt'
|
||||
|
||||
def __getattr__(self, name):
|
||||
cls = type(self) # <1>
|
||||
if len(name) == 1: # <2>
|
||||
pos = cls.shortcut_names.find(name) # <3>
|
||||
if 0 <= pos < len(self._components): # <4>
|
||||
return self._components[pos]
|
||||
msg = '{.__name__!r} object has no attribute {!r}' # <5>
|
||||
raise AttributeError(msg.format(cls, name))
|
||||
# END VECTOR_V3_GETATTR
|
||||
|
||||
# BEGIN VECTOR_V3_SETATTR
|
||||
def __setattr__(self, name, value):
|
||||
cls = type(self)
|
||||
if len(name) == 1: # <1>
|
||||
if name in cls.shortcut_names: # <2>
|
||||
error = 'readonly attribute {attr_name!r}'
|
||||
elif name.islower(): # <3>
|
||||
error = "can't set attributes 'a' to 'z' in {cls_name!r}"
|
||||
else:
|
||||
error = '' # <4>
|
||||
if error: # <5>
|
||||
msg = error.format(cls_name=cls.__name__, attr_name=name)
|
||||
raise AttributeError(msg)
|
||||
super().__setattr__(name, value) # <6>
|
||||
|
||||
# END VECTOR_V3_SETATTR
|
||||
|
||||
@classmethod
|
||||
def frombytes(cls, octets):
|
||||
typecode = chr(octets[0])
|
||||
memv = memoryview(octets[1:]).cast(typecode)
|
||||
return cls(memv)
|
@ -1,221 +0,0 @@
|
||||
"""
|
||||
A multi-dimensional ``Vector`` class, take 4
|
||||
|
||||
A ``Vector`` is built from an iterable of numbers::
|
||||
|
||||
>>> Vector([3.1, 4.2])
|
||||
Vector([3.1, 4.2])
|
||||
>>> Vector((3, 4, 5))
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> Vector(range(10))
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
|
||||
|
||||
Tests with 2-dimensions (same results as ``vector2d_v1.py``)::
|
||||
|
||||
>>> 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'd\\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 with 3-dimensions::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> x, y, z = v1
|
||||
>>> x, y, z
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> v1
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> abs(v1) # doctest:+ELLIPSIS
|
||||
7.071067811...
|
||||
>>> bool(v1), bool(Vector([0, 0, 0]))
|
||||
(True, False)
|
||||
|
||||
|
||||
Tests with many dimensions::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
>>> abs(v7) # doctest:+ELLIPSIS
|
||||
9.53939201...
|
||||
|
||||
|
||||
Test of ``.__bytes__`` and ``.frombytes()`` methods::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> v1_clone = Vector.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
|
||||
Tests of sequence behavior::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> len(v1)
|
||||
3
|
||||
>>> v1[0], v1[len(v1)-1], v1[-1]
|
||||
(3.0, 5.0, 5.0)
|
||||
|
||||
|
||||
Test of slicing::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7[-1]
|
||||
6.0
|
||||
>>> v7[1:4]
|
||||
Vector([1.0, 2.0, 3.0])
|
||||
>>> v7[-1:]
|
||||
Vector([6.0])
|
||||
>>> v7[1,2]
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Vector indices must be integers
|
||||
|
||||
|
||||
Tests of dynamic attribute access::
|
||||
|
||||
>>> v7 = Vector(range(10))
|
||||
>>> v7.x
|
||||
0.0
|
||||
>>> v7.y, v7.z, v7.t
|
||||
(1.0, 2.0, 3.0)
|
||||
|
||||
Dynamic attribute lookup failures::
|
||||
|
||||
>>> v7.k
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 'k'
|
||||
>>> v3 = Vector(range(3))
|
||||
>>> v3.t
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 't'
|
||||
>>> v3.spam
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 'spam'
|
||||
|
||||
|
||||
Tests of hashing::
|
||||
|
||||
>>> v1 = Vector([3, 4])
|
||||
>>> v2 = Vector([3.1, 4.2])
|
||||
>>> v3 = Vector([3, 4, 5])
|
||||
>>> v6 = Vector(range(6))
|
||||
>>> hash(v1), hash(v3), hash(v6)
|
||||
(7, 2, 1)
|
||||
|
||||
|
||||
Most hash values of non-integers vary from a 32-bit to 64-bit CPython build::
|
||||
|
||||
>>> import sys
|
||||
>>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986)
|
||||
True
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import reprlib
|
||||
import math
|
||||
import numbers
|
||||
import functools
|
||||
import operator
|
||||
|
||||
|
||||
class Vector:
|
||||
typecode = 'd'
|
||||
|
||||
def __init__(self, components):
|
||||
self._components = array(self.typecode, components)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._components)
|
||||
|
||||
def __repr__(self):
|
||||
components = reprlib.repr(self._components)
|
||||
components = components[components.find('['):-1]
|
||||
return 'Vector({})'.format(components)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes([ord(self.typecode)]) +
|
||||
bytes(self._components))
|
||||
|
||||
def __eq__(self, other):
|
||||
return (len(self) == len(other) and
|
||||
all(a == b for a, b in zip(self, other)))
|
||||
|
||||
def __hash__(self):
|
||||
hashes = (hash(x) for x in self)
|
||||
return functools.reduce(operator.xor, hashes, 0)
|
||||
|
||||
def __abs__(self):
|
||||
return math.sqrt(sum(x * x for x in self))
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
||||
def __len__(self):
|
||||
return len(self._components)
|
||||
|
||||
def __getitem__(self, index):
|
||||
cls = type(self)
|
||||
if isinstance(index, slice):
|
||||
return cls(self._components[index])
|
||||
elif isinstance(index, numbers.Integral):
|
||||
return self._components[index]
|
||||
else:
|
||||
msg = '{cls.__name__} indices must be integers'
|
||||
raise TypeError(msg.format(cls=cls))
|
||||
|
||||
shortcut_names = 'xyzt'
|
||||
|
||||
def __getattr__(self, name):
|
||||
cls = type(self)
|
||||
if len(name) == 1:
|
||||
pos = cls.shortcut_names.find(name)
|
||||
if 0 <= pos < len(self._components):
|
||||
return self._components[pos]
|
||||
msg = '{.__name__!r} object has no attribute {!r}'
|
||||
raise AttributeError(msg.format(cls, name))
|
||||
|
||||
@classmethod
|
||||
def frombytes(cls, octets):
|
||||
typecode = chr(octets[0])
|
||||
memv = memoryview(octets[1:]).cast(typecode)
|
||||
return cls(memv)
|
@ -1,288 +0,0 @@
|
||||
# BEGIN VECTOR_V5
|
||||
"""
|
||||
A multi-dimensional ``Vector`` class, take 5
|
||||
|
||||
A ``Vector`` is built from an iterable of numbers::
|
||||
|
||||
>>> Vector([3.1, 4.2])
|
||||
Vector([3.1, 4.2])
|
||||
>>> Vector((3, 4, 5))
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> Vector(range(10))
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
|
||||
|
||||
Tests with 2-dimensions (same results as ``vector2d_v1.py``)::
|
||||
|
||||
>>> 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'd\\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 with 3-dimensions::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> x, y, z = v1
|
||||
>>> x, y, z
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> v1
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> abs(v1) # doctest:+ELLIPSIS
|
||||
7.071067811...
|
||||
>>> bool(v1), bool(Vector([0, 0, 0]))
|
||||
(True, False)
|
||||
|
||||
|
||||
Tests with many dimensions::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
>>> abs(v7) # doctest:+ELLIPSIS
|
||||
9.53939201...
|
||||
|
||||
|
||||
Test of ``.__bytes__`` and ``.frombytes()`` methods::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> v1_clone = Vector.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
|
||||
Tests of sequence behavior::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> len(v1)
|
||||
3
|
||||
>>> v1[0], v1[len(v1)-1], v1[-1]
|
||||
(3.0, 5.0, 5.0)
|
||||
|
||||
|
||||
Test of slicing::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7[-1]
|
||||
6.0
|
||||
>>> v7[1:4]
|
||||
Vector([1.0, 2.0, 3.0])
|
||||
>>> v7[-1:]
|
||||
Vector([6.0])
|
||||
>>> v7[1,2]
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Vector indices must be integers
|
||||
|
||||
|
||||
Tests of dynamic attribute access::
|
||||
|
||||
>>> v7 = Vector(range(10))
|
||||
>>> v7.x
|
||||
0.0
|
||||
>>> v7.y, v7.z, v7.t
|
||||
(1.0, 2.0, 3.0)
|
||||
|
||||
Dynamic attribute lookup failures::
|
||||
|
||||
>>> v7.k
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 'k'
|
||||
>>> v3 = Vector(range(3))
|
||||
>>> v3.t
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 't'
|
||||
>>> v3.spam
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 'spam'
|
||||
|
||||
|
||||
Tests of hashing::
|
||||
|
||||
>>> v1 = Vector([3, 4])
|
||||
>>> v2 = Vector([3.1, 4.2])
|
||||
>>> v3 = Vector([3, 4, 5])
|
||||
>>> v6 = Vector(range(6))
|
||||
>>> hash(v1), hash(v3), hash(v6)
|
||||
(7, 2, 1)
|
||||
|
||||
|
||||
Most hash values of non-integers vary from a 32-bit to 64-bit CPython build::
|
||||
|
||||
>>> import sys
|
||||
>>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986)
|
||||
True
|
||||
|
||||
|
||||
Tests of ``format()`` with Cartesian coordinates in 2D::
|
||||
|
||||
>>> v1 = Vector([3, 4])
|
||||
>>> format(v1)
|
||||
'(3.0, 4.0)'
|
||||
>>> format(v1, '.2f')
|
||||
'(3.00, 4.00)'
|
||||
>>> format(v1, '.3e')
|
||||
'(3.000e+00, 4.000e+00)'
|
||||
|
||||
|
||||
Tests of ``format()`` with Cartesian coordinates in 3D and 7D::
|
||||
|
||||
>>> v3 = Vector([3, 4, 5])
|
||||
>>> format(v3)
|
||||
'(3.0, 4.0, 5.0)'
|
||||
>>> format(Vector(range(7)))
|
||||
'(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)'
|
||||
|
||||
|
||||
Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D::
|
||||
|
||||
>>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS
|
||||
'<1.414213..., 0.785398...>'
|
||||
>>> format(Vector([1, 1]), '.3eh')
|
||||
'<1.414e+00, 7.854e-01>'
|
||||
>>> format(Vector([1, 1]), '0.5fh')
|
||||
'<1.41421, 0.78540>'
|
||||
>>> format(Vector([1, 1, 1]), 'h') # doctest:+ELLIPSIS
|
||||
'<1.73205..., 0.95531..., 0.78539...>'
|
||||
>>> format(Vector([2, 2, 2]), '.3eh')
|
||||
'<3.464e+00, 9.553e-01, 7.854e-01>'
|
||||
>>> format(Vector([0, 0, 0]), '0.5fh')
|
||||
'<0.00000, 0.00000, 0.00000>'
|
||||
>>> format(Vector([-1, -1, -1, -1]), 'h') # doctest:+ELLIPSIS
|
||||
'<2.0, 2.09439..., 2.18627..., 3.92699...>'
|
||||
>>> format(Vector([2, 2, 2, 2]), '.3eh')
|
||||
'<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>'
|
||||
>>> format(Vector([0, 1, 0, 0]), '0.5fh')
|
||||
'<1.00000, 1.57080, 0.00000, 0.00000>'
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import reprlib
|
||||
import math
|
||||
import numbers
|
||||
import functools
|
||||
import operator
|
||||
import itertools # <1>
|
||||
|
||||
|
||||
class Vector:
|
||||
typecode = 'd'
|
||||
|
||||
def __init__(self, components):
|
||||
self._components = array(self.typecode, components)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._components)
|
||||
|
||||
def __repr__(self):
|
||||
components = reprlib.repr(self._components)
|
||||
components = components[components.find('['):-1]
|
||||
return 'Vector({})'.format(components)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes([ord(self.typecode)]) +
|
||||
bytes(self._components))
|
||||
|
||||
def __eq__(self, other):
|
||||
return (len(self) == len(other) and
|
||||
all(a == b for a, b in zip(self, other)))
|
||||
|
||||
def __hash__(self):
|
||||
hashes = (hash(x) for x in self)
|
||||
return functools.reduce(operator.xor, hashes, 0)
|
||||
|
||||
def __abs__(self):
|
||||
return math.sqrt(sum(x * x for x in self))
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
||||
def __len__(self):
|
||||
return len(self._components)
|
||||
|
||||
def __getitem__(self, index):
|
||||
cls = type(self)
|
||||
if isinstance(index, slice):
|
||||
return cls(self._components[index])
|
||||
elif isinstance(index, numbers.Integral):
|
||||
return self._components[index]
|
||||
else:
|
||||
msg = '{.__name__} indices must be integers'
|
||||
raise TypeError(msg.format(cls))
|
||||
|
||||
shortcut_names = 'xyzt'
|
||||
|
||||
def __getattr__(self, name):
|
||||
cls = type(self)
|
||||
if len(name) == 1:
|
||||
pos = cls.shortcut_names.find(name)
|
||||
if 0 <= pos < len(self._components):
|
||||
return self._components[pos]
|
||||
msg = '{.__name__!r} object has no attribute {!r}'
|
||||
raise AttributeError(msg.format(cls, name))
|
||||
|
||||
def angle(self, n): # <2>
|
||||
r = math.sqrt(sum(x * x for x in self[n:]))
|
||||
a = math.atan2(r, self[n-1])
|
||||
if (n == len(self) - 1) and (self[-1] < 0):
|
||||
return math.pi * 2 - a
|
||||
else:
|
||||
return a
|
||||
|
||||
def angles(self): # <3>
|
||||
return (self.angle(n) for n in range(1, len(self)))
|
||||
|
||||
def __format__(self, fmt_spec=''):
|
||||
if fmt_spec.endswith('h'): # hyperspherical coordinates
|
||||
fmt_spec = fmt_spec[:-1]
|
||||
coords = itertools.chain([abs(self)],
|
||||
self.angles()) # <4>
|
||||
outer_fmt = '<{}>' # <5>
|
||||
else:
|
||||
coords = self
|
||||
outer_fmt = '({})' # <6>
|
||||
components = (format(c, fmt_spec) for c in coords) # <7>
|
||||
return outer_fmt.format(', '.join(components)) # <8>
|
||||
|
||||
@classmethod
|
||||
def frombytes(cls, octets):
|
||||
typecode = chr(octets[0])
|
||||
memv = memoryview(octets[1:]).cast(typecode)
|
||||
return cls(memv)
|
||||
# END VECTOR_V5
|
@ -1,4 +0,0 @@
|
||||
Sample code for Chapter 11 - "Interfaces, protocols and ABCs"
|
||||
|
||||
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
|
||||
http://shop.oreilly.com/product/0636920032519.do
|
@ -1,28 +0,0 @@
|
||||
# BEGIN TOMBOLA_BINGO
|
||||
|
||||
import random
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
|
||||
class BingoCage(Tombola): # <1>
|
||||
|
||||
def __init__(self, items):
|
||||
self._randomizer = random.SystemRandom() # <2>
|
||||
self._items = []
|
||||
self.load(items) # <3>
|
||||
|
||||
def load(self, items):
|
||||
self._items.extend(items)
|
||||
self._randomizer.shuffle(self._items) # <4>
|
||||
|
||||
def pick(self): # <5>
|
||||
try:
|
||||
return self._items.pop()
|
||||
except IndexError:
|
||||
raise LookupError('pick from empty BingoCage')
|
||||
|
||||
def __call__(self): # <7>
|
||||
self.pick()
|
||||
|
||||
# END TOMBOLA_BINGO
|
@ -1,17 +0,0 @@
|
||||
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 pick(self):
|
||||
return self._balls.pop()
|
@ -1,26 +0,0 @@
|
||||
import collections
|
||||
|
||||
Card = collections.namedtuple('Card', ['rank', 'suit'])
|
||||
|
||||
class FrenchDeck2(collections.MutableSequence):
|
||||
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]
|
||||
|
||||
def __setitem__(self, position, value): # <1>
|
||||
self._cards[position] = value
|
||||
|
||||
def __delitem__(self, position): # <2>
|
||||
del self._cards[position]
|
||||
|
||||
def insert(self, position, value): # <3>
|
||||
self._cards.insert(position, value)
|
@ -1,30 +0,0 @@
|
||||
# BEGIN LOTTERY_BLOWER
|
||||
|
||||
import random
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
|
||||
class LotteryBlower(Tombola):
|
||||
|
||||
def __init__(self, iterable):
|
||||
self._balls = list(iterable) # <1>
|
||||
|
||||
def load(self, iterable):
|
||||
self._balls.extend(iterable)
|
||||
|
||||
def pick(self):
|
||||
try:
|
||||
position = random.randrange(len(self._balls)) # <2>
|
||||
except ValueError:
|
||||
raise LookupError('pick from empty BingoCage')
|
||||
return self._balls.pop(position) # <3>
|
||||
|
||||
def loaded(self): # <4>
|
||||
return bool(self._balls)
|
||||
|
||||
def inspect(self): # <5>
|
||||
return tuple(sorted(self._balls))
|
||||
|
||||
|
||||
# END LOTTERY_BLOWER
|
@ -1,35 +0,0 @@
|
||||
# BEGIN TOMBOLA_ABC
|
||||
|
||||
import abc
|
||||
|
||||
class Tombola(abc.ABC): # <1>
|
||||
|
||||
@abc.abstractmethod
|
||||
def load(self, iterable): # <2>
|
||||
"""Add items from an iterable."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def pick(self): # <3>
|
||||
"""Remove item at random, returning it.
|
||||
|
||||
This method should raise `LookupError` when the instance is empty.
|
||||
"""
|
||||
|
||||
def loaded(self): # <4>
|
||||
"""Return `True` if there's at least 1 item, `False` otherwise."""
|
||||
return bool(self.inspect()) # <5>
|
||||
|
||||
|
||||
def inspect(self):
|
||||
"""Return a sorted tuple with the items currently inside."""
|
||||
items = []
|
||||
while True: # <6>
|
||||
try:
|
||||
items.append(self.pick())
|
||||
except LookupError:
|
||||
break
|
||||
self.load(items) # <7>
|
||||
return tuple(sorted(items))
|
||||
|
||||
|
||||
# END TOMBOLA_ABC
|
@ -1,36 +0,0 @@
|
||||
# BEGIN TOMBOLA_RUNNER
|
||||
import doctest
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
# modules to test
|
||||
import bingo, lotto, tombolist, drum # <1>
|
||||
|
||||
TEST_FILE = 'tombola_tests.rst'
|
||||
TEST_MSG = '{0:16} {1.attempted:2} tests, {1.failed:2} failed - {2}'
|
||||
|
||||
|
||||
def main(argv):
|
||||
verbose = '-v' in argv
|
||||
real_subclasses = Tombola.__subclasses__() # <2>
|
||||
virtual_subclasses = list(Tombola._abc_registry) # <3>
|
||||
|
||||
for cls in real_subclasses + virtual_subclasses: # <4>
|
||||
test(cls, verbose)
|
||||
|
||||
|
||||
def test(cls, verbose=False):
|
||||
|
||||
res = doctest.testfile(
|
||||
TEST_FILE,
|
||||
globs={'ConcreteTombola': cls}, # <5>
|
||||
verbose=verbose,
|
||||
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)
|
||||
tag = 'FAIL' if res.failed else 'OK'
|
||||
print(TEST_MSG.format(cls.__name__, res, tag)) # <6>
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
main(sys.argv)
|
||||
# END TOMBOLA_RUNNER
|
@ -1,64 +0,0 @@
|
||||
"""
|
||||
Variation of ``tombola.Tombola`` implementing ``__subclasshook__``.
|
||||
|
||||
Tests with simple classes::
|
||||
|
||||
>>> Tombola.__subclasshook__(object)
|
||||
NotImplemented
|
||||
>>> class Complete:
|
||||
... def __init__(): pass
|
||||
... def load(): pass
|
||||
... def pick(): pass
|
||||
... def loaded(): pass
|
||||
...
|
||||
>>> Tombola.__subclasshook__(Complete)
|
||||
True
|
||||
>>> issubclass(Complete, Tombola)
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from inspect import getmembers, isfunction
|
||||
|
||||
|
||||
class Tombola(ABC): # <1>
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, iterable): # <2>
|
||||
"""New instance is loaded from an iterable."""
|
||||
|
||||
@abstractmethod
|
||||
def load(self, iterable):
|
||||
"""Add items from an iterable."""
|
||||
|
||||
@abstractmethod
|
||||
def pick(self): # <3>
|
||||
"""Remove item at random, returning it.
|
||||
|
||||
This method should raise `LookupError` when the instance is empty.
|
||||
"""
|
||||
|
||||
def loaded(self): # <4>
|
||||
try:
|
||||
item = self.pick()
|
||||
except LookupError:
|
||||
return False
|
||||
else:
|
||||
self.load([item]) # put it back
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def __subclasshook__(cls, other_cls):
|
||||
if cls is Tombola:
|
||||
interface_names = function_names(cls)
|
||||
found_names = set()
|
||||
for a_cls in other_cls.__mro__:
|
||||
found_names |= function_names(a_cls)
|
||||
if found_names >= interface_names:
|
||||
return True
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def function_names(obj):
|
||||
return {name for name, _ in getmembers(obj, isfunction)}
|
@ -1,82 +0,0 @@
|
||||
==============
|
||||
Tombola tests
|
||||
==============
|
||||
|
||||
Every concrete subclass of Tombola should pass these tests.
|
||||
|
||||
|
||||
Create and load instance from iterable::
|
||||
|
||||
>>> balls = list(range(3))
|
||||
>>> globe = ConcreteTombola(balls)
|
||||
>>> globe.loaded()
|
||||
True
|
||||
>>> globe.inspect()
|
||||
(0, 1, 2)
|
||||
|
||||
|
||||
Pick and collect balls::
|
||||
|
||||
>>> picks = []
|
||||
>>> picks.append(globe.pick())
|
||||
>>> picks.append(globe.pick())
|
||||
>>> picks.append(globe.pick())
|
||||
|
||||
|
||||
Check state and results::
|
||||
|
||||
>>> globe.loaded()
|
||||
False
|
||||
>>> sorted(picks) == balls
|
||||
True
|
||||
|
||||
|
||||
Reload::
|
||||
|
||||
>>> globe.load(balls)
|
||||
>>> globe.loaded()
|
||||
True
|
||||
>>> picks = [globe.pick() for i in balls]
|
||||
>>> globe.loaded()
|
||||
False
|
||||
|
||||
|
||||
Check that `LookupError` (or a subclass) is the exception
|
||||
thrown when the device is empty::
|
||||
|
||||
>>> globe = ConcreteTombola([])
|
||||
>>> try:
|
||||
... globe.pick()
|
||||
... except LookupError as exc:
|
||||
... print('OK')
|
||||
OK
|
||||
|
||||
|
||||
Load and pick 100 balls to verify that they all come out::
|
||||
|
||||
>>> balls = list(range(100))
|
||||
>>> globe = ConcreteTombola(balls)
|
||||
>>> picks = []
|
||||
>>> while globe.inspect():
|
||||
... picks.append(globe.pick())
|
||||
>>> len(picks) == len(balls)
|
||||
True
|
||||
>>> set(picks) == set(balls)
|
||||
True
|
||||
|
||||
|
||||
Check that the order has changed and is not simply reversed::
|
||||
|
||||
>>> picks != balls
|
||||
True
|
||||
>>> picks[::-1] != balls
|
||||
True
|
||||
|
||||
Note: the previous 2 tests have a *very* small chance of failing
|
||||
even if the implementation is OK. The probability of the 100
|
||||
balls coming out, by chance, in the order they were inspect is
|
||||
1/100!, or approximately 1.07e-158. It's much easier to win the
|
||||
Lotto or to become a billionaire working as a programmer.
|
||||
|
||||
THE END
|
||||
|
@ -1,23 +0,0 @@
|
||||
from random import randrange
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
@Tombola.register # <1>
|
||||
class TomboList(list): # <2>
|
||||
|
||||
def pick(self):
|
||||
if self: # <3>
|
||||
position = randrange(len(self))
|
||||
return self.pop(position) # <4>
|
||||
else:
|
||||
raise LookupError('pop from empty TomboList')
|
||||
|
||||
load = list.extend # <5>
|
||||
|
||||
def loaded(self):
|
||||
return bool(self) # <6>
|
||||
|
||||
def inspect(self):
|
||||
return tuple(sorted(self))
|
||||
|
||||
# Tombola.register(TomboList) # <7>
|
@ -1,4 +0,0 @@
|
||||
Sample code for Chapter 12 - "Inheritance: for good or for worse"
|
||||
|
||||
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
|
||||
http://shop.oreilly.com/product/0636920032519.do
|
@ -1,27 +0,0 @@
|
||||
class A:
|
||||
def ping(self):
|
||||
print('ping:', self)
|
||||
|
||||
|
||||
class B(A):
|
||||
def pong(self):
|
||||
print('pong:', self)
|
||||
|
||||
|
||||
class C(A):
|
||||
def pong(self):
|
||||
print('PONG:', self)
|
||||
|
||||
|
||||
class D(B, C):
|
||||
|
||||
def ping(self):
|
||||
super().ping()
|
||||
print('post-ping:', self)
|
||||
|
||||
def pingpong(self):
|
||||
self.ping()
|
||||
super().ping()
|
||||
self.pong()
|
||||
super().pong()
|
||||
C.pong(self)
|
@ -1 +0,0 @@
|
||||
== Type Hints Distilled
|
@ -1,4 +0,0 @@
|
||||
Sample code for Chapter 13 - "Operator overloading: doing it right"
|
||||
|
||||
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
|
||||
http://shop.oreilly.com/product/0636920032519.do
|
@ -1,28 +0,0 @@
|
||||
# BEGIN TOMBOLA_BINGO
|
||||
|
||||
import random
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
|
||||
class BingoCage(Tombola): # <1>
|
||||
|
||||
def __init__(self, items):
|
||||
self._randomizer = random.SystemRandom() # <2>
|
||||
self._items = []
|
||||
self.load(items) # <3>
|
||||
|
||||
def load(self, items):
|
||||
self._items.extend(items)
|
||||
self._randomizer.shuffle(self._items) # <4>
|
||||
|
||||
def pick(self): # <5>
|
||||
try:
|
||||
return self._items.pop()
|
||||
except IndexError:
|
||||
raise LookupError('pick from empty BingoCage')
|
||||
|
||||
def __call__(self): # <7>
|
||||
self.pick()
|
||||
|
||||
# END TOMBOLA_BINGO
|
@ -1,86 +0,0 @@
|
||||
"""
|
||||
======================
|
||||
AddableBingoCage tests
|
||||
======================
|
||||
|
||||
|
||||
Tests for __add__:
|
||||
|
||||
# BEGIN ADDABLE_BINGO_ADD_DEMO
|
||||
|
||||
>>> vowels = 'AEIOU'
|
||||
>>> globe = AddableBingoCage(vowels) # <1>
|
||||
>>> globe.inspect()
|
||||
('A', 'E', 'I', 'O', 'U')
|
||||
>>> globe.pick() in vowels # <2>
|
||||
True
|
||||
>>> len(globe.inspect()) # <3>
|
||||
4
|
||||
>>> globe2 = AddableBingoCage('XYZ') # <4>
|
||||
>>> globe3 = globe + globe2
|
||||
>>> len(globe3.inspect()) # <5>
|
||||
7
|
||||
>>> void = globe + [10, 20] # <6>
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: unsupported operand type(s) for +: 'AddableBingoCage' and 'list'
|
||||
|
||||
|
||||
# END ADDABLE_BINGO_ADD_DEMO
|
||||
|
||||
Tests for __iadd__:
|
||||
|
||||
# BEGIN ADDABLE_BINGO_IADD_DEMO
|
||||
|
||||
>>> globe_orig = globe # <1>
|
||||
>>> len(globe.inspect()) # <2>
|
||||
4
|
||||
>>> globe += globe2 # <3>
|
||||
>>> len(globe.inspect())
|
||||
7
|
||||
>>> globe += ['M', 'N'] # <4>
|
||||
>>> len(globe.inspect())
|
||||
9
|
||||
>>> globe is globe_orig # <5>
|
||||
True
|
||||
>>> globe += 1 # <6>
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: right operand in += must be 'AddableBingoCage' or an iterable
|
||||
|
||||
# END ADDABLE_BINGO_IADD_DEMO
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN ADDABLE_BINGO
|
||||
import itertools # <1>
|
||||
|
||||
from tombola import Tombola
|
||||
from bingo import BingoCage
|
||||
|
||||
|
||||
class AddableBingoCage(BingoCage): # <2>
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, Tombola): # <3>
|
||||
return AddableBingoCage(self.inspect() + other.inspect()) # <6>
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __iadd__(self, other):
|
||||
if isinstance(other, Tombola):
|
||||
other_iterable = other.inspect() # <4>
|
||||
else:
|
||||
try:
|
||||
other_iterable = iter(other) # <5>
|
||||
except TypeError: # <6>
|
||||
self_cls = type(self).__name__
|
||||
msg = "right operand in += must be {!r} or an iterable"
|
||||
raise TypeError(msg.format(self_cls))
|
||||
self.load(other_iterable) # <7>
|
||||
return self # <8>
|
||||
|
||||
|
||||
|
||||
|
||||
# END ADDABLE_BINGO
|
@ -1,35 +0,0 @@
|
||||
# BEGIN TOMBOLA_ABC
|
||||
|
||||
import abc
|
||||
|
||||
class Tombola(abc.ABC): # <1>
|
||||
|
||||
@abc.abstractmethod
|
||||
def load(self, iterable): # <2>
|
||||
"""Add items from an iterable."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def pick(self): # <3>
|
||||
"""Remove item at random, returning it.
|
||||
|
||||
This method should raise `LookupError` when the instance is empty.
|
||||
"""
|
||||
|
||||
def loaded(self): # <4>
|
||||
"""Return `True` if there's at least 1 item, `False` otherwise."""
|
||||
return bool(self.inspect()) # <5>
|
||||
|
||||
|
||||
def inspect(self):
|
||||
"""Return a sorted tuple with the items currently inside."""
|
||||
items = []
|
||||
while True: # <6>
|
||||
try:
|
||||
items.append(self.pick())
|
||||
except LookupError:
|
||||
break
|
||||
self.load(items) # <7>
|
||||
return tuple(sorted(items))
|
||||
|
||||
|
||||
# END TOMBOLA_ABC
|
@ -1,35 +0,0 @@
|
||||
"""
|
||||
# BEGIN UNARY_PLUS_DECIMAL
|
||||
|
||||
>>> import decimal
|
||||
>>> ctx = decimal.getcontext() # <1>
|
||||
>>> ctx.prec = 40 # <2>
|
||||
>>> one_third = decimal.Decimal('1') / decimal.Decimal('3') # <3>
|
||||
>>> one_third # <4>
|
||||
Decimal('0.3333333333333333333333333333333333333333')
|
||||
>>> one_third == +one_third # <5>
|
||||
True
|
||||
>>> ctx.prec = 28 # <6>
|
||||
>>> one_third == +one_third # <7>
|
||||
False
|
||||
>>> +one_third # <8>
|
||||
Decimal('0.3333333333333333333333333333')
|
||||
|
||||
# END UNARY_PLUS_DECIMAL
|
||||
|
||||
"""
|
||||
|
||||
import decimal
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
with decimal.localcontext() as ctx:
|
||||
ctx.prec = 40
|
||||
print('precision:', ctx.prec)
|
||||
one_third = decimal.Decimal('1') / decimal.Decimal('3')
|
||||
print(' one_third:', one_third)
|
||||
print(' +one_third:', +one_third)
|
||||
|
||||
print('precision:', decimal.getcontext().prec)
|
||||
print(' one_third:', one_third)
|
||||
print(' +one_third:', +one_third)
|
@ -1,431 +0,0 @@
|
||||
"""
|
||||
A multi-dimensional ``Vector`` class, take 9: operator ``@``
|
||||
|
||||
WARNING: This example requires Python 3.5 or later.
|
||||
|
||||
A ``Vector`` is built from an iterable of numbers::
|
||||
|
||||
>>> Vector([3.1, 4.2])
|
||||
Vector([3.1, 4.2])
|
||||
>>> Vector((3, 4, 5))
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> Vector(range(10))
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
|
||||
|
||||
Tests with 2-dimensions (same results as ``vector2d_v1.py``)::
|
||||
|
||||
>>> 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'd\\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 with 3-dimensions::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> x, y, z = v1
|
||||
>>> x, y, z
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> v1
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> abs(v1) # doctest:+ELLIPSIS
|
||||
7.071067811...
|
||||
>>> bool(v1), bool(Vector([0, 0, 0]))
|
||||
(True, False)
|
||||
|
||||
|
||||
Tests with many dimensions::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
>>> abs(v7) # doctest:+ELLIPSIS
|
||||
9.53939201...
|
||||
|
||||
|
||||
Test of ``.__bytes__`` and ``.frombytes()`` methods::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> v1_clone = Vector.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
|
||||
Tests of sequence behavior::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> len(v1)
|
||||
3
|
||||
>>> v1[0], v1[len(v1)-1], v1[-1]
|
||||
(3.0, 5.0, 5.0)
|
||||
|
||||
|
||||
Test of slicing::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7[-1]
|
||||
6.0
|
||||
>>> v7[1:4]
|
||||
Vector([1.0, 2.0, 3.0])
|
||||
>>> v7[-1:]
|
||||
Vector([6.0])
|
||||
>>> v7[1,2]
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Vector indices must be integers
|
||||
|
||||
|
||||
Tests of dynamic attribute access::
|
||||
|
||||
>>> v7 = Vector(range(10))
|
||||
>>> v7.x
|
||||
0.0
|
||||
>>> v7.y, v7.z, v7.t
|
||||
(1.0, 2.0, 3.0)
|
||||
|
||||
Dynamic attribute lookup failures::
|
||||
|
||||
>>> v7.k
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 'k'
|
||||
>>> v3 = Vector(range(3))
|
||||
>>> v3.t
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 't'
|
||||
>>> v3.spam
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 'spam'
|
||||
|
||||
|
||||
Tests of hashing::
|
||||
|
||||
>>> v1 = Vector([3, 4])
|
||||
>>> v2 = Vector([3.1, 4.2])
|
||||
>>> v3 = Vector([3, 4, 5])
|
||||
>>> v6 = Vector(range(6))
|
||||
>>> hash(v1), hash(v3), hash(v6)
|
||||
(7, 2, 1)
|
||||
|
||||
|
||||
Most hash values of non-integers vary from a 32-bit to 64-bit Python build::
|
||||
|
||||
>>> import sys
|
||||
>>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986)
|
||||
True
|
||||
|
||||
|
||||
Tests of ``format()`` with Cartesian coordinates in 2D::
|
||||
|
||||
>>> v1 = Vector([3, 4])
|
||||
>>> format(v1)
|
||||
'(3.0, 4.0)'
|
||||
>>> format(v1, '.2f')
|
||||
'(3.00, 4.00)'
|
||||
>>> format(v1, '.3e')
|
||||
'(3.000e+00, 4.000e+00)'
|
||||
|
||||
|
||||
Tests of ``format()`` with Cartesian coordinates in 3D and 7D::
|
||||
|
||||
>>> v3 = Vector([3, 4, 5])
|
||||
>>> format(v3)
|
||||
'(3.0, 4.0, 5.0)'
|
||||
>>> format(Vector(range(7)))
|
||||
'(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)'
|
||||
|
||||
|
||||
Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D::
|
||||
|
||||
>>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS
|
||||
'<1.414213..., 0.785398...>'
|
||||
>>> format(Vector([1, 1]), '.3eh')
|
||||
'<1.414e+00, 7.854e-01>'
|
||||
>>> format(Vector([1, 1]), '0.5fh')
|
||||
'<1.41421, 0.78540>'
|
||||
>>> format(Vector([1, 1, 1]), 'h') # doctest:+ELLIPSIS
|
||||
'<1.73205..., 0.95531..., 0.78539...>'
|
||||
>>> format(Vector([2, 2, 2]), '.3eh')
|
||||
'<3.464e+00, 9.553e-01, 7.854e-01>'
|
||||
>>> format(Vector([0, 0, 0]), '0.5fh')
|
||||
'<0.00000, 0.00000, 0.00000>'
|
||||
>>> format(Vector([-1, -1, -1, -1]), 'h') # doctest:+ELLIPSIS
|
||||
'<2.0, 2.09439..., 2.18627..., 3.92699...>'
|
||||
>>> format(Vector([2, 2, 2, 2]), '.3eh')
|
||||
'<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>'
|
||||
>>> format(Vector([0, 1, 0, 0]), '0.5fh')
|
||||
'<1.00000, 1.57080, 0.00000, 0.00000>'
|
||||
|
||||
|
||||
Basic tests of operator ``+``::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> v2 = Vector([6, 7, 8])
|
||||
>>> v1 + v2
|
||||
Vector([9.0, 11.0, 13.0])
|
||||
>>> v1 + v2 == Vector([3+6, 4+7, 5+8])
|
||||
True
|
||||
>>> v3 = Vector([1, 2])
|
||||
>>> v1 + v3 # short vectors are filled with 0.0 on addition
|
||||
Vector([4.0, 6.0, 5.0])
|
||||
|
||||
|
||||
Tests of ``+`` with mixed types::
|
||||
|
||||
>>> v1 + (10, 20, 30)
|
||||
Vector([13.0, 24.0, 35.0])
|
||||
>>> from vector2d_v3 import Vector2d
|
||||
>>> v2d = Vector2d(1, 2)
|
||||
>>> v1 + v2d
|
||||
Vector([4.0, 6.0, 5.0])
|
||||
|
||||
|
||||
Tests of ``+`` with mixed types, swapped operands::
|
||||
|
||||
>>> (10, 20, 30) + v1
|
||||
Vector([13.0, 24.0, 35.0])
|
||||
>>> from vector2d_v3 import Vector2d
|
||||
>>> v2d = Vector2d(1, 2)
|
||||
>>> v2d + v1
|
||||
Vector([4.0, 6.0, 5.0])
|
||||
|
||||
|
||||
Tests of ``+`` with an unsuitable operand:
|
||||
|
||||
>>> v1 + 1
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: unsupported operand type(s) for +: 'Vector' and 'int'
|
||||
>>> v1 + 'ABC'
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: unsupported operand type(s) for +: 'Vector' and 'str'
|
||||
|
||||
|
||||
Basic tests of operator ``*``::
|
||||
|
||||
>>> v1 = Vector([1, 2, 3])
|
||||
>>> v1 * 10
|
||||
Vector([10.0, 20.0, 30.0])
|
||||
>>> 10 * v1
|
||||
Vector([10.0, 20.0, 30.0])
|
||||
|
||||
|
||||
Tests of ``*`` with unusual but valid operands::
|
||||
|
||||
>>> v1 * True
|
||||
Vector([1.0, 2.0, 3.0])
|
||||
>>> from fractions import Fraction
|
||||
>>> v1 * Fraction(1, 3) # doctest:+ELLIPSIS
|
||||
Vector([0.3333..., 0.6666..., 1.0])
|
||||
|
||||
|
||||
Tests of ``*`` with unsuitable operands::
|
||||
|
||||
>>> v1 * (1, 2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: can't multiply sequence by non-int of type 'Vector'
|
||||
|
||||
|
||||
Tests of operator `==`::
|
||||
|
||||
>>> va = Vector(range(1, 4))
|
||||
>>> vb = Vector([1.0, 2.0, 3.0])
|
||||
>>> va == vb
|
||||
True
|
||||
>>> vc = Vector([1, 2])
|
||||
>>> from vector2d_v3 import Vector2d
|
||||
>>> v2d = Vector2d(1, 2)
|
||||
>>> vc == v2d
|
||||
True
|
||||
>>> va == (1, 2, 3)
|
||||
False
|
||||
|
||||
|
||||
Tests of operator `!=`::
|
||||
|
||||
>>> va != vb
|
||||
False
|
||||
>>> vc != v2d
|
||||
False
|
||||
>>> va != (1, 2, 3)
|
||||
True
|
||||
|
||||
|
||||
Tests for operator `@` (Python >= 3.5), computing the dot product::
|
||||
|
||||
>>> va = Vector([1, 2, 3])
|
||||
>>> vz = Vector([5, 6, 7])
|
||||
>>> va @ vz == 38.0 # 1*5 + 2*6 + 3*7
|
||||
True
|
||||
>>> [10, 20, 30] @ vz
|
||||
380.0
|
||||
>>> va @ 3
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: unsupported operand type(s) for @: 'Vector' and 'int'
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import reprlib
|
||||
import math
|
||||
import functools
|
||||
import operator
|
||||
import itertools
|
||||
import numbers
|
||||
|
||||
|
||||
class Vector:
|
||||
typecode = 'd'
|
||||
|
||||
def __init__(self, components):
|
||||
self._components = array(self.typecode, components)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._components)
|
||||
|
||||
def __repr__(self):
|
||||
components = reprlib.repr(self._components)
|
||||
components = components[components.find('['):-1]
|
||||
return 'Vector({})'.format(components)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes([ord(self.typecode)]) +
|
||||
bytes(self._components))
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Vector):
|
||||
return (len(self) == len(other) and
|
||||
all(a == b for a, b in zip(self, other)))
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __hash__(self):
|
||||
hashes = (hash(x) for x in self)
|
||||
return functools.reduce(operator.xor, hashes, 0)
|
||||
|
||||
def __abs__(self):
|
||||
return math.sqrt(sum(x * x for x in self))
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
||||
def __len__(self):
|
||||
return len(self._components)
|
||||
|
||||
def __getitem__(self, index):
|
||||
cls = type(self)
|
||||
if isinstance(index, slice):
|
||||
return cls(self._components[index])
|
||||
elif isinstance(index, int):
|
||||
return self._components[index]
|
||||
else:
|
||||
msg = '{.__name__} indices must be integers'
|
||||
raise TypeError(msg.format(cls))
|
||||
|
||||
shortcut_names = 'xyzt'
|
||||
|
||||
def __getattr__(self, name):
|
||||
cls = type(self)
|
||||
if len(name) == 1:
|
||||
pos = cls.shortcut_names.find(name)
|
||||
if 0 <= pos < len(self._components):
|
||||
return self._components[pos]
|
||||
msg = '{.__name__!r} object has no attribute {!r}'
|
||||
raise AttributeError(msg.format(cls, name))
|
||||
|
||||
def angle(self, n):
|
||||
r = math.sqrt(sum(x * x for x in self[n:]))
|
||||
a = math.atan2(r, self[n-1])
|
||||
if (n == len(self) - 1) and (self[-1] < 0):
|
||||
return math.pi * 2 - a
|
||||
else:
|
||||
return a
|
||||
|
||||
def angles(self):
|
||||
return (self.angle(n) for n in range(1, len(self)))
|
||||
|
||||
def __format__(self, fmt_spec=''):
|
||||
if fmt_spec.endswith('h'): # hyperspherical coordinates
|
||||
fmt_spec = fmt_spec[:-1]
|
||||
coords = itertools.chain([abs(self)],
|
||||
self.angles())
|
||||
outer_fmt = '<{}>'
|
||||
else:
|
||||
coords = self
|
||||
outer_fmt = '({})'
|
||||
components = (format(c, fmt_spec) for c in coords)
|
||||
return outer_fmt.format(', '.join(components))
|
||||
|
||||
@classmethod
|
||||
def frombytes(cls, octets):
|
||||
typecode = chr(octets[0])
|
||||
memv = memoryview(octets[1:]).cast(typecode)
|
||||
return cls(memv)
|
||||
|
||||
def __add__(self, other):
|
||||
try:
|
||||
pairs = itertools.zip_longest(self, other, fillvalue=0.0)
|
||||
return Vector(a + b for a, b in pairs)
|
||||
except TypeError:
|
||||
return NotImplemented
|
||||
|
||||
def __radd__(self, other):
|
||||
return self + other
|
||||
|
||||
def __mul__(self, scalar):
|
||||
if isinstance(scalar, numbers.Real):
|
||||
return Vector(n * scalar for n in self)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __rmul__(self, scalar):
|
||||
return self * scalar
|
||||
|
||||
def __matmul__(self, other):
|
||||
try:
|
||||
return sum(a * b for a, b in zip(self, other))
|
||||
except TypeError:
|
||||
return NotImplemented
|
||||
|
||||
def __rmatmul__(self, other):
|
||||
return self @ other # this only works in Python 3.5
|
@ -1,362 +0,0 @@
|
||||
"""
|
||||
A multi-dimensional ``Vector`` class, take 6: operator ``+``
|
||||
|
||||
A ``Vector`` is built from an iterable of numbers::
|
||||
|
||||
>>> Vector([3.1, 4.2])
|
||||
Vector([3.1, 4.2])
|
||||
>>> Vector((3, 4, 5))
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> Vector(range(10))
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
|
||||
|
||||
Tests with 2-dimensions (same results as ``vector2d_v1.py``)::
|
||||
|
||||
>>> 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'd\\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 with 3-dimensions::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> x, y, z = v1
|
||||
>>> x, y, z
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> v1
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> abs(v1) # doctest:+ELLIPSIS
|
||||
7.071067811...
|
||||
>>> bool(v1), bool(Vector([0, 0, 0]))
|
||||
(True, False)
|
||||
|
||||
|
||||
Tests with many dimensions::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
>>> abs(v7) # doctest:+ELLIPSIS
|
||||
9.53939201...
|
||||
|
||||
|
||||
Test of ``.__bytes__`` and ``.frombytes()`` methods::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> v1_clone = Vector.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
|
||||
Tests of sequence behavior::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> len(v1)
|
||||
3
|
||||
>>> v1[0], v1[len(v1)-1], v1[-1]
|
||||
(3.0, 5.0, 5.0)
|
||||
|
||||
|
||||
Test of slicing::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7[-1]
|
||||
6.0
|
||||
>>> v7[1:4]
|
||||
Vector([1.0, 2.0, 3.0])
|
||||
>>> v7[-1:]
|
||||
Vector([6.0])
|
||||
>>> v7[1,2]
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Vector indices must be integers
|
||||
|
||||
|
||||
Tests of dynamic attribute access::
|
||||
|
||||
>>> v7 = Vector(range(10))
|
||||
>>> v7.x
|
||||
0.0
|
||||
>>> v7.y, v7.z, v7.t
|
||||
(1.0, 2.0, 3.0)
|
||||
|
||||
Dynamic attribute lookup failures::
|
||||
|
||||
>>> v7.k
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 'k'
|
||||
>>> v3 = Vector(range(3))
|
||||
>>> v3.t
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 't'
|
||||
>>> v3.spam
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 'spam'
|
||||
|
||||
|
||||
Tests of hashing::
|
||||
|
||||
>>> v1 = Vector([3, 4])
|
||||
>>> v2 = Vector([3.1, 4.2])
|
||||
>>> v3 = Vector([3, 4, 5])
|
||||
>>> v6 = Vector(range(6))
|
||||
>>> hash(v1), hash(v3), hash(v6)
|
||||
(7, 2, 1)
|
||||
|
||||
|
||||
Most hash values of non-integers vary from a 32-bit to 64-bit Python build::
|
||||
|
||||
>>> import sys
|
||||
>>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986)
|
||||
True
|
||||
|
||||
|
||||
Tests of ``format()`` with Cartesian coordinates in 2D::
|
||||
|
||||
>>> v1 = Vector([3, 4])
|
||||
>>> format(v1)
|
||||
'(3.0, 4.0)'
|
||||
>>> format(v1, '.2f')
|
||||
'(3.00, 4.00)'
|
||||
>>> format(v1, '.3e')
|
||||
'(3.000e+00, 4.000e+00)'
|
||||
|
||||
|
||||
Tests of ``format()`` with Cartesian coordinates in 3D and 7D::
|
||||
|
||||
>>> v3 = Vector([3, 4, 5])
|
||||
>>> format(v3)
|
||||
'(3.0, 4.0, 5.0)'
|
||||
>>> format(Vector(range(7)))
|
||||
'(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)'
|
||||
|
||||
|
||||
Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D::
|
||||
|
||||
>>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS
|
||||
'<1.414213..., 0.785398...>'
|
||||
>>> format(Vector([1, 1]), '.3eh')
|
||||
'<1.414e+00, 7.854e-01>'
|
||||
>>> format(Vector([1, 1]), '0.5fh')
|
||||
'<1.41421, 0.78540>'
|
||||
>>> format(Vector([1, 1, 1]), 'h') # doctest:+ELLIPSIS
|
||||
'<1.73205..., 0.95531..., 0.78539...>'
|
||||
>>> format(Vector([2, 2, 2]), '.3eh')
|
||||
'<3.464e+00, 9.553e-01, 7.854e-01>'
|
||||
>>> format(Vector([0, 0, 0]), '0.5fh')
|
||||
'<0.00000, 0.00000, 0.00000>'
|
||||
>>> format(Vector([-1, -1, -1, -1]), 'h') # doctest:+ELLIPSIS
|
||||
'<2.0, 2.09439..., 2.18627..., 3.92699...>'
|
||||
>>> format(Vector([2, 2, 2, 2]), '.3eh')
|
||||
'<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>'
|
||||
>>> format(Vector([0, 1, 0, 0]), '0.5fh')
|
||||
'<1.00000, 1.57080, 0.00000, 0.00000>'
|
||||
|
||||
|
||||
Unary operator tests::
|
||||
|
||||
>>> v1 = Vector([3, 4])
|
||||
>>> abs(v1)
|
||||
5.0
|
||||
>>> -v1
|
||||
Vector([-3.0, -4.0])
|
||||
>>> +v1
|
||||
Vector([3.0, 4.0])
|
||||
|
||||
|
||||
Basic tests of operator ``+``::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> v2 = Vector([6, 7, 8])
|
||||
>>> v1 + v2
|
||||
Vector([9.0, 11.0, 13.0])
|
||||
>>> v1 + v2 == Vector([3+6, 4+7, 5+8])
|
||||
True
|
||||
>>> v3 = Vector([1, 2])
|
||||
>>> v1 + v3 # short vectors are filled with 0.0 on addition
|
||||
Vector([4.0, 6.0, 5.0])
|
||||
|
||||
|
||||
Tests of ``+`` with mixed types::
|
||||
|
||||
>>> v1 + (10, 20, 30)
|
||||
Vector([13.0, 24.0, 35.0])
|
||||
>>> from vector2d_v3 import Vector2d
|
||||
>>> v2d = Vector2d(1, 2)
|
||||
>>> v1 + v2d
|
||||
Vector([4.0, 6.0, 5.0])
|
||||
|
||||
|
||||
Tests of ``+`` with mixed types, swapped operands::
|
||||
|
||||
>>> (10, 20, 30) + v1
|
||||
Vector([13.0, 24.0, 35.0])
|
||||
>>> from vector2d_v3 import Vector2d
|
||||
>>> v2d = Vector2d(1, 2)
|
||||
>>> v2d + v1
|
||||
Vector([4.0, 6.0, 5.0])
|
||||
|
||||
|
||||
Tests of ``+`` with an unsuitable operand:
|
||||
|
||||
>>> v1 + 1
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: unsupported operand type(s) for +: 'Vector' and 'int'
|
||||
>>> v1 + 'ABC'
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: unsupported operand type(s) for +: 'Vector' and 'str'
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import reprlib
|
||||
import math
|
||||
import numbers
|
||||
import functools
|
||||
import operator
|
||||
import itertools
|
||||
|
||||
|
||||
class Vector:
|
||||
typecode = 'd'
|
||||
|
||||
def __init__(self, components):
|
||||
self._components = array(self.typecode, components)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._components)
|
||||
|
||||
def __repr__(self):
|
||||
components = reprlib.repr(self._components)
|
||||
components = components[components.find('['):-1]
|
||||
return 'Vector({})'.format(components)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes([ord(self.typecode)]) +
|
||||
bytes(self._components))
|
||||
|
||||
def __eq__(self, other):
|
||||
return (len(self) == len(other) and
|
||||
all(a == b for a, b in zip(self, other)))
|
||||
|
||||
def __hash__(self):
|
||||
hashes = (hash(x) for x in self)
|
||||
return functools.reduce(operator.xor, hashes, 0)
|
||||
|
||||
# BEGIN VECTOR_V6_UNARY
|
||||
def __abs__(self):
|
||||
return math.sqrt(sum(x * x for x in self))
|
||||
|
||||
def __neg__(self):
|
||||
return Vector(-x for x in self) # <1>
|
||||
|
||||
def __pos__(self):
|
||||
return Vector(self) # <2>
|
||||
# END VECTOR_V6_UNARY
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
||||
def __len__(self):
|
||||
return len(self._components)
|
||||
|
||||
def __getitem__(self, index):
|
||||
cls = type(self)
|
||||
if isinstance(index, slice):
|
||||
return cls(self._components[index])
|
||||
elif isinstance(index, numbers.Integral):
|
||||
return self._components[index]
|
||||
else:
|
||||
msg = '{.__name__} indices must be integers'
|
||||
raise TypeError(msg.format(cls))
|
||||
|
||||
shortcut_names = 'xyzt'
|
||||
|
||||
def __getattr__(self, name):
|
||||
cls = type(self)
|
||||
if len(name) == 1:
|
||||
pos = cls.shortcut_names.find(name)
|
||||
if 0 <= pos < len(self._components):
|
||||
return self._components[pos]
|
||||
msg = '{.__name__!r} object has no attribute {!r}'
|
||||
raise AttributeError(msg.format(cls, name))
|
||||
|
||||
def angle(self, n):
|
||||
r = math.sqrt(sum(x * x for x in self[n:]))
|
||||
a = math.atan2(r, self[n-1])
|
||||
if (n == len(self) - 1) and (self[-1] < 0):
|
||||
return math.pi * 2 - a
|
||||
else:
|
||||
return a
|
||||
|
||||
def angles(self):
|
||||
return (self.angle(n) for n in range(1, len(self)))
|
||||
|
||||
def __format__(self, fmt_spec=''):
|
||||
if fmt_spec.endswith('h'): # hyperspherical coordinates
|
||||
fmt_spec = fmt_spec[:-1]
|
||||
coords = itertools.chain([abs(self)],
|
||||
self.angles())
|
||||
outer_fmt = '<{}>'
|
||||
else:
|
||||
coords = self
|
||||
outer_fmt = '({})'
|
||||
components = (format(c, fmt_spec) for c in coords)
|
||||
return outer_fmt.format(', '.join(components))
|
||||
|
||||
@classmethod
|
||||
def frombytes(cls, octets):
|
||||
typecode = chr(octets[0])
|
||||
memv = memoryview(octets[1:]).cast(typecode)
|
||||
return cls(memv)
|
||||
|
||||
# BEGIN VECTOR_V6_ADD
|
||||
def __add__(self, other):
|
||||
try:
|
||||
pairs = itertools.zip_longest(self, other, fillvalue=0.0)
|
||||
return Vector(a + b for a, b in pairs)
|
||||
except TypeError:
|
||||
return NotImplemented
|
||||
|
||||
def __radd__(self, other):
|
||||
return self + other
|
||||
# END VECTOR_V6_ADD
|
@ -1,394 +0,0 @@
|
||||
"""
|
||||
A multi-dimensional ``Vector`` class, take 7: operator ``*``
|
||||
|
||||
A ``Vector`` is built from an iterable of numbers::
|
||||
|
||||
>>> Vector([3.1, 4.2])
|
||||
Vector([3.1, 4.2])
|
||||
>>> Vector((3, 4, 5))
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> Vector(range(10))
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
|
||||
|
||||
Tests with 2-dimensions (same results as ``vector2d_v1.py``)::
|
||||
|
||||
>>> 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'd\\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 with 3-dimensions::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> x, y, z = v1
|
||||
>>> x, y, z
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> v1
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> abs(v1) # doctest:+ELLIPSIS
|
||||
7.071067811...
|
||||
>>> bool(v1), bool(Vector([0, 0, 0]))
|
||||
(True, False)
|
||||
|
||||
|
||||
Tests with many dimensions::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
>>> abs(v7) # doctest:+ELLIPSIS
|
||||
9.53939201...
|
||||
|
||||
|
||||
Test of ``.__bytes__`` and ``.frombytes()`` methods::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> v1_clone = Vector.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
|
||||
Tests of sequence behavior::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> len(v1)
|
||||
3
|
||||
>>> v1[0], v1[len(v1)-1], v1[-1]
|
||||
(3.0, 5.0, 5.0)
|
||||
|
||||
|
||||
Test of slicing::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7[-1]
|
||||
6.0
|
||||
>>> v7[1:4]
|
||||
Vector([1.0, 2.0, 3.0])
|
||||
>>> v7[-1:]
|
||||
Vector([6.0])
|
||||
>>> v7[1,2]
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Vector indices must be integers
|
||||
|
||||
|
||||
Tests of dynamic attribute access::
|
||||
|
||||
>>> v7 = Vector(range(10))
|
||||
>>> v7.x
|
||||
0.0
|
||||
>>> v7.y, v7.z, v7.t
|
||||
(1.0, 2.0, 3.0)
|
||||
|
||||
Dynamic attribute lookup failures::
|
||||
|
||||
>>> v7.k
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 'k'
|
||||
>>> v3 = Vector(range(3))
|
||||
>>> v3.t
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 't'
|
||||
>>> v3.spam
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 'spam'
|
||||
|
||||
|
||||
Tests of hashing::
|
||||
|
||||
>>> v1 = Vector([3, 4])
|
||||
>>> v2 = Vector([3.1, 4.2])
|
||||
>>> v3 = Vector([3, 4, 5])
|
||||
>>> v6 = Vector(range(6))
|
||||
>>> hash(v1), hash(v3), hash(v6)
|
||||
(7, 2, 1)
|
||||
|
||||
|
||||
Most hash values of non-integers vary from a 32-bit to 64-bit Python build::
|
||||
|
||||
>>> import sys
|
||||
>>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986)
|
||||
True
|
||||
|
||||
|
||||
Tests of ``format()`` with Cartesian coordinates in 2D::
|
||||
|
||||
>>> v1 = Vector([3, 4])
|
||||
>>> format(v1)
|
||||
'(3.0, 4.0)'
|
||||
>>> format(v1, '.2f')
|
||||
'(3.00, 4.00)'
|
||||
>>> format(v1, '.3e')
|
||||
'(3.000e+00, 4.000e+00)'
|
||||
|
||||
|
||||
Tests of ``format()`` with Cartesian coordinates in 3D and 7D::
|
||||
|
||||
>>> v3 = Vector([3, 4, 5])
|
||||
>>> format(v3)
|
||||
'(3.0, 4.0, 5.0)'
|
||||
>>> format(Vector(range(7)))
|
||||
'(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)'
|
||||
|
||||
|
||||
Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D::
|
||||
|
||||
>>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS
|
||||
'<1.414213..., 0.785398...>'
|
||||
>>> format(Vector([1, 1]), '.3eh')
|
||||
'<1.414e+00, 7.854e-01>'
|
||||
>>> format(Vector([1, 1]), '0.5fh')
|
||||
'<1.41421, 0.78540>'
|
||||
>>> format(Vector([1, 1, 1]), 'h') # doctest:+ELLIPSIS
|
||||
'<1.73205..., 0.95531..., 0.78539...>'
|
||||
>>> format(Vector([2, 2, 2]), '.3eh')
|
||||
'<3.464e+00, 9.553e-01, 7.854e-01>'
|
||||
>>> format(Vector([0, 0, 0]), '0.5fh')
|
||||
'<0.00000, 0.00000, 0.00000>'
|
||||
>>> format(Vector([-1, -1, -1, -1]), 'h') # doctest:+ELLIPSIS
|
||||
'<2.0, 2.09439..., 2.18627..., 3.92699...>'
|
||||
>>> format(Vector([2, 2, 2, 2]), '.3eh')
|
||||
'<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>'
|
||||
>>> format(Vector([0, 1, 0, 0]), '0.5fh')
|
||||
'<1.00000, 1.57080, 0.00000, 0.00000>'
|
||||
|
||||
|
||||
Unary operator tests::
|
||||
|
||||
>>> v1 = Vector([3, 4])
|
||||
>>> abs(v1)
|
||||
5.0
|
||||
>>> -v1
|
||||
Vector([-3.0, -4.0])
|
||||
>>> +v1
|
||||
Vector([3.0, 4.0])
|
||||
|
||||
|
||||
Basic tests of operator ``+``::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> v2 = Vector([6, 7, 8])
|
||||
>>> v1 + v2
|
||||
Vector([9.0, 11.0, 13.0])
|
||||
>>> v1 + v2 == Vector([3+6, 4+7, 5+8])
|
||||
True
|
||||
>>> v3 = Vector([1, 2])
|
||||
>>> v1 + v3 # short vectors are filled with 0.0 on addition
|
||||
Vector([4.0, 6.0, 5.0])
|
||||
|
||||
|
||||
Tests of ``+`` with mixed types::
|
||||
|
||||
>>> v1 + (10, 20, 30)
|
||||
Vector([13.0, 24.0, 35.0])
|
||||
>>> from vector2d_v3 import Vector2d
|
||||
>>> v2d = Vector2d(1, 2)
|
||||
>>> v1 + v2d
|
||||
Vector([4.0, 6.0, 5.0])
|
||||
|
||||
|
||||
Tests of ``+`` with mixed types, swapped operands::
|
||||
|
||||
>>> (10, 20, 30) + v1
|
||||
Vector([13.0, 24.0, 35.0])
|
||||
>>> from vector2d_v3 import Vector2d
|
||||
>>> v2d = Vector2d(1, 2)
|
||||
>>> v2d + v1
|
||||
Vector([4.0, 6.0, 5.0])
|
||||
|
||||
|
||||
Tests of ``+`` with an unsuitable operand:
|
||||
|
||||
>>> v1 + 1
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: unsupported operand type(s) for +: 'Vector' and 'int'
|
||||
>>> v1 + 'ABC'
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: unsupported operand type(s) for +: 'Vector' and 'str'
|
||||
|
||||
|
||||
Basic tests of operator ``*``::
|
||||
|
||||
>>> v1 = Vector([1, 2, 3])
|
||||
>>> v1 * 10
|
||||
Vector([10.0, 20.0, 30.0])
|
||||
>>> 10 * v1
|
||||
Vector([10.0, 20.0, 30.0])
|
||||
|
||||
|
||||
Tests of ``*`` with unusual but valid operands::
|
||||
|
||||
>>> v1 * True
|
||||
Vector([1.0, 2.0, 3.0])
|
||||
>>> from fractions import Fraction
|
||||
>>> v1 * Fraction(1, 3) # doctest:+ELLIPSIS
|
||||
Vector([0.3333..., 0.6666..., 1.0])
|
||||
|
||||
|
||||
Tests of ``*`` with unsuitable operands::
|
||||
|
||||
>>> v1 * (1, 2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: can't multiply sequence by non-int of type 'Vector'
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import reprlib
|
||||
import math
|
||||
import numbers
|
||||
import functools
|
||||
import operator
|
||||
import itertools
|
||||
|
||||
|
||||
class Vector:
|
||||
typecode = 'd'
|
||||
|
||||
def __init__(self, components):
|
||||
self._components = array(self.typecode, components)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._components)
|
||||
|
||||
def __repr__(self):
|
||||
components = reprlib.repr(self._components)
|
||||
components = components[components.find('['):-1]
|
||||
return 'Vector({})'.format(components)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes([ord(self.typecode)]) +
|
||||
bytes(self._components))
|
||||
|
||||
def __eq__(self, other):
|
||||
return (len(self) == len(other) and
|
||||
all(a == b for a, b in zip(self, other)))
|
||||
|
||||
def __hash__(self):
|
||||
hashes = (hash(x) for x in self)
|
||||
return functools.reduce(operator.xor, hashes, 0)
|
||||
|
||||
def __abs__(self):
|
||||
return math.sqrt(sum(x * x for x in self))
|
||||
|
||||
def __neg__(self):
|
||||
return Vector(-x for x in self)
|
||||
|
||||
def __pos__(self):
|
||||
return Vector(self)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
||||
def __len__(self):
|
||||
return len(self._components)
|
||||
|
||||
def __getitem__(self, index):
|
||||
cls = type(self)
|
||||
if isinstance(index, slice):
|
||||
return cls(self._components[index])
|
||||
elif isinstance(index, numbers.Integral):
|
||||
return self._components[index]
|
||||
else:
|
||||
msg = '{.__name__} indices must be integers'
|
||||
raise TypeError(msg.format(cls))
|
||||
|
||||
shortcut_names = 'xyzt'
|
||||
|
||||
def __getattr__(self, name):
|
||||
cls = type(self)
|
||||
if len(name) == 1:
|
||||
pos = cls.shortcut_names.find(name)
|
||||
if 0 <= pos < len(self._components):
|
||||
return self._components[pos]
|
||||
msg = '{.__name__!r} object has no attribute {!r}'
|
||||
raise AttributeError(msg.format(cls, name))
|
||||
|
||||
def angle(self, n):
|
||||
r = math.sqrt(sum(x * x for x in self[n:]))
|
||||
a = math.atan2(r, self[n-1])
|
||||
if (n == len(self) - 1) and (self[-1] < 0):
|
||||
return math.pi * 2 - a
|
||||
else:
|
||||
return a
|
||||
|
||||
def angles(self):
|
||||
return (self.angle(n) for n in range(1, len(self)))
|
||||
|
||||
def __format__(self, fmt_spec=''):
|
||||
if fmt_spec.endswith('h'): # hyperspherical coordinates
|
||||
fmt_spec = fmt_spec[:-1]
|
||||
coords = itertools.chain([abs(self)],
|
||||
self.angles())
|
||||
outer_fmt = '<{}>'
|
||||
else:
|
||||
coords = self
|
||||
outer_fmt = '({})'
|
||||
components = (format(c, fmt_spec) for c in coords)
|
||||
return outer_fmt.format(', '.join(components))
|
||||
|
||||
@classmethod
|
||||
def frombytes(cls, octets):
|
||||
typecode = chr(octets[0])
|
||||
memv = memoryview(octets[1:]).cast(typecode)
|
||||
return cls(memv)
|
||||
|
||||
def __add__(self, other):
|
||||
try:
|
||||
pairs = itertools.zip_longest(self, other, fillvalue=0.0)
|
||||
return Vector(a + b for a, b in pairs)
|
||||
except TypeError:
|
||||
return NotImplemented
|
||||
|
||||
def __radd__(self, other):
|
||||
return self + other
|
||||
|
||||
def __mul__(self, scalar):
|
||||
if isinstance(scalar, numbers.Real):
|
||||
return Vector(n * scalar for n in self)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __rmul__(self, scalar):
|
||||
return self * scalar
|
@ -1,424 +0,0 @@
|
||||
"""
|
||||
A multi-dimensional ``Vector`` class, take 8: operator ``==``
|
||||
|
||||
A ``Vector`` is built from an iterable of numbers::
|
||||
|
||||
>>> Vector([3.1, 4.2])
|
||||
Vector([3.1, 4.2])
|
||||
>>> Vector((3, 4, 5))
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> Vector(range(10))
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
|
||||
|
||||
Tests with 2-dimensions (same results as ``vector2d_v1.py``)::
|
||||
|
||||
>>> 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'd\\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 with 3-dimensions::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> x, y, z = v1
|
||||
>>> x, y, z
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> v1
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1_clone = eval(repr(v1))
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
>>> print(v1)
|
||||
(3.0, 4.0, 5.0)
|
||||
>>> abs(v1) # doctest:+ELLIPSIS
|
||||
7.071067811...
|
||||
>>> bool(v1), bool(Vector([0, 0, 0]))
|
||||
(True, False)
|
||||
|
||||
|
||||
Tests with many dimensions::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7
|
||||
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
|
||||
>>> abs(v7) # doctest:+ELLIPSIS
|
||||
9.53939201...
|
||||
|
||||
|
||||
Test of ``.__bytes__`` and ``.frombytes()`` methods::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> v1_clone = Vector.frombytes(bytes(v1))
|
||||
>>> v1_clone
|
||||
Vector([3.0, 4.0, 5.0])
|
||||
>>> v1 == v1_clone
|
||||
True
|
||||
|
||||
|
||||
Tests of sequence behavior::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> len(v1)
|
||||
3
|
||||
>>> v1[0], v1[len(v1)-1], v1[-1]
|
||||
(3.0, 5.0, 5.0)
|
||||
|
||||
|
||||
Test of slicing::
|
||||
|
||||
>>> v7 = Vector(range(7))
|
||||
>>> v7[-1]
|
||||
6.0
|
||||
>>> v7[1:4]
|
||||
Vector([1.0, 2.0, 3.0])
|
||||
>>> v7[-1:]
|
||||
Vector([6.0])
|
||||
>>> v7[1,2]
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Vector indices must be integers
|
||||
|
||||
|
||||
Tests of dynamic attribute access::
|
||||
|
||||
>>> v7 = Vector(range(10))
|
||||
>>> v7.x
|
||||
0.0
|
||||
>>> v7.y, v7.z, v7.t
|
||||
(1.0, 2.0, 3.0)
|
||||
|
||||
Dynamic attribute lookup failures::
|
||||
|
||||
>>> v7.k
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 'k'
|
||||
>>> v3 = Vector(range(3))
|
||||
>>> v3.t
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 't'
|
||||
>>> v3.spam
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Vector' object has no attribute 'spam'
|
||||
|
||||
|
||||
Tests of hashing::
|
||||
|
||||
>>> v1 = Vector([3, 4])
|
||||
>>> v2 = Vector([3.1, 4.2])
|
||||
>>> v3 = Vector([3, 4, 5])
|
||||
>>> v6 = Vector(range(6))
|
||||
>>> hash(v1), hash(v3), hash(v6)
|
||||
(7, 2, 1)
|
||||
|
||||
|
||||
Most hash values of non-integers vary from a 32-bit to 64-bit Python build::
|
||||
|
||||
>>> import sys
|
||||
>>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986)
|
||||
True
|
||||
|
||||
|
||||
Tests of ``format()`` with Cartesian coordinates in 2D::
|
||||
|
||||
>>> v1 = Vector([3, 4])
|
||||
>>> format(v1)
|
||||
'(3.0, 4.0)'
|
||||
>>> format(v1, '.2f')
|
||||
'(3.00, 4.00)'
|
||||
>>> format(v1, '.3e')
|
||||
'(3.000e+00, 4.000e+00)'
|
||||
|
||||
|
||||
Tests of ``format()`` with Cartesian coordinates in 3D and 7D::
|
||||
|
||||
>>> v3 = Vector([3, 4, 5])
|
||||
>>> format(v3)
|
||||
'(3.0, 4.0, 5.0)'
|
||||
>>> format(Vector(range(7)))
|
||||
'(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)'
|
||||
|
||||
|
||||
Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D::
|
||||
|
||||
>>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS
|
||||
'<1.414213..., 0.785398...>'
|
||||
>>> format(Vector([1, 1]), '.3eh')
|
||||
'<1.414e+00, 7.854e-01>'
|
||||
>>> format(Vector([1, 1]), '0.5fh')
|
||||
'<1.41421, 0.78540>'
|
||||
>>> format(Vector([1, 1, 1]), 'h') # doctest:+ELLIPSIS
|
||||
'<1.73205..., 0.95531..., 0.78539...>'
|
||||
>>> format(Vector([2, 2, 2]), '.3eh')
|
||||
'<3.464e+00, 9.553e-01, 7.854e-01>'
|
||||
>>> format(Vector([0, 0, 0]), '0.5fh')
|
||||
'<0.00000, 0.00000, 0.00000>'
|
||||
>>> format(Vector([-1, -1, -1, -1]), 'h') # doctest:+ELLIPSIS
|
||||
'<2.0, 2.09439..., 2.18627..., 3.92699...>'
|
||||
>>> format(Vector([2, 2, 2, 2]), '.3eh')
|
||||
'<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>'
|
||||
>>> format(Vector([0, 1, 0, 0]), '0.5fh')
|
||||
'<1.00000, 1.57080, 0.00000, 0.00000>'
|
||||
|
||||
|
||||
Unary operator tests::
|
||||
|
||||
>>> v1 = Vector([3, 4])
|
||||
>>> abs(v1)
|
||||
5.0
|
||||
>>> -v1
|
||||
Vector([-3.0, -4.0])
|
||||
>>> +v1
|
||||
Vector([3.0, 4.0])
|
||||
|
||||
|
||||
Basic tests of operator ``+``::
|
||||
|
||||
>>> v1 = Vector([3, 4, 5])
|
||||
>>> v2 = Vector([6, 7, 8])
|
||||
>>> v1 + v2
|
||||
Vector([9.0, 11.0, 13.0])
|
||||
>>> v1 + v2 == Vector([3+6, 4+7, 5+8])
|
||||
True
|
||||
>>> v3 = Vector([1, 2])
|
||||
>>> v1 + v3 # short vectors are filled with 0.0 on addition
|
||||
Vector([4.0, 6.0, 5.0])
|
||||
|
||||
|
||||
Tests of ``+`` with mixed types::
|
||||
|
||||
>>> v1 + (10, 20, 30)
|
||||
Vector([13.0, 24.0, 35.0])
|
||||
>>> from vector2d_v3 import Vector2d
|
||||
>>> v2d = Vector2d(1, 2)
|
||||
>>> v1 + v2d
|
||||
Vector([4.0, 6.0, 5.0])
|
||||
|
||||
|
||||
Tests of ``+`` with mixed types, swapped operands::
|
||||
|
||||
>>> (10, 20, 30) + v1
|
||||
Vector([13.0, 24.0, 35.0])
|
||||
>>> from vector2d_v3 import Vector2d
|
||||
>>> v2d = Vector2d(1, 2)
|
||||
>>> v2d + v1
|
||||
Vector([4.0, 6.0, 5.0])
|
||||
|
||||
|
||||
Tests of ``+`` with an unsuitable operand:
|
||||
|
||||
>>> v1 + 1
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: unsupported operand type(s) for +: 'Vector' and 'int'
|
||||
>>> v1 + 'ABC'
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: unsupported operand type(s) for +: 'Vector' and 'str'
|
||||
|
||||
|
||||
Basic tests of operator ``*``::
|
||||
|
||||
>>> v1 = Vector([1, 2, 3])
|
||||
>>> v1 * 10
|
||||
Vector([10.0, 20.0, 30.0])
|
||||
>>> 10 * v1
|
||||
Vector([10.0, 20.0, 30.0])
|
||||
|
||||
|
||||
Tests of ``*`` with unusual but valid operands::
|
||||
|
||||
>>> v1 * True
|
||||
Vector([1.0, 2.0, 3.0])
|
||||
>>> from fractions import Fraction
|
||||
>>> v1 * Fraction(1, 3) # doctest:+ELLIPSIS
|
||||
Vector([0.3333..., 0.6666..., 1.0])
|
||||
|
||||
|
||||
Tests of ``*`` with unsuitable operands::
|
||||
|
||||
>>> v1 * (1, 2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: can't multiply sequence by non-int of type 'Vector'
|
||||
|
||||
|
||||
Tests of operator `==`::
|
||||
|
||||
>>> va = Vector(range(1, 4))
|
||||
>>> vb = Vector([1.0, 2.0, 3.0])
|
||||
>>> va == vb
|
||||
True
|
||||
>>> vc = Vector([1, 2])
|
||||
>>> from vector2d_v3 import Vector2d
|
||||
>>> v2d = Vector2d(1, 2)
|
||||
>>> vc == v2d
|
||||
True
|
||||
>>> va == (1, 2, 3)
|
||||
False
|
||||
|
||||
|
||||
Tests of operator `!=`::
|
||||
|
||||
>>> va != vb
|
||||
False
|
||||
>>> vc != v2d
|
||||
False
|
||||
>>> va != (1, 2, 3)
|
||||
True
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
import reprlib
|
||||
import math
|
||||
import numbers
|
||||
import functools
|
||||
import operator
|
||||
import itertools
|
||||
|
||||
|
||||
class Vector:
|
||||
typecode = 'd'
|
||||
|
||||
def __init__(self, components):
|
||||
self._components = array(self.typecode, components)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._components)
|
||||
|
||||
def __repr__(self):
|
||||
components = reprlib.repr(self._components)
|
||||
components = components[components.find('['):-1]
|
||||
return 'Vector({})'.format(components)
|
||||
|
||||
def __str__(self):
|
||||
return str(tuple(self))
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes([ord(self.typecode)]) +
|
||||
bytes(self._components))
|
||||
|
||||
# BEGIN VECTOR_V8_EQ
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Vector): # <1>
|
||||
return (len(self) == len(other) and
|
||||
all(a == b for a, b in zip(self, other)))
|
||||
else:
|
||||
return NotImplemented # <2>
|
||||
# END VECTOR_V8_EQ
|
||||
|
||||
def __hash__(self):
|
||||
hashes = (hash(x) for x in self)
|
||||
return functools.reduce(operator.xor, hashes, 0)
|
||||
|
||||
def __abs__(self):
|
||||
return math.sqrt(sum(x * x for x in self))
|
||||
|
||||
def __neg__(self):
|
||||
return Vector(-x for x in self)
|
||||
|
||||
def __pos__(self):
|
||||
return Vector(self)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
||||
def __len__(self):
|
||||
return len(self._components)
|
||||
|
||||
def __getitem__(self, index):
|
||||
cls = type(self)
|
||||
if isinstance(index, slice):
|
||||
return cls(self._components[index])
|
||||
elif isinstance(index, numbers.Integral):
|
||||
return self._components[index]
|
||||
else:
|
||||
msg = '{.__name__} indices must be integers'
|
||||
raise TypeError(msg.format(cls))
|
||||
|
||||
shortcut_names = 'xyzt'
|
||||
|
||||
def __getattr__(self, name):
|
||||
cls = type(self)
|
||||
if len(name) == 1:
|
||||
pos = cls.shortcut_names.find(name)
|
||||
if 0 <= pos < len(self._components):
|
||||
return self._components[pos]
|
||||
msg = '{.__name__!r} object has no attribute {!r}'
|
||||
raise AttributeError(msg.format(cls, name))
|
||||
|
||||
def angle(self, n):
|
||||
r = math.sqrt(sum(x * x for x in self[n:]))
|
||||
a = math.atan2(r, self[n-1])
|
||||
if (n == len(self) - 1) and (self[-1] < 0):
|
||||
return math.pi * 2 - a
|
||||
else:
|
||||
return a
|
||||
|
||||
def angles(self):
|
||||
return (self.angle(n) for n in range(1, len(self)))
|
||||
|
||||
def __format__(self, fmt_spec=''):
|
||||
if fmt_spec.endswith('h'): # hyperspherical coordinates
|
||||
fmt_spec = fmt_spec[:-1]
|
||||
coords = itertools.chain([abs(self)],
|
||||
self.angles())
|
||||
outer_fmt = '<{}>'
|
||||
else:
|
||||
coords = self
|
||||
outer_fmt = '({})'
|
||||
components = (format(c, fmt_spec) for c in coords)
|
||||
return outer_fmt.format(', '.join(components))
|
||||
|
||||
@classmethod
|
||||
def frombytes(cls, octets):
|
||||
typecode = chr(octets[0])
|
||||
memv = memoryview(octets[1:]).cast(typecode)
|
||||
return cls(memv)
|
||||
|
||||
def __add__(self, other):
|
||||
try:
|
||||
pairs = itertools.zip_longest(self, other, fillvalue=0.0)
|
||||
return Vector(a + b for a, b in pairs)
|
||||
except TypeError:
|
||||
return NotImplemented
|
||||
|
||||
def __radd__(self, other):
|
||||
return self + other
|
||||
|
||||
def __mul__(self, scalar):
|
||||
if isinstance(scalar, numbers.Real):
|
||||
return Vector(n * scalar for n in self)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __rmul__(self, scalar):
|
||||
return self * scalar
|
@ -1,4 +0,0 @@
|
||||
Sample code for Chapter 14 - "Iterables, iterators and generators"
|
||||
|
||||
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
|
||||
http://shop.oreilly.com/product/0636920032519.do
|
@ -1,31 +0,0 @@
|
||||
===========================================
|
||||
Tests for arithmetic progression generators
|
||||
===========================================
|
||||
|
||||
Tests with built-in numeric types::
|
||||
|
||||
>>> ap = aritprog_gen(1, .5, 3)
|
||||
>>> list(ap)
|
||||
[1.0, 1.5, 2.0, 2.5]
|
||||
>>> ap = aritprog_gen(0, 1/3, 1)
|
||||
>>> list(ap)
|
||||
[0.0, 0.3333333333333333, 0.6666666666666666]
|
||||
|
||||
|
||||
Tests with standard library numeric types::
|
||||
|
||||
>>> from fractions import Fraction
|
||||
>>> ap = aritprog_gen(0, Fraction(1, 3), 1)
|
||||
>>> list(ap)
|
||||
[Fraction(0, 1), Fraction(1, 3), Fraction(2, 3)]
|
||||
>>> from decimal import Decimal
|
||||
>>> ap = aritprog_gen(0, Decimal('.1'), .3)
|
||||
>>> list(ap)
|
||||
[Decimal('0'), Decimal('0.1'), Decimal('0.2')]
|
||||
|
||||
|
||||
Test producing an empty series::
|
||||
|
||||
>>> ap = aritprog_gen(0, 1, 0)
|
||||
>>> list(ap)
|
||||
[]
|
@ -1,26 +0,0 @@
|
||||
"""
|
||||
Demonstrate difference between Arithmetic Progression calculated
|
||||
as a series of increments accumulating errors versus one addition
|
||||
and one multiplication.
|
||||
"""
|
||||
|
||||
from fractions import Fraction
|
||||
from aritprog_v0 import ArithmeticProgression as APv0
|
||||
from aritprog_v1 import ArithmeticProgression as APv1
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
ap0 = iter(APv0(1, .1))
|
||||
ap1 = iter(APv1(1, .1))
|
||||
ap_frac = iter(APv1(Fraction(1, 1), Fraction(1, 10)))
|
||||
epsilon = 10**-10
|
||||
iteration = 0
|
||||
delta = next(ap0) - next(ap1)
|
||||
frac = next(ap_frac)
|
||||
while abs(delta) <= epsilon:
|
||||
delta = next(ap0) - next(ap1)
|
||||
frac = next(ap_frac)
|
||||
iteration +=1
|
||||
|
||||
print('iteration: {}\tfraction: {}\tepsilon: {}\tdelta: {}'.
|
||||
format(iteration, frac, epsilon, delta))
|
@ -1,37 +0,0 @@
|
||||
import doctest
|
||||
import importlib
|
||||
import glob
|
||||
|
||||
|
||||
TARGET_GLOB = 'aritprog*.py'
|
||||
TEST_FILE = 'aritprog.rst'
|
||||
TEST_MSG = '{0:16} {1.attempted:2} tests, {1.failed:2} failed - {2}'
|
||||
|
||||
|
||||
def main(argv):
|
||||
verbose = '-v' in argv
|
||||
for module_file_name in sorted(glob.glob(TARGET_GLOB)):
|
||||
module_name = module_file_name.replace('.py', '')
|
||||
module = importlib.import_module(module_name)
|
||||
gen_factory = getattr(module, 'ArithmeticProgression', None)
|
||||
if gen_factory is None:
|
||||
gen_factory = getattr(module, 'aritprog_gen', None)
|
||||
if gen_factory is None:
|
||||
continue
|
||||
|
||||
test(gen_factory, verbose)
|
||||
|
||||
|
||||
def test(gen_factory, verbose=False):
|
||||
res = doctest.testfile(
|
||||
TEST_FILE,
|
||||
globs={'aritprog_gen': gen_factory},
|
||||
verbose=verbose,
|
||||
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)
|
||||
tag = 'FAIL' if res.failed else 'OK'
|
||||
print(TEST_MSG.format(gen_factory.__module__, res, tag))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
main(sys.argv)
|
@ -1,25 +0,0 @@
|
||||
"""
|
||||
Arithmetic progression class
|
||||
|
||||
>>> ap = ArithmeticProgression(1, .5, 3)
|
||||
>>> list(ap)
|
||||
[1.0, 1.5, 2.0, 2.5]
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class ArithmeticProgression:
|
||||
|
||||
def __init__(self, begin, step, end=None):
|
||||
self.begin = begin
|
||||
self.step = step
|
||||
self.end = end # None -> "infinite" series
|
||||
|
||||
def __iter__(self):
|
||||
result_type = type(self.begin + self.step)
|
||||
result = result_type(self.begin)
|
||||
forever = self.end is None
|
||||
while forever or result < self.end:
|
||||
yield result
|
||||
result += self.step
|
@ -1,45 +0,0 @@
|
||||
"""
|
||||
Arithmetic progression class
|
||||
|
||||
# tag::ARITPROG_CLASS_DEMO[]
|
||||
|
||||
>>> ap = ArithmeticProgression(0, 1, 3)
|
||||
>>> list(ap)
|
||||
[0, 1, 2]
|
||||
>>> ap = ArithmeticProgression(1, .5, 3)
|
||||
>>> list(ap)
|
||||
[1.0, 1.5, 2.0, 2.5]
|
||||
>>> ap = ArithmeticProgression(0, 1/3, 1)
|
||||
>>> list(ap)
|
||||
[0.0, 0.3333333333333333, 0.6666666666666666]
|
||||
>>> from fractions import Fraction
|
||||
>>> ap = ArithmeticProgression(0, Fraction(1, 3), 1)
|
||||
>>> list(ap)
|
||||
[Fraction(0, 1), Fraction(1, 3), Fraction(2, 3)]
|
||||
>>> from decimal import Decimal
|
||||
>>> ap = ArithmeticProgression(0, Decimal('.1'), .3)
|
||||
>>> list(ap)
|
||||
[Decimal('0.0'), Decimal('0.1'), Decimal('0.2')]
|
||||
|
||||
# end::ARITPROG_CLASS_DEMO[]
|
||||
"""
|
||||
|
||||
|
||||
# tag::ARITPROG_CLASS[]
|
||||
class ArithmeticProgression:
|
||||
|
||||
def __init__(self, begin, step, end=None): # <1>
|
||||
self.begin = begin
|
||||
self.step = step
|
||||
self.end = end # None -> "infinite" series
|
||||
|
||||
def __iter__(self):
|
||||
result_type = type(self.begin + self.step) # <2>
|
||||
result = result_type(self.begin) # <3>
|
||||
forever = self.end is None # <4>
|
||||
index = 0
|
||||
while forever or result < self.end: # <5>
|
||||
yield result # <6>
|
||||
index += 1
|
||||
result = self.begin + self.step * index # <7>
|
||||
# end::ARITPROG_CLASS[]
|
@ -1,31 +0,0 @@
|
||||
"""
|
||||
Arithmetic progression generator function::
|
||||
|
||||
>>> ap = aritprog_gen(1, .5, 3)
|
||||
>>> list(ap)
|
||||
[1.0, 1.5, 2.0, 2.5]
|
||||
>>> ap = aritprog_gen(0, 1/3, 1)
|
||||
>>> list(ap)
|
||||
[0.0, 0.3333333333333333, 0.6666666666666666]
|
||||
>>> from fractions import Fraction
|
||||
>>> ap = aritprog_gen(0, Fraction(1, 3), 1)
|
||||
>>> list(ap)
|
||||
[Fraction(0, 1), Fraction(1, 3), Fraction(2, 3)]
|
||||
>>> from decimal import Decimal
|
||||
>>> ap = aritprog_gen(0, Decimal('.1'), .3)
|
||||
>>> list(ap)
|
||||
[Decimal('0.0'), Decimal('0.1'), Decimal('0.2')]
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# tag::ARITPROG_GENFUNC[]
|
||||
def aritprog_gen(begin, step, end=None):
|
||||
result = type(begin + step)(begin)
|
||||
forever = end is None
|
||||
index = 0
|
||||
while forever or result < end:
|
||||
yield result
|
||||
index += 1
|
||||
result = begin + step * index
|
||||
# end::ARITPROG_GENFUNC[]
|
@ -1,11 +0,0 @@
|
||||
# tag::ARITPROG_ITERTOOLS[]
|
||||
import itertools
|
||||
|
||||
|
||||
def aritprog_gen(begin, step, end=None):
|
||||
first = type(begin + step)(begin)
|
||||
ap_gen = itertools.count(first, step)
|
||||
if end is not None:
|
||||
ap_gen = itertools.takewhile(lambda n: n < end, ap_gen)
|
||||
return ap_gen
|
||||
# end::ARITPROG_ITERTOOLS[]
|
@ -1,51 +0,0 @@
|
||||
"""
|
||||
Fibonacci generator implemented "by hand" without generator objects
|
||||
|
||||
>>> from itertools import islice
|
||||
>>> list(islice(Fibonacci(), 15))
|
||||
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# tag::FIBO_BY_HAND[]
|
||||
class Fibonacci:
|
||||
|
||||
def __iter__(self):
|
||||
return FibonacciGenerator()
|
||||
|
||||
|
||||
class FibonacciGenerator:
|
||||
|
||||
def __init__(self):
|
||||
self.a = 0
|
||||
self.b = 1
|
||||
|
||||
def __next__(self):
|
||||
result = self.a
|
||||
self.a, self.b = self.b, self.a + self.b
|
||||
return result
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
# end::FIBO_BY_HAND[]
|
||||
|
||||
# for comparison, this is the usual implementation of a Fibonacci
|
||||
# generator in Python:
|
||||
|
||||
|
||||
def fibonacci():
|
||||
a, b = 0, 1
|
||||
while True:
|
||||
yield a
|
||||
a, b = b, a + b
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
for x, y in zip(Fibonacci(), fibonacci()):
|
||||
assert x == y, '%s != %s' % (x, y)
|
||||
print(x)
|
||||
if x > 10**10:
|
||||
break
|
||||
print('etc...')
|
@ -1,12 +0,0 @@
|
||||
isis2json.py
|
||||
============
|
||||
|
||||
This directory contains a copy of the ``isis2json.py`` script, with
|
||||
minimal dependencies, just to allow the O'Reilly Atlas toolchain to
|
||||
render the listing of the script in appendix A of the book.
|
||||
|
||||
If you want to use or contribute to this script, please get the full
|
||||
source code with all dependencies from the main ``isis2json``
|
||||
repository:
|
||||
|
||||
https://github.com/fluentpython/isis2json
|
@ -1,263 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
# isis2json.py: convert ISIS and ISO-2709 files to JSON
|
||||
#
|
||||
# Copyright (C) 2010 BIREME/PAHO/WHO
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
############################
|
||||
# BEGIN ISIS2JSON
|
||||
# this script works with Python or Jython (versions >=2.5 and <3)
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from uuid import uuid4
|
||||
import os
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
if os.name == 'java': # running Jython
|
||||
from com.xhaus.jyson import JysonCodec as json
|
||||
else:
|
||||
import simplejson as json
|
||||
|
||||
SKIP_INACTIVE = True
|
||||
DEFAULT_QTY = 2**31
|
||||
ISIS_MFN_KEY = 'mfn'
|
||||
ISIS_ACTIVE_KEY = 'active'
|
||||
SUBFIELD_DELIMITER = '^'
|
||||
INPUT_ENCODING = 'cp1252'
|
||||
|
||||
|
||||
def iter_iso_records(iso_file_name, isis_json_type): # <1>
|
||||
from iso2709 import IsoFile
|
||||
from subfield import expand
|
||||
|
||||
iso = IsoFile(iso_file_name)
|
||||
for record in iso:
|
||||
fields = {}
|
||||
for field in record.directory:
|
||||
field_key = str(int(field.tag)) # remove leading zeroes
|
||||
field_occurrences = fields.setdefault(field_key, [])
|
||||
content = field.value.decode(INPUT_ENCODING, 'replace')
|
||||
if isis_json_type == 1:
|
||||
field_occurrences.append(content)
|
||||
elif isis_json_type == 2:
|
||||
field_occurrences.append(expand(content))
|
||||
elif isis_json_type == 3:
|
||||
field_occurrences.append(dict(expand(content)))
|
||||
else:
|
||||
raise NotImplementedError('ISIS-JSON type %s conversion '
|
||||
'not yet implemented for .iso input' % isis_json_type)
|
||||
|
||||
yield fields
|
||||
iso.close()
|
||||
|
||||
|
||||
def iter_mst_records(master_file_name, isis_json_type): # <2>
|
||||
try:
|
||||
from bruma.master import MasterFactory, Record
|
||||
except ImportError:
|
||||
print('IMPORT ERROR: Jython 2.5 and Bruma.jar '
|
||||
'are required to read .mst files')
|
||||
raise SystemExit
|
||||
mst = MasterFactory.getInstance(master_file_name).open()
|
||||
for record in mst:
|
||||
fields = {}
|
||||
if SKIP_INACTIVE:
|
||||
if record.getStatus() != Record.Status.ACTIVE:
|
||||
continue
|
||||
else: # save status only there are non-active records
|
||||
fields[ISIS_ACTIVE_KEY] = (record.getStatus() ==
|
||||
Record.Status.ACTIVE)
|
||||
fields[ISIS_MFN_KEY] = record.getMfn()
|
||||
for field in record.getFields():
|
||||
field_key = str(field.getId())
|
||||
field_occurrences = fields.setdefault(field_key, [])
|
||||
if isis_json_type == 3:
|
||||
content = {}
|
||||
for subfield in field.getSubfields():
|
||||
subfield_key = subfield.getId()
|
||||
if subfield_key == '*':
|
||||
content['_'] = subfield.getContent()
|
||||
else:
|
||||
subfield_occurrences = content.setdefault(subfield_key, [])
|
||||
subfield_occurrences.append(subfield.getContent())
|
||||
field_occurrences.append(content)
|
||||
elif isis_json_type == 1:
|
||||
content = []
|
||||
for subfield in field.getSubfields():
|
||||
subfield_key = subfield.getId()
|
||||
if subfield_key == '*':
|
||||
content.insert(0, subfield.getContent())
|
||||
else:
|
||||
content.append(SUBFIELD_DELIMITER + subfield_key +
|
||||
subfield.getContent())
|
||||
field_occurrences.append(''.join(content))
|
||||
else:
|
||||
raise NotImplementedError('ISIS-JSON type %s conversion '
|
||||
'not yet implemented for .mst input' % isis_json_type)
|
||||
yield fields
|
||||
mst.close()
|
||||
|
||||
|
||||
def write_json(input_gen, file_name, output, qty, skip, id_tag, # <3>
|
||||
gen_uuid, mongo, mfn, isis_json_type, prefix,
|
||||
constant):
|
||||
start = skip
|
||||
end = start + qty
|
||||
if id_tag:
|
||||
id_tag = str(id_tag)
|
||||
ids = set()
|
||||
else:
|
||||
id_tag = ''
|
||||
for i, record in enumerate(input_gen):
|
||||
if i >= end:
|
||||
break
|
||||
if not mongo:
|
||||
if i == 0:
|
||||
output.write('[')
|
||||
elif i > start:
|
||||
output.write(',')
|
||||
if start <= i < end:
|
||||
if id_tag:
|
||||
occurrences = record.get(id_tag, None)
|
||||
if occurrences is None:
|
||||
msg = 'id tag #%s not found in record %s'
|
||||
if ISIS_MFN_KEY in record:
|
||||
msg = msg + (' (mfn=%s)' % record[ISIS_MFN_KEY])
|
||||
raise KeyError(msg % (id_tag, i))
|
||||
if len(occurrences) > 1:
|
||||
msg = 'multiple id tags #%s found in record %s'
|
||||
if ISIS_MFN_KEY in record:
|
||||
msg = msg + (' (mfn=%s)' % record[ISIS_MFN_KEY])
|
||||
raise TypeError(msg % (id_tag, i))
|
||||
else: # ok, we have one and only one id field
|
||||
if isis_json_type == 1:
|
||||
id = occurrences[0]
|
||||
elif isis_json_type == 2:
|
||||
id = occurrences[0][0][1]
|
||||
elif isis_json_type == 3:
|
||||
id = occurrences[0]['_']
|
||||
if id in ids:
|
||||
msg = 'duplicate id %s in tag #%s, record %s'
|
||||
if ISIS_MFN_KEY in record:
|
||||
msg = msg + (' (mfn=%s)' % record[ISIS_MFN_KEY])
|
||||
raise TypeError(msg % (id, id_tag, i))
|
||||
record['_id'] = id
|
||||
ids.add(id)
|
||||
elif gen_uuid:
|
||||
record['_id'] = unicode(uuid4())
|
||||
elif mfn:
|
||||
record['_id'] = record[ISIS_MFN_KEY]
|
||||
if prefix:
|
||||
# iterate over a fixed sequence of tags
|
||||
for tag in tuple(record):
|
||||
if str(tag).isdigit():
|
||||
record[prefix+tag] = record[tag]
|
||||
del record[tag] # this is why we iterate over a tuple
|
||||
# with the tags, and not directly on the record dict
|
||||
if constant:
|
||||
constant_key, constant_value = constant.split(':')
|
||||
record[constant_key] = constant_value
|
||||
output.write(json.dumps(record).encode('utf-8'))
|
||||
output.write('\n')
|
||||
if not mongo:
|
||||
output.write(']\n')
|
||||
|
||||
|
||||
def main(): # <4>
|
||||
# create the parser
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Convert an ISIS .mst or .iso file to a JSON array')
|
||||
|
||||
# add the arguments
|
||||
parser.add_argument(
|
||||
'file_name', metavar='INPUT.(mst|iso)',
|
||||
help='.mst or .iso file to read')
|
||||
parser.add_argument(
|
||||
'-o', '--out', type=argparse.FileType('w'), default=sys.stdout,
|
||||
metavar='OUTPUT.json',
|
||||
help='the file where the JSON output should be written'
|
||||
' (default: write to stdout)')
|
||||
parser.add_argument(
|
||||
'-c', '--couch', action='store_true',
|
||||
help='output array within a "docs" item in a JSON document'
|
||||
' for bulk insert to CouchDB via POST to db/_bulk_docs')
|
||||
parser.add_argument(
|
||||
'-m', '--mongo', action='store_true',
|
||||
help='output individual records as separate JSON dictionaries, one'
|
||||
' per line for bulk insert to MongoDB via mongoimport utility')
|
||||
parser.add_argument(
|
||||
'-t', '--type', type=int, metavar='ISIS_JSON_TYPE', default=1,
|
||||
help='ISIS-JSON type, sets field structure: 1=string, 2=alist,'
|
||||
' 3=dict (default=1)')
|
||||
parser.add_argument(
|
||||
'-q', '--qty', type=int, default=DEFAULT_QTY,
|
||||
help='maximum quantity of records to read (default=ALL)')
|
||||
parser.add_argument(
|
||||
'-s', '--skip', type=int, default=0,
|
||||
help='records to skip from start of .mst (default=0)')
|
||||
parser.add_argument(
|
||||
'-i', '--id', type=int, metavar='TAG_NUMBER', default=0,
|
||||
help='generate an "_id" from the given unique TAG field number'
|
||||
' for each record')
|
||||
parser.add_argument(
|
||||
'-u', '--uuid', action='store_true',
|
||||
help='generate an "_id" with a random UUID for each record')
|
||||
parser.add_argument(
|
||||
'-p', '--prefix', type=str, metavar='PREFIX', default='',
|
||||
help='concatenate prefix to every numeric field tag'
|
||||
' (ex. 99 becomes "v99")')
|
||||
parser.add_argument(
|
||||
'-n', '--mfn', action='store_true',
|
||||
help='generate an "_id" from the MFN of each record'
|
||||
' (available only for .mst input)')
|
||||
parser.add_argument(
|
||||
'-k', '--constant', type=str, metavar='TAG:VALUE', default='',
|
||||
help='Include a constant tag:value in every record (ex. -k type:AS)')
|
||||
|
||||
'''
|
||||
# TODO: implement this to export large quantities of records to CouchDB
|
||||
parser.add_argument(
|
||||
'-r', '--repeat', type=int, default=1,
|
||||
help='repeat operation, saving multiple JSON files'
|
||||
' (default=1, use -r 0 to repeat until end of input)')
|
||||
'''
|
||||
# parse the command line
|
||||
args = parser.parse_args()
|
||||
if args.file_name.lower().endswith('.mst'):
|
||||
input_gen_func = iter_mst_records # <5>
|
||||
else:
|
||||
if args.mfn:
|
||||
print('UNSUPORTED: -n/--mfn option only available for .mst input.')
|
||||
raise SystemExit
|
||||
input_gen_func = iter_iso_records # <6>
|
||||
input_gen = input_gen_func(args.file_name, args.type) # <7>
|
||||
if args.couch:
|
||||
args.out.write('{ "docs" : ')
|
||||
write_json(input_gen, args.file_name, args.out, args.qty, # <8>
|
||||
args.skip, args.id, args.uuid, args.mongo, args.mfn,
|
||||
args.type, args.prefix, args.constant)
|
||||
if args.couch:
|
||||
args.out.write('}\n')
|
||||
args.out.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
# END ISIS2JSON
|
@ -1,167 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
# ISO-2709 file reader
|
||||
#
|
||||
# Copyright (C) 2010 BIREME/PAHO/WHO
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from struct import unpack
|
||||
|
||||
CR = '\x0D' # \r
|
||||
LF = '\x0A' # \n
|
||||
IS1 = '\x1F' # ECMA-48 Unit Separator
|
||||
IS2 = '\x1E' # ECMA-48 Record Separator / ISO-2709 field separator
|
||||
IS3 = '\x1D' # ECMA-48 Group Separator / ISO-2709 record separator
|
||||
LABEL_LEN = 24
|
||||
LABEL_FORMAT = '5s c 4s c c 5s 3s c c c c'
|
||||
TAG_LEN = 3
|
||||
DEFAULT_ENCODING = 'ASCII'
|
||||
SUBFIELD_DELIMITER = '^'
|
||||
|
||||
class IsoFile(object):
|
||||
|
||||
def __init__(self, filename, encoding = DEFAULT_ENCODING):
|
||||
self.file = open(filename, 'rb')
|
||||
self.encoding = encoding
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
return IsoRecord(self)
|
||||
|
||||
__next__ = next # Python 3 compatibility
|
||||
|
||||
def read(self, size):
|
||||
''' read and drop all CR and LF characters '''
|
||||
# TODO: this is inneficient but works, patches accepted!
|
||||
# NOTE: our fixtures include files which have no linebreaks,
|
||||
# files with CR-LF linebreaks and files with LF linebreaks
|
||||
chunks = []
|
||||
count = 0
|
||||
while count < size:
|
||||
chunk = self.file.read(size-count)
|
||||
if len(chunk) == 0:
|
||||
break
|
||||
chunk = chunk.replace(CR+LF,'')
|
||||
if CR in chunk:
|
||||
chunk = chunk.replace(CR,'')
|
||||
if LF in chunk:
|
||||
chunk = chunk.replace(LF,'')
|
||||
count += len(chunk)
|
||||
chunks.append(chunk)
|
||||
return ''.join(chunks)
|
||||
|
||||
def close(self):
|
||||
self.file.close()
|
||||
|
||||
class IsoRecord(object):
|
||||
label_part_names = ('rec_len rec_status impl_codes indicator_len identifier_len'
|
||||
' base_addr user_defined'
|
||||
# directory map:
|
||||
' fld_len_len start_len impl_len reserved').split()
|
||||
rec_len = 0
|
||||
|
||||
def __init__(self, iso_file=None):
|
||||
self.iso_file = iso_file
|
||||
self.load_label()
|
||||
self.load_directory()
|
||||
self.load_fields()
|
||||
|
||||
def __len__(self):
|
||||
return self.rec_len
|
||||
|
||||
def load_label(self):
|
||||
label = self.iso_file.read(LABEL_LEN)
|
||||
if len(label) == 0:
|
||||
raise StopIteration
|
||||
elif len(label) != 24:
|
||||
raise ValueError('Invalid record label: "%s"' % label)
|
||||
parts = unpack(LABEL_FORMAT, label)
|
||||
for name, part in zip(self.label_part_names, parts):
|
||||
if name.endswith('_len') or name.endswith('_addr'):
|
||||
part = int(part)
|
||||
setattr(self, name, part)
|
||||
|
||||
def show_label(self):
|
||||
for name in self.label_part_names:
|
||||
print('%15s : %r' % (name, getattr(self, name)))
|
||||
|
||||
def load_directory(self):
|
||||
fmt_dir = '3s %ss %ss %ss' % (self.fld_len_len, self.start_len, self.impl_len)
|
||||
entry_len = TAG_LEN + self.fld_len_len + self.start_len + self.impl_len
|
||||
self.directory = []
|
||||
while True:
|
||||
char = self.iso_file.read(1)
|
||||
if char.isdigit():
|
||||
entry = char + self.iso_file.read(entry_len-1)
|
||||
entry = Field(* unpack(fmt_dir, entry))
|
||||
self.directory.append(entry)
|
||||
else:
|
||||
break
|
||||
|
||||
def load_fields(self):
|
||||
for field in self.directory:
|
||||
if self.indicator_len > 0:
|
||||
field.indicator = self.iso_file.read(self.indicator_len)
|
||||
# XXX: lilacs30.iso has an identifier_len == 2,
|
||||
# but we need to ignore it to succesfully read the field contents
|
||||
# TODO: find out when to ignore the idenfier_len,
|
||||
# or fix the lilacs30.iso fixture
|
||||
#
|
||||
##if self.identifier_len > 0: #
|
||||
## field.identifier = self.iso_file.read(self.identifier_len)
|
||||
value = self.iso_file.read(len(field))
|
||||
assert len(value) == len(field)
|
||||
field.value = value[:-1] # remove trailing field separator
|
||||
self.iso_file.read(1) # discard record separator
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
for field in self.directory:
|
||||
yield(field)
|
||||
|
||||
__next__ = next # Python 3 compatibility
|
||||
|
||||
def dump(self):
|
||||
for field in self.directory:
|
||||
print('%3s %r' % (field.tag, field.value))
|
||||
|
||||
class Field(object):
|
||||
|
||||
def __init__(self, tag, len, start, impl):
|
||||
self.tag = tag
|
||||
self.len = int(len)
|
||||
self.start = int(start)
|
||||
self.impl = impl
|
||||
|
||||
def show(self):
|
||||
for name in 'tag len start impl'.split():
|
||||
print('%15s : %r' % (name, getattr(self, name)))
|
||||
|
||||
def __len__(self):
|
||||
return self.len
|
||||
|
||||
def test():
|
||||
import doctest
|
||||
doctest.testfile('iso2709_test.txt')
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
test()
|
||||
|
@ -1,142 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
# ISIS-DM: the ISIS Data Model API
|
||||
#
|
||||
# Copyright (C) 2010 BIREME/PAHO/WHO
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from collections import namedtuple
|
||||
import re
|
||||
|
||||
|
||||
MAIN_SUBFIELD_KEY = '_'
|
||||
SUBFIELD_MARKER_RE = re.compile(r'\^([a-z0-9])', re.IGNORECASE)
|
||||
DEFAULT_ENCODING = u'utf-8'
|
||||
|
||||
def expand(content, subkeys=None):
|
||||
''' Parse a field into an association list of keys and subfields
|
||||
|
||||
>>> expand('zero^1one^2two^3three')
|
||||
[('_', 'zero'), ('1', 'one'), ('2', 'two'), ('3', 'three')]
|
||||
|
||||
'''
|
||||
if subkeys is None:
|
||||
regex = SUBFIELD_MARKER_RE
|
||||
elif subkeys == '':
|
||||
return [(MAIN_SUBFIELD_KEY, content)]
|
||||
else:
|
||||
regex = re.compile(r'\^(['+subkeys+'])', re.IGNORECASE)
|
||||
content = content.replace('^^', '^^ ')
|
||||
parts = []
|
||||
start = 0
|
||||
key = MAIN_SUBFIELD_KEY
|
||||
while True:
|
||||
found = regex.search(content, start)
|
||||
if found is None: break
|
||||
parts.append((key, content[start:found.start()].rstrip()))
|
||||
key = found.group(1).lower()
|
||||
start = found.end()
|
||||
parts.append((key, content[start:].rstrip()))
|
||||
return parts
|
||||
|
||||
|
||||
class CompositeString(object):
|
||||
''' Represent an Isis field, with subfields, using
|
||||
Python native datastructures
|
||||
|
||||
>>> author = CompositeString('John Tenniel^xillustrator',
|
||||
... subkeys='x')
|
||||
>>> unicode(author)
|
||||
u'John Tenniel^xillustrator'
|
||||
'''
|
||||
|
||||
def __init__(self, isis_raw, subkeys=None, encoding=DEFAULT_ENCODING):
|
||||
if not isinstance(isis_raw, basestring):
|
||||
raise TypeError('%r value must be unicode or str instance' % isis_raw)
|
||||
|
||||
self.__isis_raw = isis_raw.decode(encoding)
|
||||
self.__expanded = expand(self.__isis_raw, subkeys)
|
||||
|
||||
def __getitem__(self, key):
|
||||
for subfield in self.__expanded:
|
||||
if subfield[0] == key:
|
||||
return subfield[1]
|
||||
else:
|
||||
raise KeyError(key)
|
||||
|
||||
def __iter__(self):
|
||||
return (subfield[0] for subfield in self.__expanded)
|
||||
|
||||
def items(self):
|
||||
return self.__expanded
|
||||
|
||||
def __unicode__(self):
|
||||
return self.__isis_raw
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__isis_raw)
|
||||
|
||||
|
||||
class CompositeField(object):
|
||||
''' Represent an Isis field, with subfields, using
|
||||
Python native datastructures
|
||||
|
||||
>>> author = CompositeField( [('name','Braz, Marcelo'),('role','writer')] )
|
||||
>>> print author['name']
|
||||
Braz, Marcelo
|
||||
>>> print author['role']
|
||||
writer
|
||||
>>> author
|
||||
CompositeField((('name', 'Braz, Marcelo'), ('role', 'writer')))
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, value, subkeys=None):
|
||||
if subkeys is None:
|
||||
subkeys = [item[0] for item in value]
|
||||
try:
|
||||
value_as_dict = dict(value)
|
||||
except TypeError:
|
||||
raise TypeError('%r value must be a key-value structure' % self)
|
||||
|
||||
for key in value_as_dict:
|
||||
if key not in subkeys:
|
||||
raise TypeError('Unexpected keyword %r' % key)
|
||||
|
||||
self.value = tuple([(key, value_as_dict.get(key,None)) for key in subkeys])
|
||||
|
||||
def __getitem__(self, key):
|
||||
return dict(self.value)[key]
|
||||
|
||||
def __repr__(self):
|
||||
return "CompositeField(%s)" % str(self.items())
|
||||
|
||||
def items(self):
|
||||
return self.value
|
||||
|
||||
def __unicode__(self):
|
||||
unicode(self.items())
|
||||
|
||||
def __str__(self):
|
||||
str(self.items())
|
||||
|
||||
|
||||
def test():
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
if __name__=='__main__':
|
||||
test()
|
@ -1,37 +0,0 @@
|
||||
"""
|
||||
Sentence: access words by index
|
||||
|
||||
>>> text = 'To be, or not to be, that is the question'
|
||||
>>> s = Sentence(text)
|
||||
>>> len(s)
|
||||
10
|
||||
>>> s[1], s[5]
|
||||
('be', 'be')
|
||||
>>> s
|
||||
Sentence('To be, or no... the question')
|
||||
|
||||
"""
|
||||
|
||||
# tag::SENTENCE_SEQ[]
|
||||
import re
|
||||
import reprlib
|
||||
|
||||
RE_WORD = re.compile(r'\w+')
|
||||
|
||||
|
||||
class Sentence:
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
self.words = RE_WORD.findall(text) # <1>
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.words[index] # <2>
|
||||
|
||||
def __len__(self): # <3>
|
||||
return len(self.words)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Sentence(%s)' % reprlib.repr(self.text) # <4>
|
||||
|
||||
# end::SENTENCE_SEQ[]
|
@ -1,54 +0,0 @@
|
||||
==============================
|
||||
Tests for a ``Sentence`` class
|
||||
==============================
|
||||
|
||||
A ``Sentence`` is built from a ``str`` and allows iteration
|
||||
word-by-word.
|
||||
|
||||
::
|
||||
>>> 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
|
||||
|
||||
|
||||
Any punctuation is skipped while iterating::
|
||||
|
||||
>>> 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']
|
||||
|
||||
|
||||
White space including line breaks are also ignored::
|
||||
|
||||
>>> 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']
|
||||
|
||||
|
||||
Accented Latin characters are also recognized as word characters::
|
||||
|
||||
>>> 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']
|
@ -1,28 +0,0 @@
|
||||
"""
|
||||
Sentence: iterate over words using a generator function
|
||||
"""
|
||||
|
||||
# tag::SENTENCE_GEN[]
|
||||
import re
|
||||
import reprlib
|
||||
|
||||
RE_WORD = re.compile(r'\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):
|
||||
for word in self.words: # <1>
|
||||
yield word # <2>
|
||||
return # <3>
|
||||
|
||||
# done! <4>
|
||||
|
||||
# end::SENTENCE_GEN[]
|
@ -1,24 +0,0 @@
|
||||
"""
|
||||
Sentence: iterate over words using a generator function
|
||||
"""
|
||||
|
||||
# tag::SENTENCE_GEN2[]
|
||||
import re
|
||||
import reprlib
|
||||
|
||||
RE_WORD = re.compile('r\w+')
|
||||
|
||||
|
||||
class Sentence:
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text # <1>
|
||||
|
||||
def __repr__(self):
|
||||
return 'Sentence(%s)' % reprlib.repr(self.text)
|
||||
|
||||
def __iter__(self):
|
||||
for match in RE_WORD.finditer(self.text): # <2>
|
||||
yield match.group() # <3>
|
||||
|
||||
# end::SENTENCE_GEN2[]
|
@ -1,44 +0,0 @@
|
||||
"""
|
||||
Sentence: iterate over words using a generator expression
|
||||
"""
|
||||
|
||||
# tag::SENTENCE_GENEXP[]
|
||||
import re
|
||||
import reprlib
|
||||
|
||||
RE_WORD = re.compile(r'\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()
|
@ -1,65 +0,0 @@
|
||||
"""
|
||||
Sentence: iterate over words using the Iterator Pattern, take #1
|
||||
|
||||
WARNING: the Iterator Pattern is much simpler in idiomatic Python;
|
||||
see: sentence_gen*.py.
|
||||
"""
|
||||
|
||||
# tag::SENTENCE_ITER[]
|
||||
import re
|
||||
import reprlib
|
||||
|
||||
RE_WORD = re.compile(r'\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 SentenceIterator(self.words) # <2>
|
||||
|
||||
|
||||
class SentenceIterator:
|
||||
|
||||
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()
|
@ -1,37 +0,0 @@
|
||||
"""
|
||||
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(r'\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
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user