removed 1st edition code
This commit is contained in:
@@ -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
Reference in New Issue
Block a user