ch01-12: clean up by @eumiro

This commit is contained in:
Luciano Ramalho 2021-02-14 20:28:07 -03:00
parent 584a7f21ca
commit 03ace4f4ae
33 changed files with 1383 additions and 86 deletions

View File

@ -0,0 +1,12 @@
>>> from vector2d import Vector
>>> v1 = Vector(2, 4)
>>> v2 = Vector(2, 1)
>>> v1 + v2
Vector(4, 5)
>>> v = Vector(3, 4)
>>> abs(v)
5.0
>>> v * 3
Vector(9, 12)
>>> abs(v * 3)
15.0

View File

@ -29,7 +29,7 @@ Scalar multiplication::
""" """
from math import hypot import math
class Vector: class Vector:
@ -41,7 +41,7 @@ class Vector:
return f'Vector({self.x!r}, {self.y!r})' return f'Vector({self.x!r}, {self.y!r})'
def __abs__(self): def __abs__(self):
return hypot(self.x, self.y) return math.hypot(self.x, self.y)
def __bool__(self): def __bool__(self):
return bool(abs(self)) return bool(abs(self))

View File

@ -1,3 +0,0 @@
floats-*.txt
floats-*.npy
floats.bin

View File

@ -1,21 +0,0 @@
# An Array of Sequences
Sample code for Chapter 2 of _Fluent Python 2e_ by Luciano Ramalho (O'Reilly, 2020)
## Running the tests
### Doctests
Use Python's standard ``doctest`` module, for example:
$ python3 -m doctest bisect_demo.py -v
### Jupyter Notebook
Install ``pytest`` and the ``nbval`` plugin:
$ pip install pytest nbval
Run:
$ pytest --nbval

4
02-array-seq/README.rst Normal file
View File

@ -0,0 +1,4 @@
Sample code for Chapter 2 - "An array of sequences"
From the book "Fluent Python 2e" by Luciano Ramalho (O'Reilly)
http://shop.oreilly.com/product/0636920032519.do

View File

@ -36,7 +36,7 @@ Demonstration of ``bisect.bisect_left``::
""" """
# BEGIN BISECT_DEMO # tag::BISECT_DEMO[]
import bisect import bisect
import sys import sys
@ -62,4 +62,4 @@ if __name__ == '__main__':
print('haystack ->', ' '.join(f'{n:2}' for n in HAYSTACK)) print('haystack ->', ' '.join(f'{n:2}' for n in HAYSTACK))
demo(bisect_fn) demo(bisect_fn)
# END BISECT_DEMO # end::BISECT_DEMO[]

View File

@ -9,4 +9,4 @@ my_list = []
for i in range(SIZE): for i in range(SIZE):
new_item = random.randrange(SIZE * 2) new_item = random.randrange(SIZE * 2)
bisect.insort(my_list, new_item) bisect.insort(my_list, new_item)
print(f'{new_item:2} ->', my_list) print(f'{new_item:2d} -> {my_list}')

View File

@ -0,0 +1,238 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# memoryviews\n",
"\n",
"## References\n",
"\n",
"* [PEP-3118](https://www.python.org/dev/peps/pep-3118/) Revising the buffer protocol (2006)\n",
"* [issue14130](https://bugs.python.org/issue14130) memoryview: add multi-dimensional indexing and slicing (2012)"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3.7.1 (default, Dec 14 2018, 13:28:58) \n",
"[Clang 4.0.1 (tags/RELEASE_401/final)]\n"
]
}
],
"source": [
"import sys\n",
"print(sys.version)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<memory at 0x1045a31c8>"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"mv1d = memoryview(bytes(range(35, 50)))\n",
"mv1d"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(mv1d)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<memory at 0x10464a120>"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"mv2d = mv1d.cast('B', [3, 5])\n",
"mv2d"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(3, 5)"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"mv2d.shape"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(mv2d)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[[35, 36, 37, 38, 39], [40, 41, 42, 43, 44], [45, 46, 47, 48, 49]]"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"mv2d.tolist()"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[35, 36, 37, 38, 39]\n",
"[40, 41, 42, 43, 44]\n",
"[45, 46, 47, 48, 49]\n"
]
}
],
"source": [
"for row in mv2d.tolist():\n",
" print(row)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"42"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"mv2d[1, 2]"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"42"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"mv2d.tolist()[1][2]"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@ -1,11 +0,0 @@
Assigning to Slices
===================
An example that raises an error::
>>> l = list(range(10))
>>> l[2:5] = 100
Traceback (most recent call last):
...
TypeError: can only assign an iterable

View File

@ -15,7 +15,7 @@ with open(sys.argv[1], encoding='utf-8') as fp:
for line_no, line in enumerate(fp, 1): for line_no, line in enumerate(fp, 1):
for match in WORD_RE.finditer(line): for match in WORD_RE.finditer(line):
word = match.group() word = match.group()
column_no = match.start()+1 column_no = match.start() + 1
location = (line_no, column_no) location = (line_no, column_no)
# this is ugly; coded like this to make a point # this is ugly; coded like this to make a point
occurrences = index.get(word, []) # <1> occurrences = index.get(word, []) # <1>

View File

@ -16,7 +16,7 @@ with open(sys.argv[1], encoding='utf-8') as fp:
for line_no, line in enumerate(fp, 1): for line_no, line in enumerate(fp, 1):
for match in WORD_RE.finditer(line): for match in WORD_RE.finditer(line):
word = match.group() word = match.group()
column_no = match.start() + 1 column_no = match.start()+1
location = (line_no, column_no) location = (line_no, column_no)
index[word].append(location) # <2> index[word].append(location) # <2>

View File

@ -56,7 +56,7 @@ class TransformDict(MutableMapping):
@property @property
def transform_func(self): def transform_func(self):
"""This TransformDict's transformation function""" """This is TransformDict's transformation function"""
return self._transform return self._transform
# Minimum set of methods required for MutableMapping # Minimum set of methods required for MutableMapping

View File

@ -0,0 +1,45 @@
import sys
import collections
from unicodedata import name, category
def category_stats():
counts = collections.Counter()
firsts = {}
for code in range(sys.maxunicode + 1):
char = chr(code)
cat = category(char)
if cat not in counts:
firsts[cat] = char
counts[cat] += 1
return counts, firsts
def category_scan(desired):
for code in range(sys.maxunicode + 1):
char = chr(code)
if category(char) == desired:
yield char
def main(args):
count = 0
if len(args) == 2:
for char in category_scan(args[1]):
print(char, end=' ')
count += 1
if count > 200:
break
print()
print(count, 'characters shown')
else:
counts, firsts = category_stats()
for cat, count in counts.most_common():
first = firsts[cat]
if cat == 'Cs':
first = f'(surrogate U+{ord(first):04X})'
print(f'{count:6} {cat} {first}')
if __name__ == '__main__':
main(sys.argv)

View File

@ -1,5 +1,6 @@
""" """
Radical folding and text sanitizing. Radical folding and diacritic mark removal.
Handling a string with `cp1252` symbols: Handling a string with `cp1252` symbols:
@ -45,30 +46,33 @@ def shave_marks_latin(txt):
"""Remove all diacritic marks from Latin base characters""" """Remove all diacritic marks from Latin base characters"""
norm_txt = unicodedata.normalize('NFD', txt) # <1> norm_txt = unicodedata.normalize('NFD', txt) # <1>
latin_base = False latin_base = False
keepers = [] preserve = []
for c in norm_txt: for c in norm_txt:
if unicodedata.combining(c) and latin_base: # <2> if unicodedata.combining(c) and latin_base: # <2>
continue # ignore diacritic on Latin base char continue # ignore diacritic on Latin base char
keepers.append(c) # <3> preserve.append(c) # <3>
# if it isn't combining char, it's a new base char # if it isn't combining char, it's a new base char
if not unicodedata.combining(c): # <4> if not unicodedata.combining(c): # <4>
latin_base = c in string.ascii_letters latin_base = c in string.ascii_letters
shaved = ''.join(keepers) shaved = ''.join(preserve)
return unicodedata.normalize('NFC', shaved) # <5> return unicodedata.normalize('NFC', shaved) # <5>
# end::SHAVE_MARKS_LATIN[] # end::SHAVE_MARKS_LATIN[]
# tag::ASCIIZE[] # tag::ASCIIZE[]
single_map = str.maketrans("""‚ƒ„ˆ‹‘’“”•–—˜›""", # <1> single_map = str.maketrans("""‚ƒ„ˆ‹‘’“”•–—˜›""", # <1>
"""'f"*^<''""---~>""") """'f"^<''""---~>""")
multi_map = str.maketrans({ # <2> multi_map = str.maketrans({ # <2>
'': '<euro>', '': 'EUR',
'': '...', '': '...',
'Æ': 'AE',
'æ': 'ae',
'Œ': 'OE', 'Œ': 'OE',
'': '(TM)',
'œ': 'oe', 'œ': 'oe',
'': '(TM)',
'': '<per mille>', '': '<per mille>',
'': '**', '': '**',
'': '***',
}) })
multi_map.update(single_map) # <3> multi_map.update(single_map) # <3>

