updade from Atlas repo

This commit is contained in:
Luciano Ramalho 2021-05-21 18:56:12 -03:00
parent c518bf851e
commit 8a330d822b
120 changed files with 2190 additions and 1184 deletions

View File

@ -17,7 +17,7 @@ d1 = dict(DIAL_CODES) # <1>
print('d1:', d1.keys())
d2 = dict(sorted(DIAL_CODES)) # <2>
print('d2:', d2.keys())
d3 = dict(sorted(DIAL_CODES, key=lambda x:x[1])) # <3>
d3 = dict(sorted(DIAL_CODES, key=lambda x: x[1])) # <3>
print('d3:', d3.keys())
assert d1 == d2 and d2 == d3 # <4>
# end::DIALCODES[]

View File

@ -5,8 +5,8 @@
# tag::INDEX[]
"""Build an index mapping word -> list of occurrences"""
import sys
import re
import sys
WORD_RE = re.compile(r'\w+')
@ -15,11 +15,11 @@ 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.setdefault(word, []).append(location) # <1>
# print in alphabetical order
# display in alphabetical order
for word in sorted(index, key=str.upper):
print(word, index[word])
# end::INDEX[]

View File

@ -5,8 +5,8 @@
# tag::INDEX0[]
"""Build an index mapping word -> list of occurrences"""
import sys
import re
import sys
WORD_RE = re.compile(r'\w+')
@ -22,7 +22,7 @@ with open(sys.argv[1], encoding='utf-8') as fp:
occurrences.append(location) # <2>
index[word] = occurrences # <3>
# print in alphabetical order
# display in alphabetical order
for word in sorted(index, key=str.upper): # <4>
print(word, index[word])
# end::INDEX0[]

View File

@ -5,9 +5,9 @@
# tag::INDEX_DEFAULT[]
"""Build an index mapping word -> list of occurrences"""
import sys
import re
import collections
import re
import sys
WORD_RE = re.compile(r'\w+')
@ -16,11 +16,11 @@ 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>
# print in alphabetical order
# display in alphabetical order
for word in sorted(index, key=str.upper):
print(word, index[word])
# end::INDEX_DEFAULT[]

View File

@ -1,6 +1,6 @@
import sys
import collections
from unicodedata import name, category
from unicodedata import category
def category_stats():
@ -19,7 +19,7 @@ def category_scan(desired):
for code in range(sys.maxunicode + 1):
char = chr(code)
if category(char) == desired:
yield char
yield char
def main(args):
@ -30,7 +30,7 @@ def main(args):
count += 1
if count > 200:
break
print()
print()
print(count, 'characters shown')
else:
counts, firsts = category_stats()

View File

@ -1,4 +1,5 @@
import sys, locale
import locale
import sys
expressions = """
locale.getpreferredencoding()

View File

@ -10,9 +10,9 @@ zwg_sample = """
1F469 200D 2764 FE0F 200D 1F48B 200D 1F469 |kiss: woman, woman |E2.0
"""
markers = {'\u200D': 'ZWG', # ZERO WIDTH JOINER
'\uFE0F': 'V16', # VARIATION SELECTOR-16
}
markers = {'\u200D': 'ZWG', # ZERO WIDTH JOINER
'\uFE0F': 'V16', # VARIATION SELECTOR-16
}
for line in zwg_sample.strip().split('\n'):
code, descr, version = (s.strip() for s in line.split('|'))

View File

@ -13,7 +13,6 @@ from dataclasses import dataclass
@dataclass(frozen=True)
class Coordinate:
lat: float
long: float
@ -21,4 +20,4 @@ class Coordinate:
ns = 'N' if self.lat >= 0 else 'S'
we = 'E' if self.long >= 0 else 'W'
return f'{abs(self.lat):.1f}°{ns}, {abs(self.long):.1f}°{we}'
# end::COORDINATE[]
# end::COORDINATE[]

View File

@ -76,6 +76,5 @@ def demo():
print(n, name2hex(n, o))
if __name__ == '__main__':
demo()

View File

@ -10,7 +10,6 @@ def columnize(sequence: Sequence[T], num_columns: int = 0) -> List[Tuple[T, ...]
return [tuple(sequence[i::num_rows]) for i in range(num_rows)]
def demo() -> None:
nato = ('Alfa Bravo Charlie Delta Echo Foxtrot Golf Hotel India'
' Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo'
@ -22,7 +21,6 @@ def demo() -> None:
print(f'{word:15}', end='')
print()
print()
for length in range(2, 21, 6):
values = list(range(1, length + 1))

View File

@ -10,7 +10,6 @@ def columnize(sequence: Sequence[str], num_columns: int) -> List[Row]:
return [tuple(sequence[i::num_rows]) for i in range(num_rows)]
def demo() -> None:
nato = ('Alfa Bravo Charlie Delta Echo Foxtrot Golf Hotel India'
' Juliett Kilo Lima Mike November Oscar Papa Quebec Romeo'

View File

@ -1,4 +1,4 @@
from typing import List, Callable, TypeVar
from typing import List, Callable
import pytest # type: ignore

View File

@ -53,4 +53,4 @@ def test_double_nparray() -> None:
def test_double_none() -> None:
given = None
with pytest.raises(TypeError):
result = double(given)
double(given)

View File

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

View File

@ -3,12 +3,11 @@
"""passdrill: typing drills for practicing passphrases
"""
import sys
import os
import sys
from base64 import b64encode, b64decode
from getpass import getpass
from hashlib import scrypt
from base64 import b64encode, b64decode
from typing import Sequence, Tuple
HASH_FILENAME = 'passdrill.hash'
@ -20,7 +19,7 @@ def prompt() -> str:
confirmed = ''
while confirmed != 'y':
passphrase = input('Type passphrase to hash (it will be echoed): ')
if passphrase == '' or passphrase == 'q':
if passphrase in ('', 'q'):
print('ERROR: the passphrase cannot be empty or "q".')
continue
print(f'Passphrase to be hashed -> {passphrase}')
@ -45,7 +44,7 @@ def save_hash() -> None:
salted_hash = build_hash(prompt())
with open(HASH_FILENAME, 'wb') as fp:
fp.write(salted_hash)
print(f'Passphrase hash saved to', HASH_FILENAME)
print(f'Passphrase hash saved to {HASH_FILENAME}')
def load_hash() -> Tuple[bytes, bytes]:

View File

@ -20,7 +20,7 @@ class FromTo(NamedTuple):
to: str
def zip_replace(text: str, changes: Iterable[FromTo], count:int = -1) -> str:
def zip_replace(text: str, changes: Iterable[FromTo], count: int = -1) -> str:
for from_, to in changes:
text = text.replace(from_, to, count)
return text

View File

@ -1,6 +1,6 @@
values_map = [
(1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1),
( 'M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV','I')
(1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1),
( 'M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I')
]
def to_roman(arabic: int) -> str:

View File

@ -1,32 +0,0 @@
# tag::BOOKDICT[]
from typing import TypedDict, List
import json
class BookDict(TypedDict):
isbn: str
title: str
authors: List[str]
pagecount: int
# end::BOOKDICT[]
# tag::TOXML[]
AUTHOR_EL = '<AUTHOR>{}</AUTHOR>'
def to_xml(book: BookDict) -> str: # <1>
elements: List[str] = [] # <2>
for key, value in book.items():
if isinstance(value, list): # <3>
elements.extend(
AUTHOR_EL.format(n) for n in value) # <4>
else:
tag = key.upper()
elements.append(f'<{tag}>{value}</{tag}>')
xml = '\n\t'.join(elements)
return f'<BOOK>\n\t{xml}\n</BOOK>'
# end::TOXML[]
# tag::FROMJSON[]
def from_json(data: str) -> BookDict:
whatever: BookDict = json.loads(data) # <1>
return whatever # <2>
# end::FROMJSON[]

View File

@ -1,32 +0,0 @@
# tag::BOOKDICT[]
from typing import TypedDict, List
import json
class BookDict(TypedDict):
isbn: str
title: str
authors: List[str]
pagecount: int
# end::BOOKDICT[]
# tag::TOXML[]
AUTHOR_EL = '<AUTHOR>{}</AUTHOR>'
def to_xml(book: BookDict) -> str: # <1>
elements: List[str] = [] # <2>
for key, value in book.items():
if isinstance(value, list): # <3>
elements.extend(AUTHOR_EL.format(n)
for n in value)
else:
tag = key.upper()
elements.append(f'<{tag}>{value}</{tag}>')
xml = '\n\t'.join(elements)
return f'<BOOK>\n\t{xml}\n</BOOK>'
# end::TOXML[]
# tag::FROMJSON[]
def from_json(data: str) -> BookDict:
whatever = json.loads(data) # <1>
return whatever # <2>
# end::FROMJSON[]

View File

@ -1,20 +0,0 @@
from books import BookDict
from typing import TYPE_CHECKING
def demo() -> None: # <1>
book = BookDict( # <2>
isbn='0134757599',
title='Refactoring, 2e',
authors=['Martin Fowler', 'Kent Beck'],
pagecount=478
)
authors = book['authors'] # <3>
if TYPE_CHECKING: # <4>
reveal_type(authors) # <5>
authors = 'Bob' # <6>
book['weight'] = 4.2
del book['title']
if __name__ == '__main__':
demo()

View File

@ -1,23 +0,0 @@
from books import to_xml, from_json
from typing import TYPE_CHECKING
def demo() -> None:
NOT_BOOK_JSON = """
{"title": "Andromeda Strain",
"flavor": "pistachio",
"authors": true}
"""
not_book = from_json(NOT_BOOK_JSON) # <1>
if TYPE_CHECKING: # <2>
reveal_type(not_book)
reveal_type(not_book['authors'])
print(not_book) # <3>
print(not_book['flavor']) # <4>
xml = to_xml(not_book) # <5>
print(xml) # <6>
if __name__ == '__main__':
demo()

View File

@ -1,112 +0,0 @@
import json
from typing import cast
from books import BookDict, to_xml, from_json
XML_SAMPLE = """
<BOOK>
\t<ISBN>0134757599</ISBN>
\t<TITLE>Refactoring, 2e</TITLE>
\t<AUTHOR>Martin Fowler</AUTHOR>
\t<AUTHOR>Kent Beck</AUTHOR>
\t<PAGECOUNT>478</PAGECOUNT>
</BOOK>
""".strip()
# using plain dicts
def test_1() -> None:
xml = to_xml({
'isbn': '0134757599',
'title': 'Refactoring, 2e',
'authors': ['Martin Fowler', 'Kent Beck'],
'pagecount': 478,
})
assert xml == XML_SAMPLE
def test_2() -> None:
xml = to_xml(dict(
isbn='0134757599',
title='Refactoring, 2e',
authors=['Martin Fowler', 'Kent Beck'],
pagecount=478))
assert xml == XML_SAMPLE
def test_5() -> None:
book_data: BookDict = dict(
isbn='0134757599',
title='Refactoring, 2e',
authors=['Martin Fowler', 'Kent Beck'],
pagecount=478
)
xml = to_xml(book_data)
assert xml == XML_SAMPLE
def test_6() -> None:
book_data = dict(
isbn='0134757599',
title='Refactoring, 2e',
authors=['Martin Fowler', 'Kent Beck'],
pagecount=478
)
xml = to_xml(cast(BookDict, book_data)) # cast needed
assert xml == XML_SAMPLE
def test_4() -> None:
xml = to_xml(BookDict(
isbn='0134757599',
title='Refactoring, 2e',
authors=['Martin Fowler', 'Kent Beck'],
pagecount=478))
assert xml == XML_SAMPLE
def test_7() -> None:
book_data = BookDict(
isbn='0134757599',
title='Refactoring, 2e',
authors=['Martin Fowler', 'Kent Beck'],
pagecount=478
)
xml = to_xml(book_data)
assert xml == XML_SAMPLE
def test_8() -> None:
book_data: BookDict = {
'isbn': '0134757599',
'title': 'Refactoring, 2e',
'authors': ['Martin Fowler', 'Kent Beck'],
'pagecount': 478,
}
xml = to_xml(book_data)
assert xml == XML_SAMPLE
BOOK_JSON = """
{"isbn": "0134757599",
"title": "Refactoring, 2e",
"authors": ["Martin Fowler", "Kent Beck"],
"pagecount": 478}
"""
def test_load_book_0() -> None:
book_data: BookDict = json.loads(BOOK_JSON) # typed var
xml = to_xml(book_data)
assert xml == XML_SAMPLE
def test_load_book() -> None:
book_data = from_json(BOOK_JSON)
xml = to_xml(book_data)
assert xml == XML_SAMPLE
NOT_BOOK_JSON = """
{"isbn": 3.141592653589793
"title": [1, 2, 3],
"authors": ["Martin Fowler", "Kent Beck"],
"flavor": "strawberry"}
"""
def test_load_not_book() -> None:
book_data: BookDict = json.loads(BOOK_JSON) # typed var
xml = to_xml(book_data)
assert xml == XML_SAMPLE

View File

@ -1,20 +0,0 @@
from books import BookDict, to_xml
XML_SAMPLE = """
<BOOK>
\t<ISBN>0134757599</ISBN>
\t<TITLE>Refactoring, 2e</TITLE>
\t<AUTHOR>Martin Fowler</AUTHOR>
\t<AUTHOR>Kent Beck</AUTHOR>
\t<PAGECOUNT>478</PAGECOUNT>
</BOOK>
""".strip()
def test_3() -> None:
xml = to_xml(BookDict(dict([ # Expected keyword arguments, {...}, or dict(...) in TypedDict constructor
('isbn', '0134757599'),
('title', 'Refactoring, 2e'),
('authors', ['Martin Fowler', 'Kent Beck']),
('pagecount', 478),
])))
assert xml == XML_SAMPLE

View File

@ -41,4 +41,3 @@ if __name__ == '__main__':
for i in range(3):
snooze(.123)

View File

@ -25,7 +25,7 @@ def main(): # <8>
f2()
f3()
if __name__=='__main__':
if __name__ == '__main__':
main() # <9>
# end::REGISTRATION[]
# end::REGISTRATION[]

View File

@ -56,8 +56,7 @@ def test_large_order_promo_no_discount(customer_fidelity_0, cart_plain) -> None:
def test_large_order_promo_with_discount(customer_fidelity_0) -> None:
cart = [LineItem(str(item_code), 1, 1.0)
for item_code in range(10)]
cart = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]
order = Order(customer_fidelity_0, cart, LargeOrderPromo())
assert order.total() == 10.0
assert order.due() == 9.3

View File

@ -89,4 +89,3 @@ def best_promo(order):
# end::STRATEGY_BEST3[]

View File

@ -100,7 +100,7 @@ class Vector:
def __repr__(self):
components = reprlib.repr(self._components) # <3>
components = components[components.find('['):-1] # <4>
return 'Vector({})'.format(components)
return f'Vector({components})'
def __str__(self):
return str(tuple(self))
@ -113,7 +113,7 @@ class Vector:
return tuple(self) == tuple(other)
def __abs__(self):
return math.sqrt(sum(x * x for x in self)) # <6>
return math.hypot(*self) # <6>
def __bool__(self):
return bool(abs(self))

View File

@ -127,7 +127,7 @@ class Vector:
def __repr__(self):
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
return f'Vector({components})'
def __str__(self):
return str(tuple(self))
@ -140,7 +140,7 @@ class Vector:
return tuple(self) == tuple(other)
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
return math.hypot(*self)
def __bool__(self):
return bool(abs(self))

View File

@ -170,7 +170,7 @@ class Vector:
def __repr__(self):
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
return f'Vector({components})'
def __str__(self):
return str(tuple(self))
@ -183,7 +183,7 @@ class Vector:
return tuple(self) == tuple(other)
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
return math.hypot(*self)
def __bool__(self):
return bool(abs(self))
@ -207,8 +207,8 @@ class Vector:
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))
msg = f'{cls.__name__!r} object has no attribute {name!r}' # <5>
raise AttributeError(msg)
# end::VECTOR_V3_GETATTR[]
# tag::VECTOR_V3_SETATTR[]
@ -216,7 +216,7 @@ class Vector:
cls = type(self)
if len(name) == 1: # <1>
if name in cls.shortcut_names: # <2>
error = 'readonly attribute {attr_name!r}'
error = 'read-only attribute {attr_name!r}'
elif name.islower(): # <3>
error = "can't set attributes 'a' to 'z' in {cls_name!r}"
else:

View File

@ -166,7 +166,7 @@ class Vector:
def __repr__(self):
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
return f'Vector({components})'
def __str__(self):
return str(tuple(self))
@ -184,7 +184,7 @@ class Vector:
return functools.reduce(operator.xor, hashes, 0)
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
return math.hypot(*self)
def __bool__(self):
return bool(abs(self))
@ -207,8 +207,8 @@ class Vector:
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))
msg = f'{cls.__name__!r} object has no attribute {name!r}'
raise AttributeError(msg)
@classmethod
def frombytes(cls, octets):

View File

@ -209,7 +209,7 @@ class Vector:
def __repr__(self):
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
return f'Vector({components})'
def __str__(self):
return str(tuple(self))
@ -227,7 +227,7 @@ class Vector:
return functools.reduce(operator.xor, hashes, 0)
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
return math.hypot(*self)
def __bool__(self):
return bool(abs(self))
@ -250,11 +250,11 @@ class Vector:
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))
msg = f'{cls.__name__!r} object has no attribute {name!r}'
raise AttributeError(msg)
def angle(self, n): # <2>
r = math.sqrt(sum(x * x for x in self[n:]))
r = math.hypot(*self[n:])
a = math.atan2(r, self[n-1])
if (n == len(self) - 1) and (self[-1] < 0):
return math.pi * 2 - a

View File

@ -53,4 +53,4 @@ def test_double_nparray() -> None:
def test_double_none() -> None:
given = None
with pytest.raises(TypeError):
result = double(given)
double(given)

View File

@ -19,7 +19,6 @@ class Tombola(abc.ABC): # <1>
"""Return `True` if there's at least 1 item, `False` otherwise."""
return bool(self.inspect()) # <5>
def inspect(self):
"""Return a sorted tuple with the items currently inside."""
items = []

