ch01-12: clean up by @eumiro
This commit is contained in:
parent
584a7f21ca
commit
03ace4f4ae
12
01-data-model/vector2d.doctest
Normal file
12
01-data-model/vector2d.doctest
Normal 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
|
@ -29,7 +29,7 @@ Scalar multiplication::
|
||||
"""
|
||||
|
||||
|
||||
from math import hypot
|
||||
import math
|
||||
|
||||
class Vector:
|
||||
|
||||
@ -41,7 +41,7 @@ class Vector:
|
||||
return f'Vector({self.x!r}, {self.y!r})'
|
||||
|
||||
def __abs__(self):
|
||||
return hypot(self.x, self.y)
|
||||
return math.hypot(self.x, self.y)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(abs(self))
|
||||
|
3
02-array-seq/.gitignore
vendored
3
02-array-seq/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
floats-*.txt
|
||||
floats-*.npy
|
||||
floats.bin
|
@ -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
4
02-array-seq/README.rst
Normal 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
|
@ -36,7 +36,7 @@ Demonstration of ``bisect.bisect_left``::
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN BISECT_DEMO
|
||||
# tag::BISECT_DEMO[]
|
||||
import bisect
|
||||
import sys
|
||||
|
||||
@ -62,4 +62,4 @@ if __name__ == '__main__':
|
||||
print('haystack ->', ' '.join(f'{n:2}' for n in HAYSTACK))
|
||||
demo(bisect_fn)
|
||||
|
||||
# END BISECT_DEMO
|
||||
# end::BISECT_DEMO[]
|
||||
|
@ -9,4 +9,4 @@ my_list = []
|
||||
for i in range(SIZE):
|
||||
new_item = random.randrange(SIZE * 2)
|
||||
bisect.insort(my_list, new_item)
|
||||
print(f'{new_item:2} ->', my_list)
|
||||
print(f'{new_item:2d} -> {my_list}')
|
||||
|
238
02-array-seq/memoryviews.ipynb
Normal file
238
02-array-seq/memoryviews.ipynb
Normal 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
|
||||
}
|
@ -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
|
||||
|
@ -15,7 +15,7 @@ with open(sys.argv[1], encoding='utf-8') as fp:
|
||||
for line_no, line in enumerate(fp, 1):
|
||||
for match in WORD_RE.finditer(line):
|
||||
word = match.group()
|
||||
column_no = match.start()+1
|
||||
column_no = match.start() + 1
|
||||
location = (line_no, column_no)
|
||||
# this is ugly; coded like this to make a point
|
||||
occurrences = index.get(word, []) # <1>
|
||||
|
@ -16,7 +16,7 @@ with open(sys.argv[1], encoding='utf-8') as fp:
|
||||
for line_no, line in enumerate(fp, 1):
|
||||
for match in WORD_RE.finditer(line):
|
||||
word = match.group()
|
||||
column_no = match.start() + 1
|
||||
column_no = match.start()+1
|
||||
location = (line_no, column_no)
|
||||
index[word].append(location) # <2>
|
||||
|
||||
|
@ -56,7 +56,7 @@ class TransformDict(MutableMapping):
|
||||
|
||||
@property
|
||||
def transform_func(self):
|
||||
"""This TransformDict's transformation function"""
|
||||
"""This is TransformDict's transformation function"""
|
||||
return self._transform
|
||||
|
||||
# Minimum set of methods required for MutableMapping
|
||||
|
45
04-text-byte/categories.py
Normal file
45
04-text-byte/categories.py
Normal 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)
|
@ -1,5 +1,6 @@
|
||||
|
||||
"""
|
||||
Radical folding and text sanitizing.
|
||||
Radical folding and diacritic mark removal.
|
||||
|
||||
Handling a string with `cp1252` symbols:
|
||||
|
||||
@ -45,30 +46,33 @@ def shave_marks_latin(txt):
|
||||
"""Remove all diacritic marks from Latin base characters"""
|
||||
norm_txt = unicodedata.normalize('NFD', txt) # <1>
|
||||
latin_base = False
|
||||
keepers = []
|
||||
preserve = []
|
||||
for c in norm_txt:
|
||||
if unicodedata.combining(c) and latin_base: # <2>
|
||||
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 not unicodedata.combining(c): # <4>
|
||||
latin_base = c in string.ascii_letters
|
||||
shaved = ''.join(keepers)
|
||||
shaved = ''.join(preserve)
|
||||
return unicodedata.normalize('NFC', shaved) # <5>
|
||||
# end::SHAVE_MARKS_LATIN[]
|
||||
|
||||
# tag::ASCIIZE[]
|
||||
single_map = str.maketrans("""‚ƒ„†ˆ‹‘’“”•–—˜›""", # <1>
|
||||
"""'f"*^<''""---~>""")
|
||||
single_map = str.maketrans("""‚ƒ„ˆ‹‘’“”•–—˜›""", # <1>
|
||||
"""'f"^<''""---~>""")
|
||||
|
||||
multi_map = str.maketrans({ # <2>
|
||||
'€': '<euro>',
|
||||
'€': 'EUR',
|
||||
'…': '...',
|
||||
'Æ': 'AE',
|
||||
'æ': 'ae',
|
||||
'Œ': 'OE',
|
||||
'™': '(TM)',
|
||||
'œ': 'oe',
|
||||
'™': '(TM)',
|
||||
'‰': '<per mille>',
|
||||
'‡': '**',
|
||||
'†': '**',
|
||||
'‡': '***',
|
||||
})
|
||||
|
||||
multi_map.update(single_map) # <3>
|
@ -1,8 +1,6 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List # <1>
|
||||
|
||||
@dataclass
|
||||
class ClubMember:
|
||||
|
||||
name: str
|
||||
guests: List[str] = field(default_factory=list) # <2>
|
||||
guests: list[str] = field(default_factory=list) # <1>
|
||||
|
@ -30,13 +30,13 @@ To fix, ``leo2`` must be created with an explicit ``handle``::
|
||||
|
||||
# tag::HACKERCLUB[]
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar, Set
|
||||
from typing import ClassVar
|
||||
from club import ClubMember
|
||||
|
||||
@dataclass
|
||||
class HackerClubMember(ClubMember):
|
||||
|
||||
all_handles: ClassVar[Set[str]] = set()
|
||||
all_handles: ClassVar[set[str]] = set()
|
||||
|
||||
handle: str = ''
|
||||
|
||||
|
@ -27,7 +27,7 @@ A complete resource record:
|
||||
|
||||
# tag::DATACLASS[]
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
from typing import Optional
|
||||
from enum import Enum, auto
|
||||
from datetime import date
|
||||
|
||||
@ -43,12 +43,12 @@ class Resource:
|
||||
"""Media resource description."""
|
||||
identifier: str # <2>
|
||||
title: str = '<untitled>' # <3>
|
||||
creators: List[str] = field(default_factory=list)
|
||||
creators: list[str] = field(default_factory=list)
|
||||
date: Optional[date] = None # <4>
|
||||
type: ResourceType = ResourceType.BOOK # <5>
|
||||
description: str = ''
|
||||
language: str = ''
|
||||
subjects: List[str] = field(default_factory=list)
|
||||
subjects: list[str] = field(default_factory=list)
|
||||
# end::DATACLASS[]
|
||||
|
||||
|
||||
@ -58,12 +58,12 @@ from typing import TypedDict
|
||||
class ResourceDict(TypedDict):
|
||||
identifier: str
|
||||
title: str
|
||||
creators: List[str]
|
||||
creators: list[str]
|
||||
date: Optional[date]
|
||||
type: ResourceType
|
||||
description: str
|
||||
language: str
|
||||
subjects: List[str]
|
||||
subjects: list[str]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -41,7 +41,7 @@ A complete resource record:
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field, fields
|
||||
from typing import List, Optional, TypedDict
|
||||
from typing import Optional, TypedDict
|
||||
from enum import Enum, auto
|
||||
from datetime import date
|
||||
|
||||
@ -57,12 +57,12 @@ class Resource:
|
||||
"""Media resource description."""
|
||||
identifier: str
|
||||
title: str = '<untitled>'
|
||||
creators: List[str] = field(default_factory=list)
|
||||
creators: list[str] = field(default_factory=list)
|
||||
date: Optional[date] = None
|
||||
type: ResourceType = ResourceType.BOOK
|
||||
description: str = ''
|
||||
language: str = ''
|
||||
subjects: List[str] = field(default_factory=list)
|
||||
subjects: list[str] = field(default_factory=list)
|
||||
|
||||
# tag::REPR[]
|
||||
def __repr__(self):
|
||||
@ -82,12 +82,12 @@ class Resource:
|
||||
class ResourceDict(TypedDict):
|
||||
identifier: str
|
||||
title: str
|
||||
creators: List[str]
|
||||
creators: list[str]
|
||||
date: Optional[date]
|
||||
type: ResourceType
|
||||
description: str
|
||||
language: str
|
||||
subjects: List[str]
|
||||
subjects: list[str]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
# 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
|
||||
"""
|
||||
end = None
|
||||
|
@ -26,6 +26,5 @@ from comparable import Comparable
|
||||
CT = TypeVar('CT', bound=Comparable)
|
||||
|
||||
def top(series: Iterable[CT], length: int) -> List[CT]:
|
||||
ordered = sorted(series, reverse=True)
|
||||
return ordered[:length]
|
||||
# end::TOP[]
|
||||
return sorted(series, reverse=True)[:length]
|
||||
# end::TOP[]
|
||||
|
@ -8,12 +8,10 @@
|
||||
"""
|
||||
|
||||
# tag::GEOHASH[]
|
||||
from typing import Tuple
|
||||
|
||||
from geolib import geohash as gh # type: ignore
|
||||
|
||||
PRECISION = 9
|
||||
|
||||
def geohash(lat_lon: Tuple[float, float]) -> str:
|
||||
def geohash(lat_lon: tuple[float, float]) -> str:
|
||||
return gh.encode(*lat_lon, PRECISION)
|
||||
# end::GEOHASH[]
|
||||
# end::GEOHASH[]
|
||||
|
@ -13,7 +13,7 @@ def mode(data: Iterable[T]) -> T:
|
||||
|
||||
def demo() -> None:
|
||||
from typing import List, Set, TYPE_CHECKING
|
||||
pop: List[Set] = [set(), set()]
|
||||
pop: list[set] = [set(), set()]
|
||||
m = mode(pop)
|
||||
if TYPE_CHECKING:
|
||||
reveal_type(pop)
|
||||
@ -22,4 +22,4 @@ def demo() -> None:
|
||||
print(repr(m), type(m))
|
||||
|
||||
if __name__ == '__main__':
|
||||
demo()
|
||||
demo()
|
||||
|
@ -3,12 +3,12 @@ import functools
|
||||
from clockdeco import clock
|
||||
|
||||
|
||||
@functools.lru_cache # <1>
|
||||
@functools.cache # <1>
|
||||
@clock # <2>
|
||||
def fibonacci(n):
|
||||
if n < 2:
|
||||
return n
|
||||
return fibonacci(n - 2) + fibonacci(n - 1)
|
||||
return fibonacci(n-2) + fibonacci(n-1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
@ -70,7 +70,8 @@ class Order: # the Context
|
||||
return self.total() - discount
|
||||
|
||||
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
|
||||
|
@ -1,6 +1,9 @@
|
||||
#!/usr/bin/env jython
|
||||
# NOTE: Jython is still Python 2.7 in late2020
|
||||
|
||||
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))
|
||||
print 'message.secret =', secret_field.get(message)
|
||||
|
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env jython
|
||||
# NOTE: Jython is still Python 2.7 in late2020
|
||||
|
||||
from java.lang.reflect import Modifier
|
||||
import Confidential
|
||||
|
||||
@ -7,5 +10,5 @@ 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))
|
||||
print 'field:', field
|
||||
print '\t', field.getName(), '=', field.get(message)
|
||||
|
@ -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:
|
||||
|
||||
@ -13,4 +16,4 @@ 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)
|
||||
print name + '\t=', attr
|
||||
|
@ -83,8 +83,6 @@ Tests of hashing:
|
||||
>>> len({v1, v2})
|
||||
2
|
||||
|
||||
# end::VECTOR2D_V3_DEMO[]
|
||||
|
||||
"""
|
||||
|
||||
from array import array
|
||||
|
126
12-seq-hacking/vector_v1.py
Normal file
126
12-seq-hacking/vector_v1.py
Normal 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
164
12-seq-hacking/vector_v2.py
Normal 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
235
12-seq-hacking/vector_v3.py
Normal 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
217
12-seq-hacking/vector_v4.py
Normal 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
284
12-seq-hacking/vector_v5.py
Normal 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[]
|
Loading…
Reference in New Issue
Block a user