View File

@ -1,8 +1,6 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import List # <1>
@dataclass @dataclass
class ClubMember: class ClubMember:
name: str name: str
guests: List[str] = field(default_factory=list) # <2> guests: list[str] = field(default_factory=list) # <1>

View File

@ -30,13 +30,13 @@ To fix, ``leo2`` must be created with an explicit ``handle``::
# tag::HACKERCLUB[] # tag::HACKERCLUB[]
from dataclasses import dataclass from dataclasses import dataclass
from typing import ClassVar, Set from typing import ClassVar
from club import ClubMember from club import ClubMember
@dataclass @dataclass
class HackerClubMember(ClubMember): class HackerClubMember(ClubMember):
all_handles: ClassVar[Set[str]] = set() all_handles: ClassVar[set[str]] = set()
handle: str = '' handle: str = ''

View File

@ -27,7 +27,7 @@ A complete resource record:
# tag::DATACLASS[] # tag::DATACLASS[]
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import List, Optional from typing import Optional
from enum import Enum, auto from enum import Enum, auto
from datetime import date from datetime import date
@ -43,12 +43,12 @@ class Resource:
"""Media resource description.""" """Media resource description."""
identifier: str # <2> identifier: str # <2>
title: str = '<untitled>' # <3> title: str = '<untitled>' # <3>
creators: List[str] = field(default_factory=list) creators: list[str] = field(default_factory=list)
date: Optional[date] = None # <4> date: Optional[date] = None # <4>
type: ResourceType = ResourceType.BOOK # <5> type: ResourceType = ResourceType.BOOK # <5>
description: str = '' description: str = ''
language: str = '' language: str = ''
subjects: List[str] = field(default_factory=list) subjects: list[str] = field(default_factory=list)
# end::DATACLASS[] # end::DATACLASS[]
@ -58,12 +58,12 @@ from typing import TypedDict
class ResourceDict(TypedDict): class ResourceDict(TypedDict):
identifier: str identifier: str
title: str title: str
creators: List[str] creators: list[str]
date: Optional[date] date: Optional[date]
type: ResourceType type: ResourceType
description: str description: str
language: str language: str
subjects: List[str] subjects: list[str]
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -41,7 +41,7 @@ A complete resource record:
""" """
from dataclasses import dataclass, field, fields from dataclasses import dataclass, field, fields
from typing import List, Optional, TypedDict from typing import Optional, TypedDict
from enum import Enum, auto from enum import Enum, auto
from datetime import date from datetime import date
@ -57,12 +57,12 @@ class Resource:
"""Media resource description.""" """Media resource description."""
identifier: str identifier: str
title: str = '<untitled>' title: str = '<untitled>'
creators: List[str] = field(default_factory=list) creators: list[str] = field(default_factory=list)
date: Optional[date] = None date: Optional[date] = None
type: ResourceType = ResourceType.BOOK type: ResourceType = ResourceType.BOOK
description: str = '' description: str = ''
language: str = '' language: str = ''
subjects: List[str] = field(default_factory=list) subjects: list[str] = field(default_factory=list)
# tag::REPR[] # tag::REPR[]
def __repr__(self): def __repr__(self):
@ -82,12 +82,12 @@ class Resource:
class ResourceDict(TypedDict): class ResourceDict(TypedDict):
identifier: str identifier: str
title: str title: str
creators: List[str] creators: list[str]
date: Optional[date] date: Optional[date]
type: ResourceType type: ResourceType
description: str description: str
language: str language: str
subjects: List[str] subjects: list[str]
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -19,7 +19,7 @@
# tag::CLIP_ANNOT[] # tag::CLIP_ANNOT[]
def clip(text: str, max_len: 'int > 0'=80) -> str: # <1> def clip(text: str, max_len: 'int > 0' = 80) -> str: # <1>
"""Return text clipped at the last space before or after max_len """Return text clipped at the last space before or after max_len
""" """
end = None end = None