View File

@ -3,7 +3,7 @@ from typing import Any, Iterable, TYPE_CHECKING
from randompick import RandomPicker # <1>
class SimplePicker(): # <2>
class SimplePicker: # <2>
def __init__(self, items: Iterable) -> None:
self._items = list(items)
random.shuffle(self._items)

View File

@ -1,4 +1,4 @@
from typing import Protocol, runtime_checkable, Any, Iterable
from typing import Protocol, runtime_checkable
from randompick import RandomPicker
@runtime_checkable # <1>

View File

@ -1,9 +1,9 @@
import random
from typing import Any, Iterable, TYPE_CHECKING
from typing import Any, Iterable
from randompickload import LoadableRandomPicker
class SimplePicker():
class SimplePicker:
def __init__(self, items: Iterable) -> None:
self._items = list(items)
random.shuffle(self._items)
@ -11,7 +11,7 @@ class SimplePicker():
def pick(self) -> Any:
return self._items.pop()
class LoadablePicker(): # <1>
class LoadablePicker: # <1>
def __init__(self, items: Iterable) -> None:
self.load(items)

View File

@ -2,25 +2,25 @@ from typing import TypeVar, Generic
class Beverage:
"""Any beverage"""
"""Any beverage."""
class Juice(Beverage):
"""Any fruit juice"""
"""Any fruit juice."""
class OrangeJuice(Juice):
"""Delicious juice from Brazilian oranges"""
"""Delicious juice from Brazilian oranges."""
BeverageT = TypeVar('BeverageT', covariant=True)
T_co = TypeVar('T_co', covariant=True)
class BeverageDispenser(Generic[BeverageT]):
def __init__(self, beverage: BeverageT) -> None:
class BeverageDispenser(Generic[T_co]):
def __init__(self, beverage: T_co) -> None:
self.beverage = beverage
def dispense(self) -> BeverageT:
def dispense(self) -> T_co:
return self.beverage
@ -36,11 +36,11 @@ class Compostable(Biodegradable):
"""Compostable garbage."""
GarbageT = TypeVar('GarbageT', contravariant=True)
T_contra = TypeVar('T_contra', contravariant=True)
class TrashCan(Generic[GarbageT]):
def put(self, trash) -> None:
class TrashCan(Generic[T_contra]):
def put(self, trash: T_contra) -> None:
"""Store trash until dumped..."""
@ -48,35 +48,48 @@ class Cafeteria:
def __init__(
self,
dispenser: BeverageDispenser[Juice],
trash_can: TrashCan[Biodegradable]
trash_can: TrashCan[Biodegradable],
):
"""Initialize..."""
beverage_dispenser = BeverageDispenser(Beverage())
juice_dispenser = BeverageDispenser(Juice())
orange_juice_dispenser = BeverageDispenser(OrangeJuice())
################################################ exact types
trash_can: TrashCan[Garbage] = TrashCan()
juice_dispenser = BeverageDispenser(Juice())
bio_can: TrashCan[Biodegradable] = TrashCan()
compost_can: TrashCan[Compostable] = TrashCan()
arnold_hall = Cafeteria(juice_dispenser, bio_can)
######################## covariance on 1st argument
arnold_hall = Cafeteria(orange_juice_dispenser, trash_can)
################################################ covariant dispenser
orange_juice_dispenser = BeverageDispenser(OrangeJuice())
arnold_hall = Cafeteria(orange_juice_dispenser, bio_can)
################################################ non-covariant dispenser
beverage_dispenser = BeverageDispenser(Beverage())
## Argument 1 to "Cafeteria" has
## incompatible type "BeverageDispenser[Beverage]"
## expected "BeverageDispenser[Juice]"
# arnold_hall = Cafeteria(beverage_dispenser, trash_can)
# arnold_hall = Cafeteria(beverage_dispenser, bio_can)
######################## contravariance on 2nd argument
################################################ contravariant trash
trash_can: TrashCan[Garbage] = TrashCan()
arnold_hall = Cafeteria(juice_dispenser, trash_can)
################################################ non-contravariant trash
compost_can: TrashCan[Compostable] = TrashCan()
## Argument 2 to "Cafeteria" has
## incompatible type "TrashCan[Compostable]"
## expected "TrashCan[Biodegradable]"
# arnold_hall = Cafeteria(juice_dispenser, compost_can)
arnold_hall = Cafeteria(juice_dispenser, trash_can)

View File

@ -29,14 +29,6 @@ class Box(Generic[T]):
return self.contents
T_contra = TypeVar('T_contra', contravariant=True)
class InBox(Generic[T_contra]):
def put(self, item: T) -> None:
self.contents = item
T_co = TypeVar('T_co', covariant=True)
@ -46,3 +38,11 @@ class OutBox(Generic[T_co]):
def get(self) -> Any:
return self.contents
T_contra = TypeVar('T_contra', contravariant=True)
class InBox(Generic[T_contra]):
def put(self, item: T) -> None:
self.contents = item

View File

@ -53,34 +53,29 @@ Tests for __iadd__:
"""
# tag::ADDABLE_BINGO[]
import itertools # <1>
from tombola import Tombola
from bingo import BingoCage
class AddableBingoCage(BingoCage): # <2>
class AddableBingoCage(BingoCage): # <1>
def __add__(self, other):
if isinstance(other, Tombola): # <3>
return AddableBingoCage(self.inspect() + other.inspect())
if isinstance(other, Tombola): # <2>
return AddableBingoCage(self.inspect() + other.inspect())
else:
return NotImplemented
def __iadd__(self, other):
if isinstance(other, Tombola):
other_iterable = other.inspect() # <4>
other_iterable = other.inspect() # <3>
else:
try:
other_iterable = iter(other) # <5>
except TypeError: # <6>
other_iterable = iter(other) # <4>
except TypeError: # <5>
self_cls = type(self).__name__
msg = "right operand in += must be {!r} or an iterable"
raise TypeError(msg.format(self_cls))
self.load(other_iterable) # <7>
return self # <8>
self.load(other_iterable) # <6>
return self # <7>
# end::ADDABLE_BINGO[]

View File

@ -19,7 +19,6 @@ class Tombola(abc.ABC): # <1>
"""Return `True` if there's at least 1 item, `False` otherwise."""
return bool(self.inspect()) # <5>
def inspect(self):
"""Return a sorted tuple with the items currently inside."""
items = []
@ -31,5 +30,5 @@ class Tombola(abc.ABC): # <1>
self.load(items) # <7>
return tuple(sorted(items))
# END TOMBOLA_ABC

