From 1df34f2945ad472b33847c5a7734fd3e07ca14ad Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Wed, 19 Feb 2020 00:11:45 -0300 Subject: [PATCH] ch05: new chapter --- 05-record-like/cards.doctest | 22 ++++ 05-record-like/cards.py | 9 ++ 05-record-like/cards_enum.py | 14 +++ 05-record-like/class/coordinates.py | 16 +++ 05-record-like/dataclass/club.py | 9 ++ 05-record-like/dataclass/club_generic.py | 8 ++ 05-record-like/dataclass/club_wrong.py | 9 ++ 05-record-like/dataclass/coordinates.py | 24 ++++ 05-record-like/dataclass/hackerclub.py | 50 ++++++++ .../dataclass/hackerclub_annotated.py | 51 ++++++++ 05-record-like/dataclass/resource.py | 87 ++++++++++++++ 05-record-like/dataclass/resource_repr.py | 111 ++++++++++++++++++ 05-record-like/frenchdeck.doctest | 64 ++++++++++ 05-record-like/frenchdeck.py | 17 +++ 05-record-like/meaning/demo_dc.py | 8 ++ 05-record-like/meaning/demo_nt.py | 7 ++ 05-record-like/meaning/demo_plain.py | 5 + 05-record-like/struct/README | 17 +++ 05-record-like/struct/metro | Bin 0 -> 8764 bytes 05-record-like/struct/metro_areas.bin | Bin 0 -> 72 bytes 05-record-like/struct/metro_read.py | 15 +++ 05-record-like/struct/metro_write.c | 46 ++++++++ .../typing_namedtuple/coordinates.py | 22 ++++ .../typing_namedtuple/coordinates2.py | 20 ++++ .../typing_namedtuple/nocheck_demo.py | 9 ++ 25 files changed, 640 insertions(+) create mode 100644 05-record-like/cards.doctest create mode 100644 05-record-like/cards.py create mode 100644 05-record-like/cards_enum.py create mode 100644 05-record-like/class/coordinates.py create mode 100644 05-record-like/dataclass/club.py create mode 100644 05-record-like/dataclass/club_generic.py create mode 100644 05-record-like/dataclass/club_wrong.py create mode 100644 05-record-like/dataclass/coordinates.py create mode 100644 05-record-like/dataclass/hackerclub.py create mode 100644 05-record-like/dataclass/hackerclub_annotated.py create mode 100644 05-record-like/dataclass/resource.py create mode 100644 05-record-like/dataclass/resource_repr.py create mode 100644 05-record-like/frenchdeck.doctest create mode 100644 05-record-like/frenchdeck.py create mode 100644 05-record-like/meaning/demo_dc.py create mode 100644 05-record-like/meaning/demo_nt.py create mode 100644 05-record-like/meaning/demo_plain.py create mode 100644 05-record-like/struct/README create mode 100755 05-record-like/struct/metro create mode 100644 05-record-like/struct/metro_areas.bin create mode 100644 05-record-like/struct/metro_read.py create mode 100644 05-record-like/struct/metro_write.c create mode 100644 05-record-like/typing_namedtuple/coordinates.py create mode 100644 05-record-like/typing_namedtuple/coordinates2.py create mode 100644 05-record-like/typing_namedtuple/nocheck_demo.py diff --git a/05-record-like/cards.doctest b/05-record-like/cards.doctest new file mode 100644 index 0000000..f0e2f22 --- /dev/null +++ b/05-record-like/cards.doctest @@ -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) diff --git a/05-record-like/cards.py b/05-record-like/cards.py new file mode 100644 index 0000000..808514a --- /dev/null +++ b/05-record-like/cards.py @@ -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() diff --git a/05-record-like/cards_enum.py b/05-record-like/cards_enum.py new file mode 100644 index 0000000..f67a1eb --- /dev/null +++ b/05-record-like/cards_enum.py @@ -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]}' diff --git a/05-record-like/class/coordinates.py b/05-record-like/class/coordinates.py new file mode 100644 index 0000000..9186c1b --- /dev/null +++ b/05-record-like/class/coordinates.py @@ -0,0 +1,16 @@ +""" +``Coordinate``: a simple class with a custom ``__str__``:: + + >>> moscow = Coordinate(55.756, 37.617) + >>> print(moscow) # doctest:+ELLIPSIS + +""" + +# tag::COORDINATE[] +class Coordinate: + + def __init__(self, lat, long): + self.lat = lat + self.long = long + +# end::COORDINATE[] \ No newline at end of file diff --git a/05-record-like/dataclass/club.py b/05-record-like/dataclass/club.py new file mode 100644 index 0000000..cd8ff46 --- /dev/null +++ b/05-record-like/dataclass/club.py @@ -0,0 +1,9 @@ +from dataclasses import dataclass, field + + +@dataclass +class ClubMember: + + name: str + guests: list = field(default_factory=list) + diff --git a/05-record-like/dataclass/club_generic.py b/05-record-like/dataclass/club_generic.py new file mode 100644 index 0000000..41523f0 --- /dev/null +++ b/05-record-like/dataclass/club_generic.py @@ -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> diff --git a/05-record-like/dataclass/club_wrong.py b/05-record-like/dataclass/club_wrong.py new file mode 100644 index 0000000..3d73d6a --- /dev/null +++ b/05-record-like/dataclass/club_wrong.py @@ -0,0 +1,9 @@ +from dataclasses import dataclass + +# tag::CLUBMEMBER[] +@dataclass +class ClubMember: + + name: str + guests: list = [] +# end::CLUBMEMBER[] diff --git a/05-record-like/dataclass/coordinates.py b/05-record-like/dataclass/coordinates.py new file mode 100644 index 0000000..55fedbd --- /dev/null +++ b/05-record-like/dataclass/coordinates.py @@ -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[] \ No newline at end of file diff --git a/05-record-like/dataclass/hackerclub.py b/05-record-like/dataclass/hackerclub.py new file mode 100644 index 0000000..8ab2a05 --- /dev/null +++ b/05-record-like/dataclass/hackerclub.py @@ -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[] diff --git a/05-record-like/dataclass/hackerclub_annotated.py b/05-record-like/dataclass/hackerclub_annotated.py new file mode 100644 index 0000000..a25a443 --- /dev/null +++ b/05-record-like/dataclass/hackerclub_annotated.py @@ -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[] diff --git a/05-record-like/dataclass/resource.py b/05-record-like/dataclass/resource.py new file mode 100644 index 0000000..b768f09 --- /dev/null +++ b/05-record-like/dataclass/resource.py @@ -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='', creators=[], date=None, + type=, 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=, 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 = '' # <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) diff --git a/05-record-like/dataclass/resource_repr.py b/05-record-like/dataclass/resource_repr.py new file mode 100644 index 0000000..ee0a731 --- /dev/null +++ b/05-record-like/dataclass/resource_repr.py @@ -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 = '', + creators = [], + date = None, + type = , + 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 = , + 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 = '' + 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) diff --git a/05-record-like/frenchdeck.doctest b/05-record-like/frenchdeck.doctest new file mode 100644 index 0000000..0257a7f --- /dev/null +++ b/05-record-like/frenchdeck.doctest @@ -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') + diff --git a/05-record-like/frenchdeck.py b/05-record-like/frenchdeck.py new file mode 100644 index 0000000..47a0d05 --- /dev/null +++ b/05-record-like/frenchdeck.py @@ -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] diff --git a/05-record-like/meaning/demo_dc.py b/05-record-like/meaning/demo_dc.py new file mode 100644 index 0000000..3cc45ce --- /dev/null +++ b/05-record-like/meaning/demo_dc.py @@ -0,0 +1,8 @@ +from dataclasses import dataclass + +@dataclass +class DemoDataClass: + + a: int # <1> + b: float = 1.1 # <2> + c = 'spam' # <3> diff --git a/05-record-like/meaning/demo_nt.py b/05-record-like/meaning/demo_nt.py new file mode 100644 index 0000000..317fb82 --- /dev/null +++ b/05-record-like/meaning/demo_nt.py @@ -0,0 +1,7 @@ +import typing + +class DemoNTClass(typing.NamedTuple): + + a: int # <1> + b: float = 1.1 # <2> + c = 'spam' # <3> diff --git a/05-record-like/meaning/demo_plain.py b/05-record-like/meaning/demo_plain.py new file mode 100644 index 0000000..6376959 --- /dev/null +++ b/05-record-like/meaning/demo_plain.py @@ -0,0 +1,5 @@ +class DemoPlainClass: + + a: int # <1> + b: float = 1.1 # <2> + c = 'spam' # <3> diff --git a/05-record-like/struct/README b/05-record-like/struct/README new file mode 100644 index 0000000..65e16f2 --- /dev/null +++ b/05-record-like/struct/README @@ -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 diff --git a/05-record-like/struct/metro b/05-record-like/struct/metro new file mode 100755 index 0000000000000000000000000000000000000000..69a29133a0771710a7d1fb6b0ca83b2187a0261c GIT binary patch literal 8764 zcmeHNU1(fI7@chrZS2n`C=&l*TaucVl0>l(iwQ~1r8n(1i6*H*s*{_|Cc9>{8}4pM z@=!<#3ogq--t@7L1q%lBp+c)|tx3^`qE8j7hG1n27Fy~eCl(h*PC-BPB)^Qls-o3CpRUIyJOc2dOErEr49 zawugoH9M9pHo5s8?6vqB?Fa`u19sYcrToYzBjMOg*hG+Zp)PgnvF?BE6mi$7A zc(U}uZoW~QZ?{cAT)|f)wj3!#$&?w5PulHnzL?F|Y4;PCo10t8+4x*EJ}#s2i39@O zd>3s#t|1z+o5Sj{o?IvG5J<|N?xF6o9;^p7rOrBW)m$f7!>VwUl<}BME=-RlVsa*B zZp-J;aJ_N7W1LTCPT3~qWTG?+Tl1^Kc;>g0!7ySspVP;FDPzXJ<=0^IwJ{h*T+YY$ ztyMkNqx3xu_V%6BdU~DjSiq$lFzpR{63C-;Rjoqo$KL_#`&e}WM^Ilt z%{bCDpsWKwh5GV75TM?LGJsNUjAbd;p}e2|BF4LxoC?9$Py|r8&Rb`bW@{`u);hir z8waMnfWkcbLP!7n`QR%XS2{n=epDOjcol5{6d#J%iwsfdD=n+8`wqc}v_)%13gqG6 zjdJ7XQU~_&9ej^}ttM+R7vwXzkW7WATaL9Lujm+pU-5k_dy%41AFEv;3?oK z;3?oK;3@F`RA5-o+||$hzJly8agUI>uIEmz1hX}tVV7_!D}otqb4|O*m&=^Jpl4>! zuW2hA*mdbk*Q)i*Ej@SSF7_k&5Af*zf($*GZy)*VYQ96ms&@6ns&;MQk-uKMv8LVp zh2ubvu??m1pCD4tXdmVexH*;-$L%*)wIBP)ac50iFXLz{am-j8i}?*?z>n`yLm%|6 zY7cuj^bbAPG033YkuxebJzppk@?RkwpS`RvZ3=%nK$Pp~M8Jy3e+qQhf`Vjo^zu{aw|df}#QngIkm)mM$!Zt%?4BT#$=2%G z;Z6PZ?=$HU^!U@UkK?VD6>+ z2<{#%H2s8nnfqOF?>8_5w$A4;piD)d$ zy){x3FQU-OYrYxxtFNJEdC1K&j6P-FjgfU~G+RrKLfbqVAt67n#je}!`j}k@?fSG` zZ$slXdkT08cnWw5cnWw5cnWw5cnWw5cnWw5cnWw5{09`M-`{YWY1DT~UWFT>DH)1P z$;ny692akyb<176WK);q5{DDxxYS(dC-FOg9dbG%!}HKK0 z(!@iMDslCBJKS;9sgI90aMpRov#9+z*l>O)z;hHp9(5>d!LuB9yKuh?zvII1yYPey z$6c8D;Ucm48wcLU@_w`y1=hv$o9$u(xJBNohg5o0%%+-?iWX~rRa0?-oT;%V#hDIg JBDSmp@i+LF;Jg3; literal 0 HcmV?d00001 diff --git a/05-record-like/struct/metro_areas.bin b/05-record-like/struct/metro_areas.bin new file mode 100644 index 0000000000000000000000000000000000000000..1e738222c9d003f54adb49c2222f45f05d8ba747 GIT binary patch literal 72 zcmaFF&cG0opIw>HaFmsifq}s*fPq0ULfz*+P&_yzF)uwMF%u}v;Oxi1@MD26SllZy UJF%!F5hTpu>GJF4o}(YV0h6{AqW}N^ literal 0 HcmV?d00001 diff --git a/05-record-like/struct/metro_read.py b/05-record-like/struct/metro_read.py new file mode 100644 index 0000000..22f315a --- /dev/null +++ b/05-record-like/struct/metro_read.py @@ -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}') diff --git a/05-record-like/struct/metro_write.c b/05-record-like/struct/metro_write.c new file mode 100644 index 0000000..f179370 --- /dev/null +++ b/05-record-like/struct/metro_write.c @@ -0,0 +1,46 @@ +#include +#include +#include + +#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; +} diff --git a/05-record-like/typing_namedtuple/coordinates.py b/05-record-like/typing_namedtuple/coordinates.py new file mode 100644 index 0000000..a3d7401 --- /dev/null +++ b/05-record-like/typing_namedtuple/coordinates.py @@ -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[] \ No newline at end of file diff --git a/05-record-like/typing_namedtuple/coordinates2.py b/05-record-like/typing_namedtuple/coordinates2.py new file mode 100644 index 0000000..a251429 --- /dev/null +++ b/05-record-like/typing_namedtuple/coordinates2.py @@ -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[] \ No newline at end of file diff --git a/05-record-like/typing_namedtuple/nocheck_demo.py b/05-record-like/typing_namedtuple/nocheck_demo.py new file mode 100644 index 0000000..a8b5ed4 --- /dev/null +++ b/05-record-like/typing_namedtuple/nocheck_demo.py @@ -0,0 +1,9 @@ +import typing + +class Coordinate(typing.NamedTuple): + + lat: float + long: float + +trash = Coordinate('foo', None) # <1> +print(trash)