View File

@ -26,6 +26,5 @@ from comparable import Comparable
CT = TypeVar('CT', bound=Comparable) CT = TypeVar('CT', bound=Comparable)
def top(series: Iterable[CT], length: int) -> List[CT]: def top(series: Iterable[CT], length: int) -> List[CT]:
ordered = sorted(series, reverse=True) return sorted(series, reverse=True)[:length]
return ordered[:length] # end::TOP[]
# end::TOP[]

View File

@ -8,12 +8,10 @@
""" """
# tag::GEOHASH[] # tag::GEOHASH[]
from typing import Tuple
from geolib import geohash as gh # type: ignore from geolib import geohash as gh # type: ignore
PRECISION = 9 PRECISION = 9
def geohash(lat_lon: Tuple[float, float]) -> str: def geohash(lat_lon: tuple[float, float]) -> str:
return gh.encode(*lat_lon, PRECISION) return gh.encode(*lat_lon, PRECISION)
# end::GEOHASH[] # end::GEOHASH[]

View File

@ -13,7 +13,7 @@ def mode(data: Iterable[T]) -> T:
def demo() -> None: def demo() -> None:
from typing import List, Set, TYPE_CHECKING from typing import List, Set, TYPE_CHECKING
pop: List[Set] = [set(), set()] pop: list[set] = [set(), set()]
m = mode(pop) m = mode(pop)
if TYPE_CHECKING: if TYPE_CHECKING:
reveal_type(pop) reveal_type(pop)
@ -22,4 +22,4 @@ def demo() -> None:
print(repr(m), type(m)) print(repr(m), type(m))
if __name__ == '__main__': if __name__ == '__main__':
demo() demo()