View File

@ -1,431 +0,0 @@
"""
A multi-dimensional ``Vector`` class, take 9: operator ``@``
WARNING: This example requires Python 3.5 or later.
A ``Vector`` is built from an iterable of numbers::
>>> Vector([3.1, 4.2])
Vector([3.1, 4.2])
>>> Vector((3, 4, 5))
Vector([3.0, 4.0, 5.0])
>>> Vector(range(10))
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
Tests with 2-dimensions (same results as ``vector2d_v1.py``)::
>>> v1 = Vector([3, 4])
>>> x, y = v1
>>> x, y
(3.0, 4.0)
>>> v1
Vector([3.0, 4.0])
>>> v1_clone = eval(repr(v1))
>>> v1 == v1_clone
True
>>> print(v1)
(3.0, 4.0)
>>> octets = bytes(v1)
>>> octets
b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
>>> abs(v1)
5.0
>>> bool(v1), bool(Vector([0, 0]))
(True, False)
Test of ``.frombytes()`` class method:
>>> v1_clone = Vector.frombytes(bytes(v1))
>>> v1_clone
Vector([3.0, 4.0])
>>> v1 == v1_clone
True
Tests with 3-dimensions::
>>> v1 = Vector([3, 4, 5])
>>> x, y, z = v1
>>> x, y, z
(3.0, 4.0, 5.0)
>>> v1
Vector([3.0, 4.0, 5.0])
>>> v1_clone = eval(repr(v1))
>>> v1 == v1_clone
True
>>> print(v1)
(3.0, 4.0, 5.0)
>>> abs(v1) # doctest:+ELLIPSIS
7.071067811...
>>> bool(v1), bool(Vector([0, 0, 0]))
(True, False)
Tests with many dimensions::
>>> v7 = Vector(range(7))
>>> v7
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
>>> abs(v7) # doctest:+ELLIPSIS
9.53939201...
Test of ``.__bytes__`` and ``.frombytes()`` methods::
>>> v1 = Vector([3, 4, 5])
>>> v1_clone = Vector.frombytes(bytes(v1))
>>> v1_clone
Vector([3.0, 4.0, 5.0])
>>> v1 == v1_clone
True
Tests of sequence behavior::
>>> v1 = Vector([3, 4, 5])
>>> len(v1)
3
>>> v1[0], v1[len(v1)-1], v1[-1]
(3.0, 5.0, 5.0)
Test of slicing::
>>> v7 = Vector(range(7))
>>> v7[-1]
6.0
>>> v7[1:4]
Vector([1.0, 2.0, 3.0])
>>> v7[-1:]
Vector([6.0])
>>> v7[1,2]
Traceback (most recent call last):
...
TypeError: Vector indices must be integers
Tests of dynamic attribute access::
>>> v7 = Vector(range(10))
>>> v7.x
0.0
>>> v7.y, v7.z, v7.t
(1.0, 2.0, 3.0)
Dynamic attribute lookup failures::
>>> v7.k
Traceback (most recent call last):
...
AttributeError: 'Vector' object has no attribute 'k'
>>> v3 = Vector(range(3))
>>> v3.t
Traceback (most recent call last):
...
AttributeError: 'Vector' object has no attribute 't'
>>> v3.spam
Traceback (most recent call last):
...
AttributeError: 'Vector' object has no attribute 'spam'
Tests of hashing::
>>> v1 = Vector([3, 4])
>>> v2 = Vector([3.1, 4.2])
>>> v3 = Vector([3, 4, 5])
>>> v6 = Vector(range(6))
>>> hash(v1), hash(v3), hash(v6)
(7, 2, 1)
Most hash codes of non-integers vary from a 32-bit to 64-bit Python build::
>>> import sys
>>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986)
True
Tests of ``format()`` with Cartesian coordinates in 2D::
>>> v1 = Vector([3, 4])
>>> format(v1)
'(3.0, 4.0)'
>>> format(v1, '.2f')
'(3.00, 4.00)'
>>> format(v1, '.3e')
'(3.000e+00, 4.000e+00)'
Tests of ``format()`` with Cartesian coordinates in 3D and 7D::
>>> v3 = Vector([3, 4, 5])
>>> format(v3)
'(3.0, 4.0, 5.0)'
>>> format(Vector(range(7)))
'(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)'
Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D::
>>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS
'<1.414213..., 0.785398...>'
>>> format(Vector([1, 1]), '.3eh')
'<1.414e+00, 7.854e-01>'
>>> format(Vector([1, 1]), '0.5fh')
'<1.41421, 0.78540>'
>>> format(Vector([1, 1, 1]), 'h') # doctest:+ELLIPSIS
'<1.73205..., 0.95531..., 0.78539...>'
>>> format(Vector([2, 2, 2]), '.3eh')
'<3.464e+00, 9.553e-01, 7.854e-01>'
>>> format(Vector([0, 0, 0]), '0.5fh')
'<0.00000, 0.00000, 0.00000>'
>>> format(Vector([-1, -1, -1, -1]), 'h') # doctest:+ELLIPSIS
'<2.0, 2.09439..., 2.18627..., 3.92699...>'
>>> format(Vector([2, 2, 2, 2]), '.3eh')
'<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>'
>>> format(Vector([0, 1, 0, 0]), '0.5fh')
'<1.00000, 1.57080, 0.00000, 0.00000>'
Basic tests of operator ``+``::
>>> v1 = Vector([3, 4, 5])
>>> v2 = Vector([6, 7, 8])
>>> v1 + v2
Vector([9.0, 11.0, 13.0])
>>> v1 + v2 == Vector([3+6, 4+7, 5+8])
True
>>> v3 = Vector([1, 2])
>>> v1 + v3 # short vectors are filled with 0.0 on addition
Vector([4.0, 6.0, 5.0])
Tests of ``+`` with mixed types::
>>> v1 + (10, 20, 30)
Vector([13.0, 24.0, 35.0])
>>> from vector2d_v3 import Vector2d
>>> v2d = Vector2d(1, 2)
>>> v1 + v2d
Vector([4.0, 6.0, 5.0])
Tests of ``+`` with mixed types, swapped operands::
>>> (10, 20, 30) + v1
Vector([13.0, 24.0, 35.0])
>>> from vector2d_v3 import Vector2d
>>> v2d = Vector2d(1, 2)
>>> v2d + v1
Vector([4.0, 6.0, 5.0])
Tests of ``+`` with an unsuitable operand:
>>> v1 + 1
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'Vector' and 'int'
>>> v1 + 'ABC'
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'Vector' and 'str'
Basic tests of operator ``*``::
>>> v1 = Vector([1, 2, 3])
>>> v1 * 10
Vector([10.0, 20.0, 30.0])
>>> 10 * v1
Vector([10.0, 20.0, 30.0])
Tests of ``*`` with unusual but valid operands::
>>> v1 * True
Vector([1.0, 2.0, 3.0])
>>> from fractions import Fraction
>>> v1 * Fraction(1, 3) # doctest:+ELLIPSIS
Vector([0.3333..., 0.6666..., 1.0])
Tests of ``*`` with unsuitable operands::
>>> v1 * (1, 2)
Traceback (most recent call last):
...
TypeError: can't multiply sequence by non-int of type 'Vector'
Tests of operator `==`::
>>> va = Vector(range(1, 4))
>>> vb = Vector([1.0, 2.0, 3.0])
>>> va == vb
True
>>> vc = Vector([1, 2])
>>> from vector2d_v3 import Vector2d
>>> v2d = Vector2d(1, 2)
>>> vc == v2d
True
>>> va == (1, 2, 3)
False
Tests of operator `!=`::
>>> va != vb
False
>>> vc != v2d
False
>>> va != (1, 2, 3)
True
Tests for operator `@` (Python >= 3.5), computing the dot product::
>>> va = Vector([1, 2, 3])
>>> vz = Vector([5, 6, 7])
>>> va @ vz == 38.0 # 1*5 + 2*6 + 3*7
True
>>> [10, 20, 30] @ vz
380.0
>>> va @ 3
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for @: 'Vector' and 'int'
"""
from array import array
import reprlib
import math
import functools
import operator
import itertools
import numbers
class Vector:
typecode = 'd'
def __init__(self, components):
self._components = array(self.typecode, components)
def __iter__(self):
return iter(self._components)
def __repr__(self):
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
def __str__(self):
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)]) +
bytes(self._components))
def __eq__(self, other):
if isinstance(other, Vector):
return (len(self) == len(other) and
all(a == b for a, b in zip(self, other)))
else:
return NotImplemented
def __hash__(self):
hashes = (hash(x) for x in self)
return functools.reduce(operator.xor, hashes, 0)
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
def __bool__(self):
return bool(abs(self))
def __len__(self):
return len(self._components)
def __getitem__(self, index):
cls = type(self)
if isinstance(index, slice):
return cls(self._components[index])
elif isinstance(index, int):
return self._components[index]
else:
msg = '{.__name__} indices must be integers'
raise TypeError(msg.format(cls))
shortcut_names = 'xyzt'
def __getattr__(self, name):
cls = type(self)
if len(name) == 1:
pos = cls.shortcut_names.find(name)
if 0 <= pos < len(self._components):
return self._components[pos]
msg = '{.__name__!r} object has no attribute {!r}'
raise AttributeError(msg.format(cls, name))
def angle(self, n):
r = math.sqrt(sum(x * x for x in self[n:]))
a = math.atan2(r, self[n-1])
if (n == len(self) - 1) and (self[-1] < 0):
return math.pi * 2 - a
else:
return a
def angles(self):
return (self.angle(n) for n in range(1, len(self)))
def __format__(self, fmt_spec=''):
if fmt_spec.endswith('h'): # hyperspherical coordinates
fmt_spec = fmt_spec[:-1]
coords = itertools.chain([abs(self)],
self.angles())
outer_fmt = '<{}>'
else:
coords = self
outer_fmt = '({})'
components = (format(c, fmt_spec) for c in coords)
return outer_fmt.format(', '.join(components))
@classmethod
def frombytes(cls, octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv)
def __add__(self, other):
try:
pairs = itertools.zip_longest(self, other, fillvalue=0.0)
return Vector(a + b for a, b in pairs)
except TypeError:
return NotImplemented
def __radd__(self, other):
return self + other
def __mul__(self, scalar):
if isinstance(scalar, numbers.Real):
return Vector(n * scalar for n in self)
else:
return NotImplemented
def __rmul__(self, scalar):
return self * scalar
def __matmul__(self, other):
try:
return sum(a * b for a, b in zip(self, other))
except TypeError:
return NotImplemented
def __rmatmul__(self, other):
return self @ other # this only works in Python 3.5

View File

@ -264,7 +264,7 @@ class Vector:
def __repr__(self):
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
return f'Vector({components})'
def __str__(self):
return str(tuple(self))
@ -283,7 +283,7 @@ class Vector:
# tag::VECTOR_V6_UNARY[]
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
return math.hypot(*self)
def __neg__(self):
return Vector(-x for x in self) # <1>
@ -313,11 +313,11 @@ class Vector:
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))
msg = f'{cls.__name__!r} object has no attribute {name!r}'
raise AttributeError(msg)
def angle(self, n):
r = math.sqrt(sum(x * x for x in self[n:]))
r = math.hypot(*self[n:])
a = math.atan2(r, self[n-1])
if (n == len(self) - 1) and (self[-1] < 0):
return math.pi * 2 - a

View File

