ch15: draft examples

This commit is contained in:
Luciano Ramalho 2021-05-20 22:58:05 -03:00
parent 5312d4f824
commit 1689eec623
19 changed files with 501 additions and 36 deletions

View File

@ -0,0 +1,41 @@
from typing import TypeVar, Generic
class Beverage:
"""Any beverage"""
class Juice(Beverage):
"""Any fruit juice"""
class OrangeJuice(Juice):
"""Delicious juice Brazilian oranges"""
class Coak(Beverage):
"""Secret formula with lots of sugar"""
BeverageT = TypeVar('BeverageT', bound=Beverage)
JuiceT = TypeVar('JuiceT', bound=Juice)
class BeverageDispenser(Generic[BeverageT]):
beverage: BeverageT
def __init__(self, beverage: BeverageT) -> None:
self.beverage = beverage
def dispense(self) -> BeverageT:
return self.beverage
class JuiceDispenser(BeverageDispenser[JuiceT]):
pass
class Cafeteria:
def __init__(self, dispenser: BeverageDispenser[JuiceT]):
self.dispenser = dispenser

View File

@ -0,0 +1,23 @@
from cafeteria import (
Cafeteria,
BeverageDispenser,
JuiceDispenser,
Juice,
OrangeJuice,
Coak,
)
orange = OrangeJuice()
orange_dispenser: JuiceDispenser[OrangeJuice] = JuiceDispenser(orange)
juice: Juice = orange_dispenser.dispense()
soda = Coak()
## Value of type variable "JuiceT" of "JuiceDispenser" cannot be "Coak"
# soda_dispenser = JuiceDispenser(soda)
soda_dispenser = BeverageDispenser(soda)
arnold_hall = Cafeteria(soda_dispenser)

View File

@ -0,0 +1,16 @@
from collections.abc import Collection, Sequence
col_int: Collection[int]
seq_int: Sequence[int] = (1, 2, 3)
## Incompatible types in assignment
## expression has type "Collection[int]"
## variable has type "Sequence[int]"
# seq_int = col_int
col_int = seq_int
## List item 0 has incompatible type "float"
## expected "int"
# col_int = [1.1]

View File

@ -1,5 +1,4 @@
import random
from typing import Iterable, TYPE_CHECKING, List
from typing import TYPE_CHECKING
from erp import EnterpriserRandomPopper
import randompop
@ -9,7 +8,6 @@ def test_issubclass() -> None:
assert issubclass(EnterpriserRandomPopper, randompop.RandomPopper)
def test_isinstance_untyped_items_argument() -> None:
items = [1, 2, 3]
popper = EnterpriserRandomPopper(items) # [int] is not required

View File

@ -0,0 +1,59 @@
"""
In ``Generator[YieldType, SendType, ReturnType]``,
``SendType`` is contravariant.
The other type variables are covariant.
This is how ``typing.Generator`` is declared::
class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co]):
(from https://docs.python.org/3/library/typing.html#typing.Generator)
"""
from typing import Generator
# Generator[YieldType, SendType, ReturnType]
def gen_float_take_int() -> Generator[float, int, str]:
received = yield -1.0
while received:
received = yield float(received)
return 'Done'
def gen_float_take_float() -> Generator[float, float, str]:
received = yield -1.0
while received:
received = yield float(received)
return 'Done'
def gen_float_take_complex() -> Generator[float, complex, str]:
received = yield -1.0
while received:
received = yield abs(received)
return 'Done'
# Generator[YieldType, SendType, ReturnType]
g0: Generator[float, float, str] = gen_float_take_float()
g1: Generator[complex, float, str] = gen_float_take_float()
## Incompatible types in assignment
## expression has type "Generator[float, float, str]"
## variable has type "Generator[int, float, str]")
# g2: Generator[int, float, str] = gen_float_take_float()
# Generator[YieldType, SendType, ReturnType]
g3: Generator[float, int, str] = gen_float_take_float()
## Incompatible types in assignment
## expression has type "Generator[float, float, str]"
## variable has type "Generator[float, complex, str]")
## g4: Generator[float, complex, str] = gen_float_take_float()