View File

@ -3,12 +3,12 @@ import functools
from clockdeco import clock from clockdeco import clock
@functools.lru_cache # <1> @functools.cache # <1>
@clock # <2> @clock # <2>
def fibonacci(n): def fibonacci(n):
if n < 2: if n < 2:
return n return n
return fibonacci(n - 2) + fibonacci(n - 1) return fibonacci(n-2) + fibonacci(n-1)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -70,7 +70,8 @@ class Order: # the Context
return self.total() - discount return self.total() - discount
def __repr__(self): def __repr__(self):
return f'<Order total: {self.total():.2f} due: {self.due():.2f}>' fmt = '<Order total: {:.2f} due: {:.2f}>'
return fmt.format(self.total(), self.due())
@typelogged @typelogged

View File

@ -1,6 +1,9 @@
#!/usr/bin/env jython
# NOTE: Jython is still Python 2.7 in late2020
import Confidential import Confidential
message = Confidential('top secret text') message = Confidential('top secret text')
secret_field = Confidential.getDeclaredField('secret') secret_field = Confidential.getDeclaredField('secret')
secret_field.setAccessible(True) # break the lock! secret_field.setAccessible(True) # break the lock!
print('message.secret =', secret_field.get(message)) print 'message.secret =', secret_field.get(message)