@ -316,7 +316,7 @@ class Vector:
def __repr__(self):
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
return f'Vector({components})'
def __str__(self):
return str(tuple(self))
@ -334,7 +334,7 @@ class Vector:
return functools.reduce(operator.xor, hashes, 0)
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
return math.hypot(*self)
def __neg__(self):
return Vector(-x for x in self)
@ -363,11 +363,11 @@ class Vector:
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))
msg = f'{cls.__name__!r} object has no attribute {name!r}'
raise AttributeError(msg)
def angle(self, n):
r = math.sqrt(sum(x * x for x in self[n:]))
r = math.hypot(*self[n:])
a = math.atan2(r, self[n-1])
if (n == len(self) - 1) and (self[-1] < 0):
return math.pi * 2 - a

View File

@ -317,7 +317,7 @@ class Vector:
def __repr__(self):
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
return f'Vector({components})'
def __str__(self):
return str(tuple(self))
@ -340,7 +340,7 @@ class Vector:
return functools.reduce(operator.xor, hashes, 0)
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
return math.hypot(*self)
def __neg__(self):
return Vector(-x for x in self)
@ -369,11 +369,11 @@ class Vector:
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))
msg = f'{cls.__name__!r} object has no attribute {name!r}'
raise AttributeError(msg)
def angle(self, n):
r = math.sqrt(sum(x * x for x in self[n:]))
r = math.hypot(*self[n:])
a = math.atan2(r, self[n-1])
if (n == len(self) - 1) and (self[-1] < 0):
return math.pi * 2 - a

View File

@ -20,7 +20,7 @@ if __name__ == '__main__':
while abs(delta) <= epsilon:
delta = next(ap0) - next(ap1)
frac = next(ap_frac)
iteration +=1
iteration += 1
print('iteration: {}\tfraction: {}\tepsilon: {}\tdelta: {}'.
format(iteration, frac, epsilon, delta))

View File

@ -1,5 +1,5 @@
# tag::COLUMNIZE[]
from typing import Sequence, Tuple, Iterator
from typing import Sequence, Tuple, Iterator
def columnize(sequence: Sequence[str], num_columns: int = 0) -> Iterator[Tuple[str, ...]]:
if num_columns == 0:

View File

@ -44,8 +44,9 @@ def fibonacci():
if __name__ == '__main__':
for x, y in zip(Fibonacci(), fibonacci()):
assert x == y, '%s != %s' % (x, y)
assert x == y, f'{x} != {y}'
print(x)
if x > 10**10:
break
print('etc...')

View File

@ -6,7 +6,7 @@ Sentence: iterate over words using a generator function
import re
import reprlib
RE_WORD = re.compile('r\w+')
RE_WORD = re.compile(r'\w+')
class Sentence:
@ -15,7 +15,7 @@ class Sentence:
self.text = text # <1>
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
return f'Sentence({reprlib.repr(self.text)})'
def __iter__(self):
for match in RE_WORD.finditer(self.text): # <2>

View File

@ -15,7 +15,7 @@ class Sentence:
self.text = text
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
return f'Sentence({reprlib.repr(self.text)})'
def __iter__(self):
return (match.group() for match in RE_WORD.finditer(self.text))
@ -29,7 +29,7 @@ def main():
filename = sys.argv[1]
word_number = int(sys.argv[2])
except (IndexError, ValueError):
print('Usage: %s <file-name> <word-number>' % sys.argv[0])
print(f'Usage: {sys.argv[0]} <file-name> <word-number>')
sys.exit(2) # command line usage error
with open(filename, 'rt', encoding='utf-8') as text_file:
s = Sentence(text_file.read())
@ -38,7 +38,7 @@ def main():
print(word)
break
else:
warnings.warn('last word is #%d, "%s"' % (n, word))
warnings.warn(f'last word is #{n}, {word!r}')
if __name__ == '__main__':
main()

View File

@ -19,7 +19,7 @@ class Sentence:
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
return f'Sentence({reprlib.repr(self.text)})'
def __iter__(self): # <1>
return SentenceIterator(self.words) # <2>
@ -50,7 +50,7 @@ def main():
filename = sys.argv[1]
word_number = int(sys.argv[2])
except (IndexError, ValueError):
print('Usage: %s <file-name> <word-number>' % sys.argv[0])
print(f'Usage: {sys.argv[0]} <file-name> <word-number>')
sys.exit(2) # command line usage error
with open(filename, 'rt', encoding='utf-8') as text_file:
s = Sentence(text_file.read())
@ -59,7 +59,7 @@ def main():
print(word)
break
else:
warnings.warn('last word is #%d, "%s"' % (n, word))
warnings.warn(f'last word is #{n}, {word!r}')
if __name__ == '__main__':
main()

View File

@ -17,14 +17,14 @@ class Sentence:
self.text = text
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
return f'Sentence({reprlib.repr(self.text)})'
def __iter__(self):
word_iter = RE_WORD.finditer(self.text) # <1>
return SentenceIter(word_iter) # <2>
class SentenceIter():
class SentenceIter:
def __init__(self, word_iter):
self.word_iter = word_iter # <3>

View File

@ -1,11 +1,11 @@
from tree import tree
SPACES = ' ' * 4
HLINE = '\u2500' # ─ BOX DRAWINGS LIGHT HORIZONTAL
HLINE = '\u2500' # ─ BOX DRAWINGS LIGHT HORIZONTAL
HLINE2 = HLINE * 2
ELBOW = f'\u2514{HLINE2} ' # └ BOX DRAWINGS LIGHT UP AND RIGHT
TEE = f'\u251C{HLINE2} ' # ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT
PIPE = f'\u2502 ' # │ BOX DRAWINGS LIGHT VERTICAL
ELBOW = f'\u2514{HLINE2} ' # └ BOX DRAWINGS LIGHT UP AND RIGHT
TEE = f'\u251C{HLINE2} ' # ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT
PIPE = '\u2502 ' # │ BOX DRAWINGS LIGHT VERTICAL
def render_lines(tree_iter):

View File

@ -1,5 +1,3 @@
import pytest
from pretty_tree import tree, render_lines
def test_1_level():
@ -7,7 +5,7 @@ def test_1_level():
expected = [
'BrokenPipeError',
]
assert expected == result
assert expected == result
def test_2_levels_1_leaf():
@ -16,7 +14,7 @@ def test_2_levels_1_leaf():
'IndentationError',
'└── TabError',
]
assert expected == result
assert expected == result
def test_3_levels_1_leaf():
@ -29,7 +27,7 @@ def test_3_levels_1_leaf():
'└── Y',
' └── Z',
]
assert expected == result
assert expected == result
def test_4_levels_1_leaf():
@ -98,4 +96,5 @@ def test_4_levels_4_leaves():
]
result = list(render_lines(tree(A)))
assert expected == result
assert expected == result

View File

@ -5,7 +5,7 @@ def test_1_level():
class One: pass
expected = [('One', 0, True)]
result = list(tree(One))
assert expected == result
assert expected == result
def test_2_levels_2_leaves():
@ -18,7 +18,7 @@ def test_2_levels_2_leaves():
('Leaf2', 1, True),
]
result = list(tree(Branch))
assert expected == result
assert expected == result
def test_3_levels_1_leaf():
@ -31,7 +31,7 @@ def test_3_levels_1_leaf():
('Z', 2, True),
]
result = list(tree(X))
assert expected == result
assert expected == result
def test_4_levels_1_leaf():
@ -47,7 +47,7 @@ def test_4_levels_1_leaf():
]
result = list(tree(Level0))
assert expected == result
assert expected == result
def test_4_levels_3_leaves():
@ -69,7 +69,7 @@ def test_4_levels_3_leaves():
]
result = list(tree(A))
assert expected == result
assert expected == result
def test_many_levels_1_leaf():
@ -87,4 +87,4 @@ def test_many_levels_1_leaf():
assert len(result) == level_count
assert result[0] == ('Root', 0, True)
assert result[-1] == ('Sub99', 99, True)
assert expected == result
assert expected == result

View File

@ -2,7 +2,7 @@ def tree(cls, level=0, last_in_level=True):
yield cls.__name__, level, last_in_level
subclasses = cls.__subclasses__()
if subclasses:
last = subclasses[-1]
last = subclasses[-1]
for sub_cls in subclasses:
yield from tree(sub_cls, level+1, sub_cls is last)

View File

@ -5,4 +5,4 @@ def test_1_level():
class One: pass
expected = ['One']
result = list(tree(One))
assert expected == result
assert expected == result

View File

@ -8,4 +8,4 @@ def display(cls):
if __name__ == '__main__':
display(BaseException)
display(BaseException)

View File

@ -5,7 +5,7 @@ def test_1_level():
class One: pass
expected = [('One', 0)]
result = list(tree(One))
assert expected == result
assert expected == result
def test_2_levels_2_leaves():
@ -18,4 +18,4 @@ def test_2_levels_2_leaves():
('Leaf2', 1),
]
result = list(tree(Branch))
assert expected == result
assert expected == result

View File

@ -5,7 +5,7 @@ def test_1_level():
class One: pass
expected = [('One', 0)]
result = list(tree(One))
assert expected == result
assert expected == result
def test_2_levels_2_leaves():
@ -18,4 +18,4 @@ def test_2_levels_2_leaves():
('Leaf2', 1),
]
result = list(tree(Branch))
assert expected == result
assert expected == result

View File

@ -5,7 +5,7 @@ def test_1_level():
class One: pass
expected = [('One', 0)]
result = list(tree(One))
assert expected == result
assert expected == result
def test_2_levels_2_leaves():
@ -18,7 +18,7 @@ def test_2_levels_2_leaves():
('Leaf2', 1),
]
result = list(tree(Branch))
assert expected == result
assert expected == result
def test_3_levels_1_leaf():
@ -31,4 +31,4 @@ def test_3_levels_1_leaf():
('Z', 2),
]
result = list(tree(X))
assert expected == result
assert expected == result

View File

@ -5,7 +5,7 @@ def test_1_level():
class One: pass
expected = [('One', 0)]
result = list(tree(One))
assert expected == result
assert expected == result
def test_2_levels_2_leaves():
@ -18,7 +18,7 @@ def test_2_levels_2_leaves():
('Leaf2', 1),
]
result = list(tree(Branch))
assert expected == result
assert expected == result
def test_3_levels_1_leaf():
@ -31,7 +31,7 @@ def test_3_levels_1_leaf():
('Z', 2),
]
result = list(tree(X))
assert expected == result
assert expected == result
def test_4_levels_1_leaf():
@ -47,7 +47,7 @@ def test_4_levels_1_leaf():
]
result = list(tree(Level0))
assert expected == result
assert expected == result
def test_4_levels_3_leaves():
@ -69,4 +69,5 @@ def test_4_levels_3_leaves():
]
result = list(tree(A))
assert expected == result
assert expected == result

View File

@ -5,7 +5,7 @@ def test_1_level():
class One: pass
expected = [('One', 0)]
result = list(tree(One))
assert expected == result
assert expected == result
def test_2_levels_2_leaves():
@ -18,7 +18,7 @@ def test_2_levels_2_leaves():
('Leaf2', 1),
]
result = list(tree(Branch))
assert expected == result
assert expected == result
def test_3_levels_1_leaf():
@ -31,7 +31,7 @@ def test_3_levels_1_leaf():
('Z', 2),
]
result = list(tree(X))
assert expected == result
assert expected == result
def test_4_levels_1_leaf():
@ -47,7 +47,7 @@ def test_4_levels_1_leaf():
]
result = list(tree(Level0))
assert expected == result
assert expected == result
def test_4_levels_3_leaves():
@ -69,7 +69,7 @@ def test_4_levels_3_leaves():
]
result = list(tree(A))
assert expected == result
assert expected == result
def test_many_levels_1_leaf():
@ -87,4 +87,5 @@ def test_many_levels_1_leaf():
assert len(result) == level_count
assert result[0] == ('Root', 0)
assert result[-1] == ('Sub99', 99)
assert expected == result
assert expected == result

View File