View File

@ -0,0 +1,47 @@
from typing import TypeVar, Generic, Any
class Pet:
"""Domestic animal kept for companionship."""
class Dog(Pet):
"""Canis familiaris"""
class Cat(Pet):
"""Felis catus"""
class Siamese(Cat):
"""Cat breed from Thailand"""
T = TypeVar('T')
class Box(Generic[T]):
def put(self, item: T) -> None:
self.contents = item
def get(self) -> 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)
class OutBox(Generic[T_co]):
def __init__(self, contents: Any):
self.contents = contents
def get(self) -> Any:
return self.contents

View File

@ -0,0 +1,42 @@
from typing import TYPE_CHECKING
from petbox import *
cat_box: Box[Cat] = Box()
si = Siamese()
cat_box.put(si)
animal = cat_box.get()
#if TYPE_CHECKING:
# reveal_type(animal) # Revealed: petbox.Cat*
################### Covariance
out_box: OutBox[Cat] = OutBox(Cat())
out_box_si: OutBox[Siamese] = OutBox(Siamese())
## Incompatible types in assignment
## expression has type "OutBox[Cat]"
# variable has type "OutBox[Siamese]"
# out_box_si = out_box
out_box = out_box_si
################### Contravariance
in_box: InBox[Cat] = InBox()
in_box_si: InBox[Siamese] = InBox()
in_box_si = in_box
## Incompatible types in assignment
## expression has type "InBox[Siamese]"
## variable has type "InBox[Cat]"
# in_box = in_box_si

View File

@ -4,7 +4,7 @@ from typing import Iterable, TYPE_CHECKING
from randompick_generic import GenericRandomPicker
class LottoPicker():
class LottoPicker:
def __init__(self, items: Iterable[int]) -> None:
self._items = list(items)
random.shuffle(self._items)

View File

@ -1,4 +1,4 @@
from typing import Protocol, TypeVar, runtime_checkable, Any
from typing import Protocol, runtime_checkable, Any
@runtime_checkable

View File

@ -3,7 +3,7 @@ import random
from typing import Any, Iterable, TYPE_CHECKING
class SimplePopper():
class SimplePopper:
def __init__(self, items: Iterable) -> None:
self._items = list(items)
random.shuffle(self._items)

View File

@ -0,0 +1,32 @@
# 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

@ -0,0 +1,32 @@
# 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

@ -0,0 +1,20 @@
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

@ -0,0 +1,23 @@
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

@ -0,0 +1,112 @@
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

@ -0,0 +1,20 @@
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

