ch05: new chapter
This commit is contained in:
parent
b74ea52ffb
commit
1df34f2945
22
05-record-like/cards.doctest
Normal file
22
05-record-like/cards.doctest
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
>>> from cards import Card
|
||||||
|
>>> helen = Card('Q', 'hearts')
|
||||||
|
>>> helen
|
||||||
|
Card(rank='Q', suit='hearts')
|
||||||
|
|
||||||
|
>>> cards = [Card(r, s) for s in Card.suits for r in Card.ranks]
|
||||||
|
>>> cards[:3]
|
||||||
|
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
|
||||||
|
>>> sorted(cards)[:3]
|
||||||
|
[Card(rank='2', suit='clubs'), Card(rank='2', suit='diamonds'), Card(rank='2', suit='hearts')]
|
||||||
|
|
||||||
|
>>> from cards_enum import Card, Suit, Rank
|
||||||
|
>>> helen = Card('Q', 'hearts')
|
||||||
|
>>> helen
|
||||||
|
Card(rank='Q', suit='hearts')
|
||||||
|
|
||||||
|
>>> cards = [Card(r, s) for s in Suit for r in Rank]
|
||||||
|
>>> cards[:3]
|
||||||
|
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
|
||||||
|
>>> sorted(cards)[:3]
|
||||||
|
[Card(rank='2', suit='clubs'), Card(rank='2', suit='diamonds'), Card(rank='2', suit='hearts')]
|
||||||
|
>>> for card in cards[12::13]: print(card)
|
9
05-record-like/cards.py
Normal file
9
05-record-like/cards.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass(order=True)
|
||||||
|
class Card:
|
||||||
|
rank: str
|
||||||
|
suit: str
|
||||||
|
|
||||||
|
ranks = [str(n) for n in range(2, 10)] + list('JQKA')
|
||||||
|
suits = 'spades diamonds clubs hearts'.split()
|
14
05-record-like/cards_enum.py
Normal file
14
05-record-like/cards_enum.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
import enum
|
||||||
|
|
||||||
|
Suit = enum.IntEnum('Suit', 'spades diamonds clubs hearts')
|
||||||
|
Rank = enum.Enum('Rank', [str(n) for n in range(2, 10)] + list('JQKA'))
|
||||||
|
|
||||||
|
@dataclass(order=True)
|
||||||
|
class Card:
|
||||||
|
rank: Suit
|
||||||
|
suit: Rank
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
glyphs = [chr(x) for x in range(0x2660, 0x2664)]
|
||||||
|
return f'{self.rank} of {glyphs[self.suit-1]}'
|
16
05-record-like/class/coordinates.py
Normal file
16
05-record-like/class/coordinates.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
``Coordinate``: a simple class with a custom ``__str__``::
|
||||||
|
|
||||||
|
>>> moscow = Coordinate(55.756, 37.617)
|
||||||
|
>>> print(moscow) # doctest:+ELLIPSIS
|
||||||
|
<coordinates.Coordinate object at 0x...>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# tag::COORDINATE[]
|
||||||
|
class Coordinate:
|
||||||
|
|
||||||
|
def __init__(self, lat, long):
|
||||||
|
self.lat = lat
|
||||||
|
self.long = long
|
||||||
|
|
||||||
|
# end::COORDINATE[]
|
9
05-record-like/dataclass/club.py
Normal file
9
05-record-like/dataclass/club.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ClubMember:
|
||||||
|
|
||||||
|
name: str
|
||||||
|
guests: list = field(default_factory=list)
|
||||||
|
|
8
05-record-like/dataclass/club_generic.py
Normal file
8
05-record-like/dataclass/club_generic.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List # <1>
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ClubMember:
|
||||||
|
|
||||||
|
name: str
|
||||||
|
guests: List[str] = field(default_factory=list) # <2>
|
9
05-record-like/dataclass/club_wrong.py
Normal file
9
05-record-like/dataclass/club_wrong.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
# tag::CLUBMEMBER[]
|
||||||
|
@dataclass
|
||||||
|
class ClubMember:
|
||||||
|
|
||||||
|
name: str
|
||||||
|
guests: list = []
|
||||||
|
# end::CLUBMEMBER[]
|
24
05-record-like/dataclass/coordinates.py
Normal file
24
05-record-like/dataclass/coordinates.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
"""
|
||||||
|
``Coordinate``: simple class decorated with ``dataclass`` and a custom ``__str__``::
|
||||||
|
|
||||||
|
>>> moscow = Coordinate(55.756, 37.617)
|
||||||
|
>>> print(moscow)
|
||||||
|
55.8°N, 37.6°E
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# tag::COORDINATE[]
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Coordinate:
|
||||||
|
|
||||||
|
lat: float
|
||||||
|
long: float
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
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[]
|
50
05-record-like/dataclass/hackerclub.py
Normal file
50
05-record-like/dataclass/hackerclub.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# tag::DOCTESTS[]
|
||||||
|
"""
|
||||||
|
``HackerClubMember`` objects accept an optional ``handle`` argument::
|
||||||
|
|
||||||
|
>>> anna = HackerClubMember('Anna Ravenscroft', handle='AnnaRaven')
|
||||||
|
>>> anna
|
||||||
|
HackerClubMember(name='Anna Ravenscroft', guests=[], handle='AnnaRaven')
|
||||||
|
|
||||||
|
If ``handle`` is ommitted, it's set to the first part of the member's name::
|
||||||
|
|
||||||
|
>>> leo = HackerClubMember('Leo Rochael')
|
||||||
|
>>> leo
|
||||||
|
HackerClubMember(name='Leo Rochael', guests=[], handle='Leo')
|
||||||
|
|
||||||
|
Members must have a unique handle. The following ``leo2`` will not be created,
|
||||||
|
because its ``handle`` would be 'Leo', which was taken by ``leo``::
|
||||||
|
|
||||||
|
>>> leo2 = HackerClubMember('Leo DaVinci')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: handle 'Leo' already exists.
|
||||||
|
|
||||||
|
To fix, ``leo2`` must be created with an explicit ``handle``::
|
||||||
|
|
||||||
|
>>> leo2 = HackerClubMember('Leo DaVinci', handle='Neo')
|
||||||
|
>>> leo2
|
||||||
|
HackerClubMember(name='Leo DaVinci', guests=[], handle='Neo')
|
||||||
|
"""
|
||||||
|
# end::DOCTESTS[]
|
||||||
|
|
||||||
|
# tag::HACKERCLUB[]
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from club import ClubMember
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class HackerClubMember(ClubMember): # <1>
|
||||||
|
|
||||||
|
all_handles = set() # <2>
|
||||||
|
|
||||||
|
handle: str = '' # <3>
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
cls = self.__class__ # <4>
|
||||||
|
if self.handle == '': # <5>
|
||||||
|
self.handle = self.name.split()[0]
|
||||||
|
if self.handle in cls.all_handles: # <6>
|
||||||
|
msg = f'handle {self.handle!r} already exists.'
|
||||||
|
raise ValueError(msg)
|
||||||
|
cls.all_handles.add(self.handle) # <7>
|
||||||
|
# end::HACKERCLUB[]
|
51
05-record-like/dataclass/hackerclub_annotated.py
Normal file
51
05-record-like/dataclass/hackerclub_annotated.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# tag::DOCTESTS[]
|
||||||
|
"""
|
||||||
|
``HackerClubMember`` objects can be created with a ``name`` and an optional ``handle``::
|
||||||
|
|
||||||
|
>>> anna = HackerClubMember('Anna Ravenscroft', handle='AnnaRaven')
|
||||||
|
>>> anna
|
||||||
|
HackerClubMember(name='Anna Ravenscroft', guests=[], handle='AnnaRaven')
|
||||||
|
|
||||||
|
If ``handle`` is ommitted, it's set to the first part of the member's name::
|
||||||
|
|
||||||
|
>>> leo = HackerClubMember('Leo Rochael')
|
||||||
|
>>> leo
|
||||||
|
HackerClubMember(name='Leo Rochael', guests=[], handle='Leo')
|
||||||
|
|
||||||
|
Members must have a unique handle. This ``leo2`` will not be created,
|
||||||
|
because its ``handle`` would be 'Leo', which was taken by ``leo``::
|
||||||
|
|
||||||
|
>>> leo2 = HackerClubMember('Leo DaVinci')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: handle 'Leo' already exists.
|
||||||
|
|
||||||
|
To fix, ``leo2`` must be created with an explicit ``handle``::
|
||||||
|
|
||||||
|
>>> leo2 = HackerClubMember('Leo DaVinci', handle='Neo')
|
||||||
|
>>> leo2
|
||||||
|
HackerClubMember(name='Leo DaVinci', guests=[], handle='Neo')
|
||||||
|
"""
|
||||||
|
# end::DOCTESTS[]
|
||||||
|
|
||||||
|
# tag::HACKERCLUB[]
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import ClassVar, Set
|
||||||
|
from club import ClubMember
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class HackerClubMember(ClubMember):
|
||||||
|
|
||||||
|
all_handles: ClassVar[Set[str]] = set()
|
||||||
|
|
||||||
|
handle: str = ''
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
cls = self.__class__
|
||||||
|
if self.handle == '':
|
||||||
|
self.handle = self.name.split()[0]
|
||||||
|
if self.handle in cls.all_handles:
|
||||||
|
msg = f'handle {self.handle!r} already exists.'
|
||||||
|
raise ValueError(msg)
|
||||||
|
cls.all_handles.add(self.handle)
|
||||||
|
# end::HACKERCLUB[]
|
87
05-record-like/dataclass/resource.py
Normal file
87
05-record-like/dataclass/resource.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
"""
|
||||||
|
Media resource description class with subset of the Dublin Core fields.
|
||||||
|
|
||||||
|
Default field values:
|
||||||
|
|
||||||
|
>>> r = Resource('0')
|
||||||
|
>>> r # doctest: +NORMALIZE_WHITESPACE
|
||||||
|
Resource(identifier='0', title='<untitled>', creators=[], date=None,
|
||||||
|
type=<ResourceType.BOOK: 1>, description='', language='', subjects=[])
|
||||||
|
|
||||||
|
A complete resource record:
|
||||||
|
# tag::DOCTEST[]
|
||||||
|
|
||||||
|
>>> description = 'Improving the design of existing code'
|
||||||
|
>>> book = Resource('978-0-13-475759-9', 'Refactoring, 2nd Edition',
|
||||||
|
... ['Martin Fowler', 'Kent Beck'], date(2018, 11, 19),
|
||||||
|
... ResourceType.BOOK, description, 'EN',
|
||||||
|
... ['computer programming', 'OOP'])
|
||||||
|
>>> book # doctest: +NORMALIZE_WHITESPACE
|
||||||
|
Resource(identifier='978-0-13-475759-9', title='Refactoring, 2nd Edition',
|
||||||
|
creators=['Martin Fowler', 'Kent Beck'], date=datetime.date(2018, 11, 19),
|
||||||
|
type=<ResourceType.BOOK: 1>, description='Improving the design of existing code',
|
||||||
|
language='EN', subjects=['computer programming', 'OOP'])
|
||||||
|
|
||||||
|
# end::DOCTEST[]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# tag::DATACLASS[]
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List, Optional
|
||||||
|
from enum import Enum, auto
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceType(Enum): # <1>
|
||||||
|
BOOK = auto()
|
||||||
|
EBOOK = auto()
|
||||||
|
VIDEO = auto()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Resource:
|
||||||
|
"""Media resource description."""
|
||||||
|
identifier: str # <2>
|
||||||
|
title: str = '<untitled>' # <3>
|
||||||
|
creators: List[str] = field(default_factory=list)
|
||||||
|
date: Optional[date] = None # <4>
|
||||||
|
type: ResourceType = ResourceType.BOOK # <5>
|
||||||
|
description: str = ''
|
||||||
|
language: str = ''
|
||||||
|
subjects: List[str] = field(default_factory=list)
|
||||||
|
# end::DATACLASS[]
|
||||||
|
|
||||||
|
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceDict(TypedDict):
|
||||||
|
identifier: str
|
||||||
|
title: str
|
||||||
|
creators: List[str]
|
||||||
|
date: Optional[date]
|
||||||
|
type: ResourceType
|
||||||
|
description: str
|
||||||
|
language: str
|
||||||
|
subjects: List[str]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
r = Resource('0')
|
||||||
|
description = 'Improving the design of existing code'
|
||||||
|
book = Resource('978-0-13-475759-9', 'Refactoring, 2nd Edition',
|
||||||
|
['Martin Fowler', 'Kent Beck'], date(2018, 11, 19),
|
||||||
|
ResourceType.BOOK, description,
|
||||||
|
'EN', ['computer programming', 'OOP'])
|
||||||
|
print(book)
|
||||||
|
book_dict: ResourceDict = {
|
||||||
|
'identifier': '978-0-13-475759-9',
|
||||||
|
'title': 'Refactoring, 2nd Edition',
|
||||||
|
'creators': ['Martin Fowler', 'Kent Beck'],
|
||||||
|
'date': date(2018, 11, 19),
|
||||||
|
'type': ResourceType.BOOK,
|
||||||
|
'description': 'Improving the design of existing code',
|
||||||
|
'language': 'EN',
|
||||||
|
'subjects': ['computer programming', 'OOP']}
|
||||||
|
book2 = Resource(**book_dict)
|
||||||
|
print(book == book2)
|
111
05-record-like/dataclass/resource_repr.py
Normal file
111
05-record-like/dataclass/resource_repr.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
"""
|
||||||
|
Media resource description class with subset of the Dublin Core fields.
|
||||||
|
|
||||||
|
Default field values:
|
||||||
|
|
||||||
|
>>> r = Resource('0')
|
||||||
|
>>> r # doctest: +NORMALIZE_WHITESPACE
|
||||||
|
Resource(
|
||||||
|
identifier = '0',
|
||||||
|
title = '<untitled>',
|
||||||
|
creators = [],
|
||||||
|
date = None,
|
||||||
|
type = <ResourceType.BOOK: 1>,
|
||||||
|
description = '',
|
||||||
|
language = '',
|
||||||
|
subjects = [],
|
||||||
|
)
|
||||||
|
|
||||||
|
A complete resource record:
|
||||||
|
|
||||||
|
>>> description = 'Improving the design of existing code'
|
||||||
|
>>> book = Resource('978-0-13-475759-9', 'Refactoring, 2nd Edition',
|
||||||
|
... ['Martin Fowler', 'Kent Beck'], date(2018, 11, 19),
|
||||||
|
... ResourceType.BOOK, description, 'EN',
|
||||||
|
... ['computer programming', 'OOP'])
|
||||||
|
|
||||||
|
# tag::DOCTEST[]
|
||||||
|
>>> book # doctest: +NORMALIZE_WHITESPACE
|
||||||
|
Resource(
|
||||||
|
identifier = '978-0-13-475759-9',
|
||||||
|
title = 'Refactoring, 2nd Edition',
|
||||||
|
creators = ['Martin Fowler', 'Kent Beck'],
|
||||||
|
date = datetime.date(2018, 11, 19),
|
||||||
|
type = <ResourceType.BOOK: 1>,
|
||||||
|
description = 'Improving the design of existing code',
|
||||||
|
language = 'EN',
|
||||||
|
subjects = ['computer programming', 'OOP'],
|
||||||
|
)
|
||||||
|
|
||||||
|
# end::DOCTEST[]
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field, fields
|
||||||
|
from typing import List, Optional, TypedDict
|
||||||
|
from enum import Enum, auto
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceType(Enum):
|
||||||
|
BOOK = auto()
|
||||||
|
EBOOK = auto()
|
||||||
|
VIDEO = auto()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Resource:
|
||||||
|
"""Media resource description."""
|
||||||
|
identifier: str
|
||||||
|
title: str = '<untitled>'
|
||||||
|
creators: List[str] = field(default_factory=list)
|
||||||
|
date: Optional[date] = None
|
||||||
|
type: ResourceType = ResourceType.BOOK
|
||||||
|
description: str = ''
|
||||||
|
language: str = ''
|
||||||
|
subjects: List[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
# tag::REPR[]
|
||||||
|
def __repr__(self):
|
||||||
|
cls = self.__class__
|
||||||
|
cls_name = cls.__name__
|
||||||
|
indent = ' ' * 4
|
||||||
|
res = [f'{cls_name}('] # <1>
|
||||||
|
for f in fields(cls): # <2>
|
||||||
|
value = getattr(self, f.name) # <3>
|
||||||
|
res.append(f'{indent}{f.name} = {value!r},') # <4>
|
||||||
|
|
||||||
|
res.append(')') # <5>
|
||||||
|
return '\n'.join(res) # <6>
|
||||||
|
# end::REPR[]
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceDict(TypedDict):
|
||||||
|
identifier: str
|
||||||
|
title: str
|
||||||
|
creators: List[str]
|
||||||
|
date: Optional[date]
|
||||||
|
type: ResourceType
|
||||||
|
description: str
|
||||||
|
language: str
|
||||||
|
subjects: List[str]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
r = Resource('0')
|
||||||
|
description = 'Improving the design of existing code'
|
||||||
|
book = Resource('978-0-13-475759-9', 'Refactoring, 2nd Edition',
|
||||||
|
['Martin Fowler', 'Kent Beck'], date(2018, 11, 19),
|
||||||
|
ResourceType.BOOK, description,
|
||||||
|
'EN', ['computer programming', 'OOP'])
|
||||||
|
print(book)
|
||||||
|
book_dict: ResourceDict = {
|
||||||
|
'identifier': '978-0-13-475759-9',
|
||||||
|
'title': 'Refactoring, 2nd Edition',
|
||||||
|
'creators': ['Martin Fowler', 'Kent Beck'],
|
||||||
|
'date': date(2018, 11, 19),
|
||||||
|
'type': ResourceType.BOOK,
|
||||||
|
'description': 'Improving the design of existing code',
|
||||||
|
'language': 'EN',
|
||||||
|
'subjects': ['computer programming', 'OOP']}
|
||||||
|
book2 = Resource(**book_dict)
|
||||||
|
print(book == book2)
|
64
05-record-like/frenchdeck.doctest
Normal file
64
05-record-like/frenchdeck.doctest
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
>>> from frenchdeck import FrenchDeck, Card
|
||||||
|
>>> beer_card = Card('7', 'diamonds')
|
||||||
|
>>> beer_card
|
||||||
|
Card(rank='7', suit='diamonds')
|
||||||
|
>>> deck = FrenchDeck()
|
||||||
|
>>> len(deck)
|
||||||
|
52
|
||||||
|
>>> deck[:3]
|
||||||
|
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
|
||||||
|
>>> deck[12::13]
|
||||||
|
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]
|
||||||
|
>>> Card('Q', 'hearts') in deck
|
||||||
|
True
|
||||||
|
>>> Card('Z', 'clubs') in deck
|
||||||
|
False
|
||||||
|
>>> for card in deck: # doctest: +ELLIPSIS
|
||||||
|
... print(card)
|
||||||
|
Card(rank='2', suit='spades')
|
||||||
|
Card(rank='3', suit='spades')
|
||||||
|
Card(rank='4', suit='spades')
|
||||||
|
...
|
||||||
|
>>> for card in reversed(deck): # doctest: +ELLIPSIS
|
||||||
|
... print(card)
|
||||||
|
Card(rank='A', suit='hearts')
|
||||||
|
Card(rank='K', suit='hearts')
|
||||||
|
Card(rank='Q', suit='hearts')
|
||||||
|
...
|
||||||
|
>>> for n, card in enumerate(deck, 1): # doctest: +ELLIPSIS
|
||||||
|
... print(n, card)
|
||||||
|
1 Card(rank='2', suit='spades')
|
||||||
|
2 Card(rank='3', suit='spades')
|
||||||
|
3 Card(rank='4', suit='spades')
|
||||||
|
...
|
||||||
|
|
||||||
|
Sort with *spades high* overall ranking
|
||||||
|
|
||||||
|
# tag::SPADES_HIGH[]
|
||||||
|
>>> Card.suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0) # <1>
|
||||||
|
>>> def spades_high(card): # <2>
|
||||||
|
... rank_value = FrenchDeck.ranks.index(card.rank)
|
||||||
|
... suit_value = card.suit_values[card.suit]
|
||||||
|
... return rank_value * len(card.suit_values) + suit_value
|
||||||
|
...
|
||||||
|
>>> Card.overall_rank = spades_high # <3>
|
||||||
|
>>> lowest_card = Card('2', 'clubs')
|
||||||
|
>>> highest_card = Card('A', 'spades')
|
||||||
|
>>> lowest_card.overall_rank() # <4>
|
||||||
|
0
|
||||||
|
>>> highest_card.overall_rank()
|
||||||
|
51
|
||||||
|
|
||||||
|
# end::SPADES_HIGH[]
|
||||||
|
|
||||||
|
|
||||||
|
>>> for card in sorted(deck, key=Card.overall_rank): # doctest: +ELLIPSIS
|
||||||
|
... print(card)
|
||||||
|
Card(rank='2', suit='clubs')
|
||||||
|
Card(rank='2', suit='diamonds')
|
||||||
|
Card(rank='2', suit='hearts')
|
||||||
|
...
|
||||||
|
Card(rank='A', suit='diamonds')
|
||||||
|
Card(rank='A', suit='hearts')
|
||||||
|
Card(rank='A', suit='spades')
|
||||||
|
|
17
05-record-like/frenchdeck.py
Normal file
17
05-record-like/frenchdeck.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import collections
|
||||||
|
|
||||||
|
Card = collections.namedtuple('Card', ['rank', 'suit'])
|
||||||
|
|
||||||
|
class FrenchDeck:
|
||||||
|
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
|
||||||
|
suits = 'spades diamonds clubs hearts'.split()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._cards = [Card(rank, suit) for suit in self.suits
|
||||||
|
for rank in self.ranks]
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._cards)
|
||||||
|
|
||||||
|
def __getitem__(self, position):
|
||||||
|
return self._cards[position]
|
8
05-record-like/meaning/demo_dc.py
Normal file
8
05-record-like/meaning/demo_dc.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DemoDataClass:
|
||||||
|
|
||||||
|
a: int # <1>
|
||||||
|
b: float = 1.1 # <2>
|
||||||
|
c = 'spam' # <3>
|
7
05-record-like/meaning/demo_nt.py
Normal file
7
05-record-like/meaning/demo_nt.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import typing
|
||||||
|
|
||||||
|
class DemoNTClass(typing.NamedTuple):
|
||||||
|
|
||||||
|
a: int # <1>
|
||||||
|
b: float = 1.1 # <2>
|
||||||
|
c = 'spam' # <3>
|
5
05-record-like/meaning/demo_plain.py
Normal file
5
05-record-like/meaning/demo_plain.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class DemoPlainClass:
|
||||||
|
|
||||||
|
a: int # <1>
|
||||||
|
b: float = 1.1 # <2>
|
||||||
|
c = 'spam' # <3>
|
17
05-record-like/struct/README
Normal file
17
05-record-like/struct/README
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
To compile `metro_write.c` on MacOS 10.14 with XCode:
|
||||||
|
|
||||||
|
$ clang metro_write.c -o metro
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
$ xxd metro_areas.bin
|
||||||
|
00000000: e207 0000 546f 6b79 6f00 0000 0000 0000 ....Tokyo.......
|
||||||
|
00000010: 0000 0000 0000 0000 4a50 01e7 1158 274c ........JP...X'L
|
||||||
|
00000020: df07 0000 5368 616e 6768 6169 0000 0000 ....Shanghai....
|
||||||
|
00000030: 0100 0000 0e00 0000 434e 0000 f8a0 134c ........CN.....L
|
||||||
|
00000040: df07 0000 4a61 6b61 7274 6100 0000 0000 ....Jakarta.....
|
||||||
|
00000050: 0000 0000 0000 0000 4944 0000 bcc5 f14b ........ID.....K
|
||||||
|
$ python3 metro_read.py
|
||||||
|
2018 Tokyo, JP 43,868,228
|
||||||
|
2015 Shanghai, CN 38,700,000
|
||||||
|
2015 Jakarta, ID 31,689,592
|
BIN
05-record-like/struct/metro
Executable file
BIN
05-record-like/struct/metro
Executable file
Binary file not shown.
BIN
05-record-like/struct/metro_areas.bin
Normal file
BIN
05-record-like/struct/metro_areas.bin
Normal file
Binary file not shown.
15
05-record-like/struct/metro_read.py
Normal file
15
05-record-like/struct/metro_read.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from struct import iter_unpack
|
||||||
|
|
||||||
|
FORMAT = 'i12s2sf' # <1>
|
||||||
|
|
||||||
|
def text(field: bytes) -> str: # <2>
|
||||||
|
octets = field.split(b'\0', 1)[0] # <3>
|
||||||
|
return octets.decode('cp437') # <4>
|
||||||
|
|
||||||
|
with open('metro_areas.bin', 'rb') as fp: # <5>
|
||||||
|
data = fp.read()
|
||||||
|
|
||||||
|
for fields in iter_unpack(FORMAT, data): # <6>
|
||||||
|
year, name, country, pop = fields
|
||||||
|
place = text(name) + ', ' + text(country) # <7>
|
||||||
|
print(f'{year}\t{place}\t{pop:,.0f}')
|
46
05-record-like/struct/metro_write.c
Normal file
46
05-record-like/struct/metro_write.c
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define LEN 3
|
||||||
|
|
||||||
|
struct MetroArea {
|
||||||
|
int year;
|
||||||
|
char name[12];
|
||||||
|
char country[2];
|
||||||
|
float population;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
struct MetroArea metro[LEN];
|
||||||
|
int rank;
|
||||||
|
|
||||||
|
metro[0].year = 2018;
|
||||||
|
strcpy(metro[0].name, "Tokyo");
|
||||||
|
metro[0].country[0] = 'J';
|
||||||
|
metro[0].country[1] = 'P';
|
||||||
|
metro[0].population = 43868229.0;
|
||||||
|
|
||||||
|
metro[1].year = 2015;
|
||||||
|
strcpy(metro[1].name, "Shanghai");
|
||||||
|
metro[1].country[0] = 'C';
|
||||||
|
metro[1].country[1] = 'N';
|
||||||
|
metro[1].population = 38700000.0;
|
||||||
|
|
||||||
|
metro[2].year = 2015;
|
||||||
|
strcpy(metro[2].name, "Jakarta");
|
||||||
|
metro[2].country[0] = 'I';
|
||||||
|
metro[2].country[1] = 'D';
|
||||||
|
metro[2].population = 31689592.0;
|
||||||
|
|
||||||
|
FILE* data;
|
||||||
|
if ( (data = fopen("metro_areas.bin", "wb")) == NULL ) {
|
||||||
|
printf("Error opening file\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite(metro, sizeof(struct MetroArea), LEN, data);
|
||||||
|
fclose(data);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
22
05-record-like/typing_namedtuple/coordinates.py
Normal file
22
05-record-like/typing_namedtuple/coordinates.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"""
|
||||||
|
``Coordinate``: a simple ``NamedTuple`` subclass with a custom ``__str__``::
|
||||||
|
|
||||||
|
>>> moscow = Coordinate(55.756, 37.617)
|
||||||
|
>>> print(moscow)
|
||||||
|
55.8°N, 37.6°E
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# tag::COORDINATE[]
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
class Coordinate(NamedTuple):
|
||||||
|
|
||||||
|
lat: float
|
||||||
|
long: float
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
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[]
|
20
05-record-like/typing_namedtuple/coordinates2.py
Normal file
20
05-record-like/typing_namedtuple/coordinates2.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
"""
|
||||||
|
``Coordinate``: a simple ``NamedTuple`` subclass
|
||||||
|
|
||||||
|
This version has a field with a default value::
|
||||||
|
|
||||||
|
>>> moscow = Coordinate(55.756, 37.617)
|
||||||
|
>>> moscow
|
||||||
|
Coordinate(lat=55.756, long=37.617, reference='WGS84')
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# tag::COORDINATE[]
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
class Coordinate(NamedTuple):
|
||||||
|
|
||||||
|
lat: float # <1>
|
||||||
|
long: float
|
||||||
|
reference: str = 'WGS84' # <2>
|
||||||
|
# end::COORDINATE[]
|
9
05-record-like/typing_namedtuple/nocheck_demo.py
Normal file
9
05-record-like/typing_namedtuple/nocheck_demo.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import typing
|
||||||
|
|
||||||
|
class Coordinate(typing.NamedTuple):
|
||||||
|
|
||||||
|
lat: float
|
||||||
|
long: float
|
||||||
|
|
||||||
|
trash = Coordinate('foo', None) # <1>
|
||||||
|
print(trash)
|
Loading…
Reference in New Issue
Block a user