@ -5,7 +5,7 @@ def test_1_level():
class One: pass
expected = [('One', 0)]
result = list(tree(One))
assert expected == result
assert expected == result
def test_2_levels_2_leaves():
@ -18,7 +18,7 @@ def test_2_levels_2_leaves():
('Leaf2', 1),
]
result = list(tree(Branch))
assert expected == result
assert expected == result
def test_3_levels_1_leaf():
@ -31,7 +31,7 @@ def test_3_levels_1_leaf():
('Z', 2),
]
result = list(tree(X))
assert expected == result
assert expected == result
def test_4_levels_1_leaf():
@ -47,7 +47,7 @@ def test_4_levels_1_leaf():
]
result = list(tree(Level0))
assert expected == result
assert expected == result
def test_4_levels_3_leaves():
@ -69,7 +69,7 @@ def test_4_levels_3_leaves():
]
result = list(tree(A))
assert expected == result
assert expected == result
def test_many_levels_1_leaf():
@ -87,4 +87,5 @@ def test_many_levels_1_leaf():
assert len(result) == level_count
assert result[0] == ('Root', 0)
assert result[-1] == ('Sub99', 99)
assert expected == result
assert expected == result

View File

@ -4,8 +4,8 @@ from functools import wraps
def coroutine(func):
"""Decorator: primes `func` by advancing to first `yield`"""
@wraps(func)
def primer(*args,**kwargs): # <1>
gen = func(*args,**kwargs) # <2>
def primer(*args, **kwargs): # <1>
gen = func(*args, **kwargs) # <2>
next(gen) # <3>
return gen # <4>
return primer

View File

@ -49,11 +49,11 @@ See longer sample run at the end of this module.
"""
import random
import collections
import queue
import argparse
import time
import collections
import random
import queue
DEFAULT_NUMBER_OF_TAXIS = 3
DEFAULT_END_TIME = 180
@ -126,8 +126,8 @@ def compute_duration(previous_action):
elif previous_action == 'going home':
interval = 1
else:
raise ValueError('Unknown previous_action: %s' % previous_action)
return int(random.expovariate(1/interval)) + 1
raise ValueError(f'Unknown previous_action: {previous_action}')
return int(random.expovariate(1 / interval)) + 1
def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS,
@ -136,7 +136,7 @@ def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS,
if seed is not None:
random.seed(seed) # get reproducible results
taxis = {i: taxi_process(i, (i+1)*2, i*DEPARTURE_INTERVAL)
taxis = {i: taxi_process(i, (i + 1) * 2, i * DEPARTURE_INTERVAL)
for i in range(num_taxis)}
sim = Simulator(taxis)
sim.run(end_time)

View File

@ -27,11 +27,10 @@ See explanation and longer sample run at the end of this module.
"""
import sys
import random
import argparse
import collections
import queue
import argparse
import random
DEFAULT_NUMBER_OF_TAXIS = 3
DEFAULT_END_TIME = 80
@ -44,7 +43,7 @@ Event = collections.namedtuple('Event', 'time proc action')
def compute_delay(interval):
"""Compute action delay using exponential distribution"""
return int(random.expovariate(1/interval)) + 1
return int(random.expovariate(1 / interval)) + 1
# BEGIN TAXI_PROCESS
def taxi_process(ident, trips, start_time=0): # <1>
@ -68,7 +67,6 @@ class Simulator:
self.events = queue.PriorityQueue()
self.procs = dict(procs_map)
def run(self, end_time): # <1>
"""Schedule and display events until time is up"""
# schedule the first event for each cab
@ -108,7 +106,7 @@ def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS,
if seed is not None:
random.seed(seed) # get reproducible results
taxis = {i: taxi_process(i, (i+1)*2, i*DEPARTURE_INTERVAL)
taxis = {i: taxi_process(i, (i + 1) * 2, i * DEPARTURE_INTERVAL)
for i in range(num_taxis)}
sim = Simulator(taxis)
sim.run(end_time)

View File

@ -5,7 +5,7 @@ certifi==2020.12.5
chardet==4.0.0
idna==2.10
requests==2.25.1
urllib3==1.26.4
urllib3==1.26.3
tqdm==4.56.2
multidict==5.1.0
yarl==1.6.3

View File

@ -1,15 +1,16 @@
from curio import TaskGroup
import curio.socket as socket
from collections.abc import Iterable, AsyncIterator
from typing import NamedTuple
from curio import TaskGroup
import curio.socket as socket
class Result(NamedTuple):
domain: str
found: bool
async def probe(domain: str) -> Result:
async def probe(domain: str) -> Result:
try:
await socket.getaddrinfo(domain, None)
except socket.gaierror:

View File

@ -16,8 +16,8 @@ schedule_v2.py: property to get venue linked to an event
"""
# tag::SCHEDULE2_RECORD[]
import json
import inspect # <1>
import json
JSON_PATH = 'data/osconfeed.json'

View File

@ -20,8 +20,8 @@ schedule_v3.py: property to get list of event speakers
# end::SCHEDULE3_DEMO[]
"""
import json
import inspect
import json
JSON_PATH = 'data/osconfeed.json'

View File

@ -18,8 +18,8 @@ schedule_v4.py: homegrown cached property for speakers
# end::SCHEDULE4_DEMO[]
"""
import json
import inspect
import json
JSON_PATH = 'data/osconfeed.json'

View File

@ -54,7 +54,7 @@ class Quantity:
msg = f'{self.storage_name} must be > 0'
raise ValueError(msg)
# no __get__ needed
# no __get__ needed # <4>
class LineItem:
weight = Quantity() # <5>

View File

@ -32,5 +32,5 @@ class NonBlank(Validated):
value = value.strip()
if len(value) == 0:
raise ValueError(f'{name} cannot be blank')
return value # <8>
return value # <2>
# end::MODEL_V5_VALIDATED_SUB[]

View File

@ -5,20 +5,20 @@ Overriding descriptor (a.k.a. data descriptor or enforced descriptor):
>>> obj = Managed() # <1>
>>> obj.over # <2>
-> Overriding.__get__(<Overriding object>, <Managed object>,
-> Overriding.__get__(<Overriding object>, <Managed object>,
<class Managed>)
>>> Managed.over # <3>
-> Overriding.__get__(<Overriding object>, None, <class Managed>)
>>> obj.over = 7 # <4>
-> Overriding.__set__(<Overriding object>, <Managed object>, 7)
>>> obj.over # <5>
-> Overriding.__get__(<Overriding object>, <Managed object>,
-> Overriding.__get__(<Overriding object>, <Managed object>,
<class Managed>)
>>> obj.__dict__['over'] = 8 # <6>
>>> vars(obj) # <7>
{'over': 8}
>>> obj.over # <8>
-> Overriding.__get__(<Overriding object>, <Managed object>,
-> Overriding.__get__(<Overriding object>, <Managed object>,
<class Managed>)
# end::DESCR_KINDS_DEMO1[]
@ -50,7 +50,7 @@ Non-overriding descriptor (a.k.a. non-data descriptor or shadowable descriptor):
>>> obj = Managed()
>>> obj.non_over # <1>
-> NonOverriding.__get__(<NonOverriding object>, <Managed object>,
-> NonOverriding.__get__(<NonOverriding object>, <Managed object>,
<class Managed>)
>>> obj.non_over = 7 # <2>
>>> obj.non_over # <3>
@ -59,7 +59,7 @@ Non-overriding descriptor (a.k.a. non-data descriptor or shadowable descriptor):
-> NonOverriding.__get__(<NonOverriding object>, None, <class Managed>)
>>> del obj.non_over # <5>
>>> obj.non_over # <6>
-> NonOverriding.__get__(<NonOverriding object>, <Managed object>,
-> NonOverriding.__get__(<NonOverriding object>, <Managed object>,
<class Managed>)
# end::DESCR_KINDS_DEMO3[]

View File

@ -0,0 +1,22 @@
# tag::WilyDict[]
class WilyDict(dict):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__next_value = 0
def __missing__(self, key):
if key.startswith('__') and key.endswith('__'):
raise KeyError(key)
self[key] = value = self.__next_value
self.__next_value += 1
return value
# end::WilyDict[]
# tag::AUTOCONST[]
class AutoConstMeta(type):
def __prepare__(name, bases, **kwargs):
return WilyDict()
class AutoConst(metaclass=AutoConstMeta):
pass
# end::AUTOCONST[]

View File

@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""
Testing ``WilyDict``::
>>> from autoconst import WilyDict
>>> wd = WilyDict()
>>> len(wd)
0
>>> wd['first']
0
>>> wd
{'first': 0}
>>> wd['second']
1
>>> wd['third']
2
>>> len(wd)
3
>>> wd
{'first': 0, 'second': 1, 'third': 2}
>>> wd['__magic__']
Traceback (most recent call last):
...
KeyError: '__magic__'
Testing ``AutoConst``::
>>> from autoconst import AutoConst
# tag::AUTOCONST[]
>>> class Flavor(AutoConst):
... banana
... coconut
... vanilla
...
>>> Flavor.vanilla
2
>>> Flavor.banana, Flavor.coconut
(0, 1)
# end::AUTOCONST[]
"""
from autoconst import AutoConst
class Flavor(AutoConst):
banana
coconut
vanilla
print('Flavor.vanilla ==', Flavor.vanilla)

View File

@ -0,0 +1,34 @@
# Legacy Class Descriptor and Metaclass Examples
Examples from _Fluent Python, First Edition_—Chapter 21, _Class Metaprogramming_,
that are mentioned in _Fluent Python, Second Edition_—Chapter 25, _Class Metaprogramming_.
These examples were developed with Python 3.4.
They run correctly in Python 3.9, but now it is easier to fullfill the same requirements
without resorting to class decorators or metaclasses.
I have preserved them here as examples of class metaprogramming techniques
that you may find in legacy code, and that can be refactored to simpler code
using a base class with `__init_subclass__` and decorators implementing `__set_name__`.
## Suggested Exercise
If you'd like to practice the concepts presented in chapters 24 and 25 of
_Fluent Python, Second Edition_,
you may to refactor the most advanced example, `model_v8.py` with these changes:
1. Simplify the `AutoStorage` descriptor by implementing `__set_name__`.
This will allow you to simplify the `EntityMeta` metaclass as well.
2. Rewrite the `Entity` class to use `__init_subclass__` instead of the `EntityMeta` metaclass—which you can then delete.
Nothing should change in the `bulkfood_v8.py` code, and its doctests should still pass.
To run the doctests while refactoring, it's often convenient to pass the `-f` option,
to exit the test runner on the first failing test.
```
$ python3 -m doctest -f bulkfood_v8.py
```
Enjoy!

View File

@ -0,0 +1,84 @@
"""
A line item for a bulk food order has description, weight and price fields::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> raisins.weight, raisins.description, raisins.price
(10, 'Golden raisins', 6.95)
A ``subtotal`` method gives the total price for that line item::
>>> raisins.subtotal()
69.5
The weight of a ``LineItem`` must be greater than 0::
>>> raisins.weight = -20
Traceback (most recent call last):
...
ValueError: value must be > 0
No change was made::
>>> raisins.weight
10
The value of the attributes managed by the descriptors are stored in
alternate attributes, created by the descriptors in each ``LineItem``
instance::
# tag::LINEITEM_V6_DEMO[]
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> dir(raisins)[:3]
['_NonBlank#description', '_Quantity#price', '_Quantity#weight']
>>> LineItem.description.storage_name
'_NonBlank#description'
>>> raisins.description
'Golden raisins'
>>> getattr(raisins, '_NonBlank#description')
'Golden raisins'
# end::LINEITEM_V6_DEMO[]
If the descriptor is accessed in the class, the descriptor object is
returned:
>>> LineItem.weight # doctest: +ELLIPSIS
<model_v6.Quantity object at 0x...>
>>> LineItem.weight.storage_name
'_Quantity#weight'
The `NonBlank` descriptor prevents empty or blank strings to be used
for the description:
>>> br_nuts = LineItem('Brazil Nuts', 10, 34.95)
>>> br_nuts.description = ' '
Traceback (most recent call last):
...
ValueError: value cannot be empty or blank
>>> void = LineItem('', 1, 1)
Traceback (most recent call last):
...
ValueError: value cannot be empty or blank
"""
# tag::LINEITEM_V6[]
import model_v6 as model
@model.entity # <1>
class LineItem:
description = model.NonBlank()
weight = model.Quantity()
price = model.Quantity()
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
# end::LINEITEM_V6[]

View File

@ -0,0 +1,79 @@
"""
A line item for a bulk food order has description, weight and price fields::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> raisins.weight, raisins.description, raisins.price
(10, 'Golden raisins', 6.95)
A ``subtotal`` method gives the total price for that line item::
>>> raisins.subtotal()
69.5
The weight of a ``LineItem`` must be greater than 0::
>>> raisins.weight = -20
Traceback (most recent call last):
...
ValueError: value must be > 0
No change was made::
>>> raisins.weight
10
The value of the attributes managed by the descriptors are stored in
alternate attributes, created by the descriptors in each ``LineItem``
instance::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> dir(raisins)[:3]
['_NonBlank#description', '_Quantity#price', '_Quantity#weight']
>>> LineItem.description.storage_name
'_NonBlank#description'
>>> raisins.description
'Golden raisins'
>>> getattr(raisins, '_NonBlank#description')
'Golden raisins'
If the descriptor is accessed in the class, the descriptor object is
returned:
>>> LineItem.weight # doctest: +ELLIPSIS
<model_v7.Quantity object at 0x...>
>>> LineItem.weight.storage_name
'_Quantity#weight'
The `NonBlank` descriptor prevents empty or blank strings to be used
for the description:
>>> br_nuts = LineItem('Brazil Nuts', 10, 34.95)
>>> br_nuts.description = ' '
Traceback (most recent call last):
...
ValueError: value cannot be empty or blank
>>> void = LineItem('', 1, 1)
Traceback (most recent call last):
...
ValueError: value cannot be empty or blank
"""
# tag::LINEITEM_V7[]
import model_v7 as model
class LineItem(model.Entity): # <1>
description = model.NonBlank()
weight = model.Quantity()
price = model.Quantity()
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
# end::LINEITEM_V7[]

View File

@ -0,0 +1,86 @@
"""
A line item for a bulk food order has description, weight and price fields::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> raisins.weight, raisins.description, raisins.price
(10, 'Golden raisins', 6.95)
A ``subtotal`` method gives the total price for that line item::
>>> raisins.subtotal()
69.5
The weight of a ``LineItem`` must be greater than 0::
>>> raisins.weight = -20
Traceback (most recent call last):
...
ValueError: value must be > 0
No change was made::
>>> raisins.weight
10
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> dir(raisins)[:3]
['_NonBlank#description', '_Quantity#price', '_Quantity#weight']
>>> LineItem.description.storage_name
'_NonBlank#description'
>>> raisins.description
'Golden raisins'
>>> getattr(raisins, '_NonBlank#description')
'Golden raisins'
If the descriptor is accessed in the class, the descriptor object is
returned:
>>> LineItem.weight # doctest: +ELLIPSIS
<model_v8.Quantity object at 0x...>
>>> LineItem.weight.storage_name
'_Quantity#weight'
The `NonBlank` descriptor prevents empty or blank strings to be used
for the description:
>>> br_nuts = LineItem('Brazil Nuts', 10, 34.95)
>>> br_nuts.description = ' '
Traceback (most recent call last):
...
ValueError: value cannot be empty or blank
>>> void = LineItem('', 1, 1)
Traceback (most recent call last):
...
ValueError: value cannot be empty or blank
Fields can be retrieved in the order they were declared:
# tag::LINEITEM_V8_DEMO[]
>>> for name in LineItem.field_names():
... print(name)
...
description
weight
price
# end::LINEITEM_V8_DEMO[]
"""
import model_v8 as model
class LineItem(model.Entity):
description = model.NonBlank()
weight = model.Quantity()
price = model.Quantity()
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price

View File

@ -0,0 +1,60 @@
import abc
class AutoStorage:
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}#{}'.format(prefix, index)
cls.__counter += 1
def __get__(self, instance, owner):
if instance is None:
return self
else:
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
setattr(instance, self.storage_name, value)
class Validated(abc.ABC, AutoStorage):
def __set__(self, instance, value):
value = self.validate(instance, value)
super().__set__(instance, value)
@abc.abstractmethod
def validate(self, instance, value):
"""return validated value or raise ValueError"""
class Quantity(Validated):
"""a number greater than zero"""
def validate(self, instance, value):
if value <= 0:
raise ValueError('value must be > 0')
return value
class NonBlank(Validated):
"""a string with at least one non-space character"""
def validate(self, instance, value):
value = value.strip()
if len(value) == 0:
raise ValueError('value cannot be empty or blank')
return value
# tag::MODEL_V6[]
def entity(cls): # <1>
for key, attr in cls.__dict__.items(): # <2>
if isinstance(attr, Validated): # <3>
type_name = type(attr).__name__
attr.storage_name = '_{}#{}'.format(type_name, key) # <4>
return cls # <5>
# end::MODEL_V6[]

