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:
|
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))
|
||||||
|
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 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[]
|
||||||
|
@ -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}')
|
||||||
|
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 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>
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
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:
|
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>
|
@ -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>
|
||||||
|
@ -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 = ''
|
||||||
|
|
||||||
|
@ -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__':
|
||||||
|
@ -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__':
|
||||||
|
@ -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
|
||||||
|
@ -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[]
|
|
||||||
|
@ -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[]
|
||||||
|
@ -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()
|
||||||
|
@ -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__':
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
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…
x
Reference in New Issue
Block a user