View File

@ -1,3 +1,6 @@
#!/usr/bin/env jython
# NOTE: Jython is still Python 2.7 in late2020
from java.lang.reflect import Modifier from java.lang.reflect import Modifier
import Confidential import Confidential
@ -7,5 +10,5 @@ for field in fields:
# list private fields only # list private fields only
if Modifier.isPrivate(field.getModifiers()): if Modifier.isPrivate(field.getModifiers()):
field.setAccessible(True) # break the lock field.setAccessible(True) # break the lock
print('field:', field) print 'field:', field
print('\t', field.getName(), '=', field.get(message)) print '\t', field.getName(), '=', field.get(message)

View File

@ -1,3 +1,6 @@
#!/usr/bin/env jython
# NOTE: Jython is still Python 2.7 in late2020
""" """
In the Jython registry file there is this line: In the Jython registry file there is this line:
@ -13,4 +16,4 @@ message = Confidential('top secret text')
for name in dir(message): for name in dir(message):
attr = getattr(message, name) attr = getattr(message, name)
if not callable(attr): # non-methods only if not callable(attr): # non-methods only
print(name + '\t=', attr) print name + '\t=', attr

View File

@ -83,8 +83,6 @@ Tests of hashing:
>>> len({v1, v2}) >>> len({v1, v2})
2 2
# end::VECTOR2D_V3_DEMO[]
""" """
from array import array from array import array

126
12-seq-hacking/vector_v1.py Normal file
View File

@ -0,0 +1,126 @@
"""
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
"""
# tag::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[]

164
12-seq-hacking/vector_v2.py Normal file
View File

@ -0,0 +1,164 @@
"""
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::
# tag::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: 'tuple' object cannot be interpreted as an integer
# end::VECTOR_V2_DEMO[]
"""
from array import array
import reprlib
import math
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 tuple(self) == tuple(other)
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
def __bool__(self):
return bool(abs(self))
# tag::VECTOR_V2[]
def __len__(self):
return len(self._components)
def __getitem__(self, key):
if isinstance(key, slice): # <1>
cls = type(self) # <2>
return cls(self._components[key]) # <3>
index = operator.index(key) # <4>
return self._components[index] # <5>
# end::VECTOR_V2[]
@classmethod
def frombytes(cls, octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv)

235
12-seq-hacking/vector_v3.py Normal file
View File

@ -0,0 +1,235 @@
"""
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: 'tuple' object cannot be interpreted as an integer
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 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 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, key):
if isinstance(key, slice):
cls = type(self)
return cls(self._components[key])
index = operator.index(key)
return self._components[index]
# tag::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[]
# tag::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)

217
12-seq-hacking/vector_v4.py Normal file
View File

@ -0,0 +1,217 @@
"""
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: 'tuple' object cannot be interpreted as an integer
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 codes 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 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, key):
if isinstance(key, slice):
cls = type(self)
return cls(self._components[key])
index = operator.index(key)
return self._components[index]
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)

284
12-seq-hacking/vector_v5.py Normal file
View File

@ -0,0 +1,284 @@
# tag::VECTOR_V5[]
"""
A multidimensional ``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 two 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 three 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: 'tuple' object cannot be interpreted as an integer
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 codes 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 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, key):
if isinstance(key, slice):
cls = type(self)
return cls(self._components[key])
index = operator.index(key)
return self._components[index]
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[]