View File

@ -0,0 +1,66 @@
import abc
class AutoStorage:
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}#{}'.format(prefix, index)
cls.__counter += 1
def __get__(self, instance, owner):
if instance is None:
return self
else:
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
setattr(instance, self.storage_name, value)
class Validated(abc.ABC, AutoStorage):
def __set__(self, instance, value):
value = self.validate(instance, value)
super().__set__(instance, value)
@abc.abstractmethod
def validate(self, instance, value):
"""return validated value or raise ValueError"""
class Quantity(Validated):
"""a number greater than zero"""
def validate(self, instance, value):
if value <= 0:
raise ValueError('value must be > 0')
return value
class NonBlank(Validated):
"""a string with at least one non-space character"""
def validate(self, instance, value):
value = value.strip()
if len(value) == 0:
raise ValueError('value cannot be empty or blank')
return value
# tag::MODEL_V7[]
class EntityMeta(type):
"""Metaclass for business entities with validated fields"""
def __init__(cls, name, bases, attr_dict):
super().__init__(name, bases, attr_dict) # <1>
for key, attr in attr_dict.items(): # <2>
if isinstance(attr, Validated):
type_name = type(attr).__name__
attr.storage_name = '_{}#{}'.format(type_name, key)
class Entity(metaclass=EntityMeta): # <3>
"""Business entity with validated fields"""
# end::MODEL_V7[]

View File

@ -0,0 +1,80 @@
import abc
import collections
class AutoStorage:
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}#{}'.format(prefix, index)
cls.__counter += 1
def __get__(self, instance, owner):
if instance is None:
return self
else:
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
setattr(instance, self.storage_name, value)
class Validated(abc.ABC, AutoStorage):
def __set__(self, instance, value):
value = self.validate(instance, value)
super().__set__(instance, value)
@abc.abstractmethod
def validate(self, instance, value):
"""return validated value or raise ValueError"""
class Quantity(Validated):
"""a number greater than zero"""
def validate(self, instance, value):
if value <= 0:
raise ValueError('value must be > 0')
return value
class NonBlank(Validated):
"""a string with at least one non-space character"""
def validate(self, instance, value):
value = value.strip()
if len(value) == 0:
raise ValueError('value cannot be empty or blank')
return value
# tag::MODEL_V8[]
class EntityMeta(type):
"""Metaclass for business entities with validated fields"""
@classmethod
def __prepare__(cls, name, bases):
return collections.OrderedDict() # <1>
def __init__(cls, name, bases, attr_dict):
super().__init__(name, bases, attr_dict)
cls._field_names = [] # <2>
for key, attr in attr_dict.items(): # <3>
if isinstance(attr, Validated):
type_name = type(attr).__name__
attr.storage_name = '_{}#{}'.format(type_name, key)
cls._field_names.append(key) # <4>
class Entity(metaclass=EntityMeta):
"""Business entity with validated fields"""
@classmethod
def field_names(cls): # <5>
for name in cls._field_names:
yield name
# end::MODEL_V8[]

View File