@ -1,6 +1,6 @@
# Fluent Python 2e example code
Example code for the book **Fluent Python, 2<sup>nd</sup> edition** by Luciano Ramalho (O'Reilly, 2021).
Example code for the book **Fluent Python, 2<sup>nd</sup> edition** by Luciano Ramalho (O'Reilly, 2020).
> **BEWARE**: This is a work in progress!
>
@ -14,40 +14,40 @@ Example code for the book **Fluent Python, 2<sup>nd</sup> edition** by Luciano R
All chapters are undergoing review and updates, including significant rewrites in the chapters about concurrency in **Part V**.
New chapters in **Fluent Python 2<sup>nd</sup> edition** are marked with 🆕.
New chapters in **Fluent Python 2e** are marked with 🆕.
🚨 This table of contents is subject to change at any time until the book goes to the printer.
🚨 This table of contents is subject to change at any time until the book goes to the printer.
Part / Chapter #|Title|Directory|1<sup>st</sup> edition Chapter&nbsp;#
---:|---|---|:---:
Part / Chapter #|Title|Directory|Notebook|1<sup>st</sup> ed. Chapter&nbsp;#
---:|---|---|---|:---:
**I Prologue**|
1|The Python Data Model|[01-data-model](01-data-model)|1
1|The Python Data Model|[01-data-model](01-data-model)|[data-model.ipynb](01-data-model/data-model.ipynb)|1
**II Data Structures**|
2|An Array of Sequences|[02-array-seq](02-array-seq)|2
3|Dictionaries and Sets|[03-dict-set](03-dict-set)|3
4|Text versus Bytes|[04-text-byte](04-text-byte)|4
5|Record-like Data Structures|[05-record-like](05-record-like)|🆕
6|Object References, Mutability, and Recycling|[06-obj-ref](06-obj-ref)|8
2|An Array of Sequences|[02-array-seq](02-array-seq)|[array-seq.ipynb](02-array-seq/array-seq.ipynb)|2
3|Dictionaries and Sets|[03-dict-set](03-dict-set)||3
4|Text versus Bytes|[04-text-byte](04-text-byte)||4
🆕 5|Record-like Data Structures|[05-record-like](05-record-like)||
6|Object References, Mutability, and Recycling|[06-obj-ref](06-obj-ref)||8
**III Functions as Objects**|
7|First-Class Funcions|[07-1class-func](07-1class-func)|5
8|Type Hints in Function Definitions|[08-def-type-hints](08-def-type-hints)|🆕
9|Function Decorators and Closures|[09-closure-deco](09-closure-deco)|7
10|Design Patterns with First-Class Functions|[10-dp-1class-func](10-dp-1class-func)|6
7|First-Class Funcions|[07-1class-func](07-1class-func)||5
🆕 8|Type Hints in Function Definitions|[08-def-type-hints](08-def-type-hints)||
9|Function Decorators and Closures|[09-closure-deco](09-closure-deco)||7
10|Design Patterns with First-Class Functions|[10-dp-1class-func](10-dp-1class-func)||6
**IV Object-Oriented Idioms**|
11|A Pythonic Object|[11-pythonic-obj](11-pythonic-obj)|9
12|Sequence Hacking, Hashing, and Slicing|[12-seq-hacking](12-seq-hacking)|10
13|Interfaces, Protocols, and ABCs|[13-protocl-abc](13-protocol-abc)|11
14|Inheritance: For Good or For Worse|[14-inheritance](14-inheritance)|12
15|More About Type Hints|15-more-typing|🆕
16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)|13
11|A Pythonic Object|[11-pythonic-obj](11-pythonic-obj)||9
12|Sequence Hacking, Hashing, and Slicing|[12-seq-hacking](12-seq-hacking)||10
13|Interfaces, Protocols, and ABCs|[13-protocl-abc](13-protocol-abc)||11
14|Inheritance: For Good or For Worse|[14-inheritance](14-inheritance)||12
🆕 15|More About Type Hints|[15-more-types](15-more-types)||
16|Operator Overloading: Doing It Right|[16-op-overloading](16-op-overloading)||13
**V Control Flow**|
17|Iterables, Iterators, and Generators|[17-it-generator](17-it-generator)|14
18|Context Managers and else Blocks|[18-context-mngr](18-context-mngr)|15
19|Classic Coroutines|[19-coroutine](19-coroutine)|16
20|Concurrency Models in Python|[20-concurrency](20-concurrency)|🆕
21|Concurrency with Futures|[21-futures](21-futures)|17
22|Asynchronous Programming|[22-async](22-async)|18
17|Iterables, Iterators, and Generators|[17-it-generator](17-it-generator)||14
18|Context Managers and else Blocks|[18-context-mngr](18-context-mngr)||15
19|Classic Coroutines|[19-coroutine](19-coroutine)||16
🆕 20|Concurrency Models in Python|[20-concurrency](20-concurrency)||-
21|Concurrency with Futures|[21-futures](21-futures)||17
22|Asynchronous Programming|[22-async](22-async)||18
**VI Metaprogramming**|
23|Dynamic Attributes and Properties|[23-dyn-attr-prop](23-dyn-attr-prop)|19
24|Attribute Descriptors|[24-descriptor](24-descriptor)|20
25|Class Metaprogramming|[25-class-metaprog](25-class-metaprog)|21
23|Dynamic Attributes and Properties|[22-dyn-attr-prop](22-dyn-attr-prop)||19
24|Attribute Descriptors|[23-descriptor](23-descriptor)||20
25|Class Metaprogramming|[24-class-metaprog](24-class-metaprog)||21