@ -8,13 +8,13 @@ used to create an instance, and provides a nice ``__repr__``::
... class Movie:
... title: str
... year: int
... megabucks: float
... box_office: float
...
>>> movie = Movie(title='The Godfather', year=1972, megabucks=137) # <3>
>>> movie = Movie(title='The Godfather', year=1972, box_office=137)
>>> movie.title
'The Godfather'
>>> movie # <4>
Movie(title='The Godfather', year=1972, megabucks=137.0)
>>> movie
Movie(title='The Godfather', year=1972, box_office=137.0)
# end::MOVIE_DEFINITION[]
@ -23,14 +23,14 @@ including during instantiation::
# tag::MOVIE_TYPE_VALIDATION[]
>>> movie.year = 'MCMLXXII' # <1>
>>> blockbuster = Movie(title='Avatar', year=2009, box_office='billions')
Traceback (most recent call last):
...
TypeError: 'billions' is not compatible with box_office:float
>>> movie.year = 'MCMLXXII'
Traceback (most recent call last):
...
TypeError: 'MCMLXXII' is not compatible with year:int
>>> blockbuster = Movie(title='Avatar', year=2009, megabucks='billions') # <2>
Traceback (most recent call last):
...
TypeError: 'billions' is not compatible with megabucks:float
# end::MOVIE_TYPE_VALIDATION[]
@ -40,13 +40,13 @@ default values::
# tag::MOVIE_DEFAULTS[]
>>> Movie(title='Life of Brian')
Movie(title='Life of Brian', year=0, megabucks=0.0)
Movie(title='Life of Brian', year=0, box_office=0.0)
# end::MOVIE_DEFAULTS[]
Providing extra arguments to the constructor is not allowed::
>>> blockbuster = Movie(title='Avatar', year=2009, megabucks=2000,
>>> blockbuster = Movie(title='Avatar', year=2009, box_office=2000,
... director='James Cameron')
Traceback (most recent call last):
...
@ -62,109 +62,90 @@ Creating new attributes at runtime is restricted as well::
The `_as_dict` instance creates a `dict` from the attributes of a `Movie` object::
>>> movie._asdict()
{'title': 'The Godfather', 'year': 1972, 'megabucks': 137.0}
{'title': 'The Godfather', 'year': 1972, 'box_office': 137.0}
"""
from collections.abc import Callable # <1>
from typing import Any, NoReturn, get_type_hints
MISSING = object() # <2>
class Field:
def __init__(self, name: str, constructor: Callable) -> None: # <3>
def __init__(self, name: str, constructor: Callable) -> None: # <2>
if not callable(constructor) or constructor is type(None):
raise TypeError(f'{name!r} type hint must be callable')
self.name = name
self.constructor = constructor
def __set__(self, instance: Any, value: Any) -> None: # <4>
if value is MISSING: # <5>
def __set__(self, instance: Any, value: Any) -> None: # <3>
if value is ...: # <4>
value = self.constructor()
else:
try:
value = self.constructor(value) # <6>
value = self.constructor(value) # <5>
except (TypeError, ValueError) as e:
type_name = self.constructor.__name__
msg = (
f'{value!r} is not compatible with {self.name}:{type_name}'
)
raise TypeError(msg) from e
instance.__dict__[self.name] = value # <7>
instance.__dict__[self.name] = value # <6>
# tag::CHECKED_DECORATOR_TOP[]
_methods_to_inject: list[Callable] = []
_classmethods_to_inject: list[Callable] = []
# tag::CHECKED_DECORATOR[]
def checked(cls: type) -> type: # <1>
for name, constructor in _fields(cls).items(): # <2>
setattr(cls, name, Field(name, constructor)) # <3>
def checked(cls: type) -> type: # <2>
for func in _methods_to_inject:
name = func.__name__
setattr(cls, name, func) # <5>
cls._fields = classmethod(_fields) #type: ignore # <4>
for func in _classmethods_to_inject:
name = func.__name__
setattr(cls, name, classmethod(func)) # <5>
instance_methods = ( # <5>
__init__,
__repr__,
__setattr__,
_asdict,
__flag_unknown_attrs,
)
for method in instance_methods: # <6>
setattr(cls, method.__name__, method)
for name, constructor in _fields(cls).items(): # <4>
setattr(cls, name, Field(name, constructor)) # <5>
return cls # <7>
# end::CHECKED_DECORATOR[]
return cls
def _method(func: Callable) -> Callable:
_methods_to_inject.append(func)
return func
def _classmethod(func: Callable) -> Callable:
_classmethods_to_inject.append(func)
return func
# tag::CHECKED_METHODS_TOP[]
@_classmethod
def _fields(cls: type) -> dict[str, type]: # <1>
# tag::CHECKED_METHODS[]
def _fields(cls: type) -> dict[str, type]:
return get_type_hints(cls)
@_method
def __init__(self: Any, **kwargs: Any) -> None:
for name in self._fields(): # <6>
value = kwargs.pop(name, MISSING) # <7>
setattr(self, name, value) # <8>
if kwargs: # <9>
self.__flag_unknown_attrs(*kwargs) # <10>
for name in self._fields():
value = kwargs.pop(name, ...)
setattr(self, name, value)
if kwargs:
self.__flag_unknown_attrs(*kwargs)
@_method
def __setattr__(self: Any, name: str, value: Any) -> None: # <11>
if name in self._fields(): # <12>
def __setattr__(self: Any, name: str, value: Any) -> None:
if name in self._fields():
cls = self.__class__
descriptor = getattr(cls, name)
descriptor.__set__(self, value) # <13>
else: # <14>
descriptor.__set__(self, value)
else:
self.__flag_unknown_attrs(name)
# end::CHECKED_METHODS_TOP[]
# tag::CHECKED_METHODS_BOTTOM[]
@_method
def __flag_unknown_attrs(self: Any, *names: str) -> NoReturn: # <1>
def __flag_unknown_attrs(self: Any, *names: str) -> NoReturn:
plural = 's' if len(names) > 1 else ''
extra = ', '.join(f'{name!r}' for name in names)
cls_name = repr(self.__class__.__name__)
raise AttributeError(f'{cls_name} has no attribute{plural} {extra}')
@_method
def _asdict(self: Any) -> dict[str, Any]: # <2>
def _asdict(self: Any) -> dict[str, Any]:
return {
name: getattr(self, name)
for name, attr in self.__class__.__dict__.items()
if isinstance(attr, Field)
}
@_method
def __repr__(self: Any) -> str: # <3>
def __repr__(self: Any) -> str:
kwargs = ', '.join(
f'{key}={value!r}' for key, value in self._asdict().items()
)
return f'{self.__class__.__name__}({kwargs})'
# end::CHECKED_METHODS_BOTTOM[]
# end::CHECKED_METHODS[]

View File

@ -0,0 +1,26 @@
#!/usr/bin/env python3
from checkeddeco import checked
@checked
class Movie:
title: str
year: int
box_office: float
if __name__ == '__main__':
# No static type checker can understand this...
movie = Movie(title='The Godfather', year=1972, box_office=137) # type: ignore
print(movie.title)
print(movie)
try:
# remove the "type: ignore" comment to see Mypy correctly spot the error
movie.year = 'MCMLXXII' # type: ignore
except TypeError as e:
print(e)
try:
# Again, no static type checker can understand this...
blockbuster = Movie(title='Avatar', year=2009, box_office='billions') # type: ignore
except TypeError as e:
print(e)

View File

@ -38,3 +38,13 @@ def test_constructor_attribute_error():
felix = Cat(name='Felix', weight=3.2, age=7)
assert str(e.value) == "'Cat' has no attribute 'age'"
def test_field_invalid_constructor():
with pytest.raises(TypeError) as e:
@checked
class Cat:
name: str
weight: None
assert str(e.value) == "'weight' type hint must be callable"

View File

@ -1,13 +1,15 @@
#!/usr/bin/env python3
from checkedlib import Checked
class Movie(Checked):
title: str
year: int
megabucks: float
box_office: float
if __name__ == '__main__':
movie = Movie(title='The Godfather', year=1972, megabucks=137)
movie = Movie(title='The Godfather', year=1972, box_office=137)
print(movie.title)
print(movie)
try:
@ -16,6 +18,6 @@ if __name__ == '__main__':
except TypeError as e:
print(e)
try:
blockbuster = Movie(title='Avatar', year=2009, megabucks='billions')
blockbuster = Movie(title='Avatar', year=2009, box_office='billions')
except TypeError as e:
print(e)

View File

@ -7,29 +7,29 @@ used to create an instance, and provides a nice ``__repr__``::
>>> class Movie(Checked): # <1>
... title: str # <2>
... year: int
... megabucks: float
... box_office: float
...
>>> movie = Movie(title='The Godfather', year=1972, megabucks=137) # <3>
>>> movie = Movie(title='The Godfather', year=1972, box_office=137) # <3>
>>> movie.title
'The Godfather'
>>> movie # <4>
Movie(title='The Godfather', year=1972, megabucks=137.0)
Movie(title='The Godfather', year=1972, box_office=137.0)
# end::MOVIE_DEFINITION[]
The type of arguments is runtime checked when an attribute is set,
including during instantiation::
The type of arguments is runtime checked during instantiation
and when an attribute is set::
# tag::MOVIE_TYPE_VALIDATION[]
>>> movie.year = 'MCMLXXII' # <1>
>>> blockbuster = Movie(title='Avatar', year=2009, box_office='billions')
Traceback (most recent call last):
...
TypeError: 'billions' is not compatible with box_office:float
>>> movie.year = 'MCMLXXII'
Traceback (most recent call last):
...
TypeError: 'MCMLXXII' is not compatible with year:int
>>> blockbuster = Movie(title='Avatar', year=2009, megabucks='billions') # <2>
Traceback (most recent call last):
...
TypeError: 'billions' is not compatible with megabucks:float
# end::MOVIE_TYPE_VALIDATION[]
@ -39,29 +39,29 @@ default values::
# tag::MOVIE_DEFAULTS[]
>>> Movie(title='Life of Brian')
Movie(title='Life of Brian', year=0, megabucks=0.0)
Movie(title='Life of Brian', year=0, box_office=0.0)
# end::MOVIE_DEFAULTS[]
Providing extra arguments to the constructor is not allowed::
>>> blockbuster = Movie(title='Avatar', year=2009, megabucks=2000,
>>> blockbuster = Movie(title='Avatar', year=2009, box_office=2000,
... director='James Cameron')
Traceback (most recent call last):
...
AttributeError: 'Movie' has no attribute 'director'
AttributeError: 'Movie' object has no attribute 'director'
Creating new attributes at runtime is restricted as well::
>>> movie.director = 'Francis Ford Coppola'
Traceback (most recent call last):
...
AttributeError: 'Movie' has no attribute 'director'
AttributeError: 'Movie' object has no attribute 'director'
The `_as_dict` instance creates a `dict` from the attributes of a `Movie` object::
>>> movie._asdict()
{'title': 'The Godfather', 'year': 1972, 'megabucks': 137.0}
{'title': 'The Godfather', 'year': 1972, 'box_office': 137.0}
"""
@ -69,27 +69,25 @@ The `_as_dict` instance creates a `dict` from the attributes of a `Movie` object
from collections.abc import Callable # <1>
from typing import Any, NoReturn, get_type_hints
MISSING = object() # <2>
class Field:
def __init__(self, name: str, constructor: Callable) -> None: # <3>
def __init__(self, name: str, constructor: Callable) -> None: # <2>
if not callable(constructor) or constructor is type(None): # <3>
raise TypeError(f'{name!r} type hint must be callable')
self.name = name
self.constructor = constructor
def __set__(self, instance: 'Checked', value: Any) -> None: # <4>
if value is MISSING: # <5>
def __set__(self, instance: Any, value: Any) -> None:
if value is ...: # <4>
value = self.constructor()
else:
try:
value = self.constructor(value) # <6>
except (TypeError, ValueError) as e:
value = self.constructor(value) # <5>
except (TypeError, ValueError) as e: # <6>
type_name = self.constructor.__name__
msg = f'{value!r} is not compatible with {self.name}:{type_name}'
raise TypeError(msg) from e
instance.__dict__[self.name] = value # <7>
# end::CHECKED_FIELD[]
# tag::CHECKED_TOP[]
@ -105,36 +103,36 @@ class Checked:
def __init__(self, **kwargs: Any) -> None:
for name in self._fields(): # <6>
value = kwargs.pop(name, MISSING) # <7>
value = kwargs.pop(name, ...) # <7>
setattr(self, name, value) # <8>
if kwargs: # <9>
self.__flag_unknown_attrs(*kwargs) # <10>
def __setattr__(self, name: str, value: Any) -> None: # <11>
if name in self._fields(): # <12>
cls = self.__class__
descriptor = getattr(cls, name)
descriptor.__set__(self, value) # <13>
else: # <14>
self.__flag_unknown_attrs(name)
# end::CHECKED_TOP[]
# tag::CHECKED_BOTTOM[]
def __flag_unknown_attrs(self, *names: str) -> NoReturn: # <1>
def __setattr__(self, name: str, value: Any) -> None: # <1>
if name in self._fields(): # <2>
cls = self.__class__
descriptor = getattr(cls, name)
descriptor.__set__(self, value) # <3>
else: # <4>
self.__flag_unknown_attrs(name)
def __flag_unknown_attrs(self, *names: str) -> NoReturn: # <5>
plural = 's' if len(names) > 1 else ''
extra = ', '.join(f'{name!r}' for name in names)
cls_name = repr(self.__class__.__name__)
raise AttributeError(f'{cls_name} has no attribute{plural} {extra}')
raise AttributeError(f'{cls_name} object has no attribute{plural} {extra}')
def _asdict(self) -> dict[str, Any]: # <2>
def _asdict(self) -> dict[str, Any]: # <6>
return {
name: getattr(self, name)
for name, attr in self.__class__.__dict__.items()
if isinstance(attr, Field)
}
def __repr__(self) -> str: # <3>
def __repr__(self) -> str: # <7>
kwargs = ', '.join(
f'{key}={value!r}' for key, value in self._asdict().items()
)

View File

@ -0,0 +1,59 @@
import pytest
from checkedlib import Checked
def test_field_validation_type_error():
class Cat(Checked):
name: str
weight: float
with pytest.raises(TypeError) as e:
felix = Cat(name='Felix', weight=None)
assert str(e.value) == 'None is not compatible with weight:float'
def test_field_validation_value_error():
class Cat(Checked):
name: str
weight: float
with pytest.raises(TypeError) as e:
felix = Cat(name='Felix', weight='half stone')
assert str(e.value) == "'half stone' is not compatible with weight:float"
def test_constructor_attribute_error():
class Cat(Checked):
name: str
weight: float
with pytest.raises(AttributeError) as e:
felix = Cat(name='Felix', weight=3.2, age=7)
assert str(e.value) == "'Cat' object has no attribute 'age'"
def test_assignment_attribute_error():
class Cat(Checked):
name: str
weight: float
felix = Cat(name='Felix', weight=3.2)
with pytest.raises(AttributeError) as e:
felix.color = 'tan'
assert str(e.value) == "'Cat' object has no attribute 'color'"
def test_field_invalid_constructor():
with pytest.raises(TypeError) as e:
class Cat(Checked):
name: str
weight: None
assert str(e.value) == "'weight' type hint must be callable"

View File

@ -1,22 +1,25 @@
from checkeddeco import checked
#!/usr/bin/env python3
@checked
class Movie:
# tag::MOVIE_DEMO[]
from checkedlib import Checked
class Movie(Checked):
title: str
year: int
megabucks: float
box_office: float
if __name__ == '__main__':
movie = Movie(title='The Godfather', year=1972, megabucks=137)
print(movie.title)
movie = Movie(title='The Godfather', year=1972, box_office=137)
print(movie)
print(movie.title)
# end::MOVIE_DEMO[]
try:
# remove the "type: ignore" comment to see Mypy error
movie.year = 'MCMLXXII' # type: ignore
except TypeError as e:
print(e)
try:
blockbuster = Movie(title='Avatar', year=2009, megabucks='billions')
blockbuster = Movie(title='Avatar', year=2009, box_office='billions')
except TypeError as e:
print(e)

View File

@ -0,0 +1,148 @@
"""
A ``Checked`` subclass definition requires that keyword arguments are
used to create an instance, and provides a nice ``__repr__``::
# tag::MOVIE_DEFINITION[]
>>> class Movie(Checked): # <1>
... title: str # <2>
... year: int
... box_office: float
...
>>> movie = Movie(title='The Godfather', year=1972, box_office=137) # <3>
>>> movie.title
'The Godfather'
>>> movie # <4>
Movie(title='The Godfather', year=1972, box_office=137.0)
# end::MOVIE_DEFINITION[]
The type of arguments is runtime checked during instantiation
and when an attribute is set::
# tag::MOVIE_TYPE_VALIDATION[]
>>> blockbuster = Movie(title='Avatar', year=2009, box_office='billions')
Traceback (most recent call last):
...
TypeError: 'billions' is not compatible with box_office:float
>>> movie.year = 'MCMLXXII'
Traceback (most recent call last):
...
TypeError: 'MCMLXXII' is not compatible with year:int
# end::MOVIE_TYPE_VALIDATION[]
Attributes not passed as arguments to the constructor are initialized with
default values::
# tag::MOVIE_DEFAULTS[]
>>> Movie(title='Life of Brian')
Movie(title='Life of Brian', year=0, box_office=0.0)
# end::MOVIE_DEFAULTS[]
Providing extra arguments to the constructor is not allowed::
>>> blockbuster = Movie(title='Avatar', year=2009, box_office=2000,
... director='James Cameron')
Traceback (most recent call last):
...
AttributeError: 'Movie' object has no attribute 'director'
Creating new attributes at runtime is restricted as well::
>>> movie.director = 'Francis Ford Coppola'
Traceback (most recent call last):
...
AttributeError: 'Movie' object has no attribute 'director'
The `_as_dict` instance creates a `dict` from the attributes of a `Movie` object::
>>> movie._asdict()
{'title': 'The Godfather', 'year': 1972, 'box_office': 137.0}
"""
from collections.abc import Callable
from typing import Any, NoReturn, get_type_hints
# tag::CHECKED_FIELD[]
class Field:
def __init__(self, name: str, constructor: Callable) -> None:
if not callable(constructor) or constructor is type(None):
raise TypeError(f'{name!r} type hint must be callable')
self.name = name
self.storage_name = '_' + name # <1>
self.constructor = constructor
def __get__(self, instance, owner=None): # <2>
return getattr(instance, self.storage_name) # <3>
def __set__(self, instance: Any, value: Any) -> None:
if value is ...:
value = self.constructor()
else:
try:
value = self.constructor(value)
except (TypeError, ValueError) as e:
type_name = self.constructor.__name__
msg = f'{value!r} is not compatible with {self.name}:{type_name}'
raise TypeError(msg) from e
setattr(instance, self.storage_name, value) # <4>
# end::CHECKED_FIELD[]
# tag::CHECKED_META[]
class CheckedMeta(type):
def __new__(meta_cls, cls_name, bases, cls_dict): # <1>
if '__slots__' not in cls_dict: # <2>
slots = []
type_hints = cls_dict.get('__annotations__', {}) # <3>
for name, constructor in type_hints.items(): # <4>
field = Field(name, constructor) # <5>
cls_dict[name] = field # <6>
slots.append(field.storage_name) # <7>
cls_dict['__slots__'] = slots # <8>
return super().__new__(
meta_cls, cls_name, bases, cls_dict) # <9>
# end::CHECKED_META[]
# tag::CHECKED_CLASS[]
class Checked(metaclass=CheckedMeta):
__slots__ = () # skip CheckedMeta.__new__ processing
@classmethod
def _fields(cls) -> dict[str, type]:
return get_type_hints(cls)
def __init__(self, **kwargs: Any) -> None:
for name in self._fields():
value = kwargs.pop(name, ...)
setattr(self, name, value)
if kwargs:
self.__flag_unknown_attrs(*kwargs)
def __flag_unknown_attrs(self, *names: str) -> NoReturn:
plural = 's' if len(names) > 1 else ''
extra = ', '.join(f'{name!r}' for name in names)
cls_name = repr(self.__class__.__name__)
raise AttributeError(f'{cls_name} object has no attribute{plural} {extra}')
def _asdict(self) -> dict[str, Any]:
return {
name: getattr(self, name)
for name, attr in self.__class__.__dict__.items()
if isinstance(attr, Field)
}
def __repr__(self) -> str:
kwargs = ', '.join(
f'{key}={value!r}' for key, value in self._asdict().items()
)
return f'{self.__class__.__name__}({kwargs})'
# end::CHECKED_CLASS[]

View File

@ -34,4 +34,25 @@ def test_constructor_attribute_error():
with pytest.raises(AttributeError) as e:
felix = Cat(name='Felix', weight=3.2, age=7)
assert str(e.value) == "'Cat' has no attribute 'age'"
assert str(e.value) == "'Cat' object has no attribute 'age'"
def test_assignment_attribute_error():
class Cat(Checked):
name: str
weight: float
felix = Cat(name='Felix', weight=3.2)
with pytest.raises(AttributeError) as e:
felix.color = 'tan'
assert str(e.value) == "'Cat' object has no attribute 'color'"
def test_field_invalid_constructor():
with pytest.raises(TypeError) as e:
class Cat(Checked):
name: str
weight: None
assert str(e.value) == "'weight' type hint must be callable"

View File

@ -1,29 +0,0 @@
# tag::BEGINNING[]
print('<[100]> evalsupport module start')
def deco_alpha(cls):
print('<[200]> deco_alpha')
def inner_1(self):
print('<[300]> deco_alpha:inner_1')
cls.method_y = inner_1
return cls
# end::BEGINNING[]
# tag::META_ALEPH[]
class MetaAleph(type):
print('<[400]> MetaAleph body')
def __init__(cls, name, bases, dic):
print('<[500]> MetaAleph.__init__')
def inner_2(self):
print('<[600]> MetaAleph.__init__:inner_2')
cls.method_z = inner_2
# end::META_ALEPH[]
# tag::END[]
print('<[700]> evalsupport module end')
# end::END[]

View File

@ -1,49 +0,0 @@
from evalsupport import deco_alpha
print('<[1]> evaltime module start')
class ClassOne():
print('<[2]> ClassOne body')
def __init__(self):
print('<[3]> ClassOne.__init__')
def __del__(self):
print('<[4]> ClassOne.__del__')
def method_x(self):
print('<[5]> ClassOne.method_x')
class ClassTwo(object):
print('<[6]> ClassTwo body')
@deco_alpha
class ClassThree():
print('<[7]> ClassThree body')
def method_y(self):
print('<[8]> ClassThree.method_y')
class ClassFour(ClassThree):
print('<[9]> ClassFour body')
def method_y(self):
print('<[10]> ClassFour.method_y')
if __name__ == '__main__':
print('<[11]> ClassOne tests', 30 * '.')
one = ClassOne()
one.method_x()
print('<[12]> ClassThree tests', 30 * '.')
three = ClassThree()
three.method_y()
print('<[13]> ClassFour tests', 30 * '.')
four = ClassFour()
four.method_y()
print('<[14]> evaltime module end')

View File

@ -0,0 +1,50 @@
# tag::BUILDERLIB_TOP[]
print('@ builderlib module start')
class Builder: # <1>
print('@ Builder body')
def __init_subclass__(cls): # <2>
print(f'@ Builder.__init_subclass__({cls!r})')
def inner_0(self): # <3>
print(f'@ SuperA.__init_subclass__:inner_0({self!r})')
cls.method_a = inner_0
def __init__(self):
super().__init__()
print(f'@ Builder.__init__({self!r})')
def deco(cls): # <4>
print(f'@ deco({cls!r})')
def inner_1(self): # <5>
print(f'@ deco:inner_1({self!r})')
cls.method_b = inner_1
return cls # <6>
# end::BUILDERLIB_TOP[]
# tag::BUILDERLIB_BOTTOM[]
class Descriptor: # <1>
print('@ Descriptor body')
def __init__(self): # <2>
print(f'@ Descriptor.__init__({self!r})')
def __set_name__(self, owner, name): # <3>
args = (self, owner, name)
print(f'@ Descriptor.__set_name__{args!r}')
def __set__(self, instance, value): # <4>
args = (self, instance, value)
print(f'@ Descriptor.__set__{args!r}')
def __repr__(self):
return '<Descriptor instance>'
print('@ builderlib module end')
# end::BUILDERLIB_BOTTOM[]

View File

@ -0,0 +1,30 @@
#!/usr/bin/env python3
from builderlib import Builder, deco, Descriptor
print('# evaldemo module start')
@deco # <1>
class Klass(Builder): # <2>
print('# Klass body')
attr = Descriptor() # <3>
def __init__(self):
super().__init__()
print(f'# Klass.__init__({self!r})')
def __repr__(self):
return '<Klass instance>'
def main(): # <4>
obj = Klass()
obj.method_a()
obj.method_b()
obj.attr = 999
if __name__ == '__main__':
main()
print('# evaldemo module end')

View File

@ -0,0 +1,33 @@
#!/usr/bin/env python3
from builderlib import Builder, deco, Descriptor
from metalib import MetaKlass # <1>
print('# evaldemo_meta module start')
@deco
class Klass(Builder, metaclass=MetaKlass): # <2>
print('# Klass body')
attr = Descriptor()
def __init__(self):
super().__init__()
print(f'# Klass.__init__({self!r})')
def __repr__(self):
return '<Klass instance>'
def main():
obj = Klass()
obj.method_a()
obj.method_b()
obj.method_c() # <3>
obj.attr = 999
if __name__ == '__main__':
main()
print('# evaldemo_meta module end')

View File

@ -0,0 +1,43 @@
# tag::METALIB_TOP[]
print('% metalib module start')
import collections
class NosyDict(collections.UserDict):
def __setitem__(self, key, value):
args = (self, key, value)
print(f'% NosyDict.__setitem__{args!r}')
super().__setitem__(key, value)
def __repr__(self):
return '<NosyDict instance>'
# end::METALIB_TOP[]
# tag::METALIB_BOTTOM[]
class MetaKlass(type):
print('% MetaKlass body')
@classmethod # <1>
def __prepare__(meta_cls, cls_name, bases): # <2>
args = (meta_cls, cls_name, bases)
print(f'% MetaKlass.__prepare__{args!r}')
return NosyDict() # <3>
def __new__(meta_cls, cls_name, bases, cls_dict): # <4>
args = (meta_cls, cls_name, bases, cls_dict)
print(f'% MetaKlass.__new__{args!r}')
def inner_2(self):
print(f'% MetaKlass.__new__:inner_2({self!r})')
cls = super().__new__(meta_cls, cls_name, bases, cls_dict.data) # <5>
cls.method_c = inner_2 # <6>
return cls # <7>
def __repr__(cls): # <8>
cls_name = cls.__name__
return f"<class {cls_name!r} built by MetaKlass>"
print('% metalib module end')
# end::METALIB_BOTTOM[]

View File

@ -1,53 +0,0 @@
from evalsupport import deco_alpha
from evalsupport import MetaAleph
print('<[1]> evaltime_meta module start')
@deco_alpha
class ClassThree():
print('<[2]> ClassThree body')
def method_y(self):
print('<[3]> ClassThree.method_y')
class ClassFour(ClassThree):
print('<[4]> ClassFour body')
def method_y(self):
print('<[5]> ClassFour.method_y')
class ClassFive(metaclass=MetaAleph):
print('<[6]> ClassFive body')
def __init__(self):
print('<[7]> ClassFive.__init__')
def method_z(self):
print('<[8]> ClassFive.method_z')
class ClassSix(ClassFive):
print('<[9]> ClassSix body')
def method_z(self):
print('<[10]> ClassSix.method_z')
if __name__ == '__main__':
print('<[11]> ClassThree tests', 30 * '.')
three = ClassThree()
three.method_y()
print('<[12]> ClassFour tests', 30 * '.')
four = ClassFour()
four.method_y()
print('<[13]> ClassFive tests', 30 * '.')
five = ClassFive()
five.method_z()
print('<[14]> ClassSix tests', 30 * '.')
six = ClassSix()
six.method_z()
print('<[15]> evaltime_meta module end')

Some files were not shown because too many files have changed in this diff Show More