diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index f34d338..daed35d 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: node-version: 18 diff --git a/Gemfile b/Gemfile index dc25152..0ff18f4 100644 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,6 @@ source "https://rubygems.org" ruby '3.3.9' -gem 'asciidoctor', '2.0.23' -gem 'asciidoctor-pdf', '2.3.19' +gem 'asciidoctor', '2.0.26' +gem 'asciidoctor-pdf', '2.3.24' gem 'rouge', '4.6.0' diff --git a/Gemfile.lock b/Gemfile.lock index 6216d91..58fc3b6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,10 +5,10 @@ GEM addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) afm (1.0.0) - asciidoctor (2.0.23) - asciidoctor-pdf (2.3.19) + asciidoctor (2.0.26) + asciidoctor-pdf (2.3.24) asciidoctor (~> 2.0) - concurrent-ruby (~> 1.1) + concurrent-ruby (~> 1.3) matrix (~> 0.4) prawn (~> 2.4.0) prawn-icon (~> 3.0.0) @@ -46,10 +46,10 @@ GEM pdf-reader (~> 2.0) prawn (~> 2.2) public_suffix (6.0.2) - rexml (3.4.2) + rexml (3.4.4) rouge (4.6.0) ruby-rc4 (0.1.5) - treetop (1.6.14) + treetop (1.6.18) polyglot (~> 0.3) ttfunk (1.7.0) @@ -58,8 +58,8 @@ PLATFORMS x86_64-darwin-24 DEPENDENCIES - asciidoctor (= 2.0.23) - asciidoctor-pdf (= 2.3.19) + asciidoctor (= 2.0.26) + asciidoctor-pdf (= 2.3.24) rouge (= 4.6.0) RUBY VERSION diff --git a/atributos-pt_BR.adoc b/atributos-pt_BR.adoc index c2694e4..ba05309 100644 --- a/atributos-pt_BR.adoc +++ b/atributos-pt_BR.adoc @@ -27,5 +27,8 @@ :last-update-label: HTML gerado em :version-label: Versão :colophon: Copyright -// Substituições +// Substituições :dunder: __ +:rt-arrow: -> +:lte: <= +:iadd: += diff --git a/code/10-dp-1class-func/strategy.py b/code/10-dp-1class-func/strategy.py index 89a93ba..139ef40 100644 --- a/code/10-dp-1class-func/strategy.py +++ b/code/10-dp-1class-func/strategy.py @@ -33,7 +33,6 @@ from decimal import Decimal from typing import Optional, Callable, NamedTuple - class Customer(NamedTuple): name: str fidelity: int @@ -46,6 +45,7 @@ class LineItem(NamedTuple): def total(self): return self.price * self.quantity + @dataclass(frozen=True) class Order: # the Context @@ -67,10 +67,8 @@ def due(self) -> Decimal: def __repr__(self): return f'' - # <3> - def fidelity_promo(order: Order) -> Decimal: # <4> """5% discount for customers with 1000 or more fidelity points""" if order.customer.fidelity >= 1000: @@ -86,13 +84,10 @@ def bulk_item_promo(order: Order) -> Decimal: discount += item.total() * Decimal('0.1') return discount - def large_order_promo(order: Order) -> Decimal: """7% discount for orders with 10 or more distinct items""" distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: return order.total() * Decimal('0.07') return Decimal(0) - - # end::STRATEGY[] diff --git a/code/10-dp-1class-func/strategy_best4.py b/code/10-dp-1class-func/strategy_best4.py index 8e124bb..72a9d3c 100644 --- a/code/10-dp-1class-func/strategy_best4.py +++ b/code/10-dp-1class-func/strategy_best4.py @@ -45,17 +45,14 @@ from strategy import Order # tag::STRATEGY_BEST4[] - Promotion = Callable[[Order], Decimal] promos: list[Promotion] = [] # <1> - def promotion(promo: Promotion) -> Promotion: # <2> promos.append(promo) return promo - def best_promo(order: Order) -> Decimal: """Compute the best discount available""" return max(promo(order) for promo in promos) # <3> @@ -68,7 +65,6 @@ def fidelity(order: Order) -> Decimal: return order.total() * Decimal('0.05') return Decimal(0) - @promotion def bulk_item(order: Order) -> Decimal: """10% discount for each LineItem with 20 or more units""" @@ -78,7 +74,6 @@ def bulk_item(order: Order) -> Decimal: discount += item.total() * Decimal('0.1') return discount - @promotion def large_order(order: Order) -> Decimal: """7% discount for orders with 10 or more distinct items""" @@ -86,5 +81,4 @@ def large_order(order: Order) -> Decimal: if len(distinct_items) >= 10: return order.total() * Decimal('0.07') return Decimal(0) - # end::STRATEGY_BEST4[] diff --git a/code/12-seq-hacking/vector_v1.py b/code/12-seq-hacking/vector_v1.py index f52dcbc..46a06bd 100644 --- a/code/12-seq-hacking/vector_v1.py +++ b/code/12-seq-hacking/vector_v1.py @@ -87,7 +87,6 @@ import reprlib import math - class Vector: typecode = 'd' diff --git a/code/12-seq-hacking/vector_v2.py b/code/12-seq-hacking/vector_v2.py index 4d71b8f..98ea9db 100644 --- a/code/12-seq-hacking/vector_v2.py +++ b/code/12-seq-hacking/vector_v2.py @@ -148,6 +148,7 @@ def __bool__(self): # tag::VECTOR_V2[] def __len__(self): return len(self._components) + def __getitem__(self, key): if isinstance(key, slice): # <1> diff --git a/code/13-protocol-abc/tombola.py b/code/13-protocol-abc/tombola.py index 50f19fc..ed62706 100644 --- a/code/13-protocol-abc/tombola.py +++ b/code/13-protocol-abc/tombola.py @@ -12,11 +12,11 @@ def load(self, iterable): # <2> def pick(self): # <3> """Remove item at random, returning it. - This method should raise `LookupError` when the instance is empty. + Must raise LookupError when the instance is empty. """ def loaded(self): # <4> - """Return `True` if there's at least 1 item, `False` otherwise.""" + """True if there's at least 1 item, otherwise False.""" return bool(self.inspect()) # <5> def inspect(self): diff --git a/code/13-protocol-abc/typing/randompick_test.py b/code/13-protocol-abc/typing/randompick_test.py index 115c4d3..be7a6de 100644 --- a/code/13-protocol-abc/typing/randompick_test.py +++ b/code/13-protocol-abc/typing/randompick_test.py @@ -3,6 +3,7 @@ from randompick import RandomPicker # <1> + class SimplePicker: # <2> def __init__(self, items: Iterable) -> None: self._items = list(items) @@ -11,6 +12,7 @@ def __init__(self, items: Iterable) -> None: def pick(self) -> Any: # <3> return self._items.pop() + def test_isinstance() -> None: # <4> popper: RandomPicker = SimplePicker([1]) # <5> assert isinstance(popper, RandomPicker) # <6> diff --git a/code/13-protocol-abc/typing/vector2d_v4.py b/code/13-protocol-abc/typing/vector2d_v4.py index 2960633..15de067 100644 --- a/code/13-protocol-abc/typing/vector2d_v4.py +++ b/code/13-protocol-abc/typing/vector2d_v4.py @@ -167,6 +167,6 @@ def __complex__(self): return complex(self.x, self.y) @classmethod - def fromcomplex(cls, datum): - return cls(datum.real, datum.imag) # <1> + def fromcomplex(cls, n): + return cls(n.real, n.imag) # <1> # end::VECTOR2D_V4_COMPLEX[] diff --git a/code/13-protocol-abc/typing/vector2d_v5.py b/code/13-protocol-abc/typing/vector2d_v5.py index bd7be4c..f9dbeae 100644 --- a/code/13-protocol-abc/typing/vector2d_v5.py +++ b/code/13-protocol-abc/typing/vector2d_v5.py @@ -168,7 +168,7 @@ def __complex__(self) -> complex: # <2> return complex(self.x, self.y) @classmethod - def fromcomplex(cls, datum: SupportsComplex) -> Vector2d: # <3> - c = complex(datum) # <4> + def fromcomplex(cls, n: complex | SupportsComplex) -> Vector2d: # <3> + c = complex(n) # <4> return cls(c.real, c.imag) # end::VECTOR2D_V5_COMPLEX[] diff --git a/code/15-more-types/cafeteria/covariant.py b/code/15-more-types/cafeteria/covariant.py index 768fe2d..c4ca6f3 100644 --- a/code/15-more-types/cafeteria/covariant.py +++ b/code/15-more-types/cafeteria/covariant.py @@ -16,7 +16,6 @@ class OrangeJuice(Juice): # tag::BEVERAGE_TYPES[] T_co = TypeVar('T_co', covariant=True) # <1> - class BeverageDispenser(Generic[T_co]): # <2> def __init__(self, beverage: T_co) -> None: self.beverage = beverage @@ -33,7 +32,6 @@ def install(dispenser: BeverageDispenser[Juice]) -> None: # <3> # tag::INSTALL_JUICE_DISPENSERS[] juice_dispenser = BeverageDispenser(Juice()) install(juice_dispenser) - orange_juice_dispenser = BeverageDispenser(OrangeJuice()) install(orange_juice_dispenser) # end::INSTALL_JUICE_DISPENSERS[] diff --git a/code/18-with-match/lispy/py3.10/lis.py b/code/18-with-match/lispy/py3.10/lis.py index f2d982c..dd73108 100755 --- a/code/18-with-match/lispy/py3.10/lis.py +++ b/code/18-with-match/lispy/py3.10/lis.py @@ -177,7 +177,10 @@ class Procedure: "A user-defined Scheme procedure." def __init__( # <1> - self, parms: list[Symbol], body: list[Expression], env: Environment + self, + parms: list[Symbol], + body: list[Expression], + env: Environment ): self.parms = parms # <2> self.body = body diff --git a/ferramentas/toc-pt-br.txt b/ferramentas/toc-pt-br.txt index 3cea603..7f719ae 100644 --- a/ferramentas/toc-pt-br.txt +++ b/ferramentas/toc-pt-br.txt @@ -199,7 +199,7 @@ Parte IV: Controle de fluxo 17.8. Um gerador de progressão aritmética 17.9. Funções geradoras na biblioteca padrão 17.10. Funções de redução de iteráveis - 17.11. Subgeradoras com yield from + 17.11. Subgeradores com yield from 17.12. Tipos iteráveis genéricos 17.13. Corrotinas clássicas 17.14. Resumo do capítulo diff --git a/guia-de-estilo.adoc b/guia-de-estilo.adoc index aa539f6..624ff15 100644 --- a/guia-de-estilo.adoc +++ b/guia-de-estilo.adoc @@ -59,6 +59,30 @@ Exemplo de `cap06.adoc`: ++++ +### Formatação de links externos + +O título do recurso externo em língua estrangeira deve estar em itálico, assim: + +https://fpy.li/15-47[_Programming TypeScript_] + +Quando for interessante traduzir o título da página de destino, +a tradução deverá aparecer depois e fora do link, entre parêntesis, assim: + +https://fpy.li/15-51[_Generics Considered Harmful_] (Genéricos considerados nocivos_) + +O título original pode ficar em Title Case, a tradução deve começar com maíuscula e +usar maiúscula só para siglas e nomes próprios. + +O tradutor usou esta convenção: + +https://fpy.li/15-51["Generics Considered Harmful" (_Genéricos Considerados Nocivos_)] + +Para transformar, no VS Code esta expressão regular funciona: + +---- +DE: "([^"]+)" \(_([^)]+)_\)\] +PARA: _$1_] ($2) +---- ## Termos adotados @@ -89,7 +113,7 @@ Termo em português adotado:: embutido _(built-in)_ Termo em inglês adotado:: - _o cluster_ (agrupamento) + _cluster_ (agrupamento) **Legenda** @@ -105,7 +129,7 @@ Termo em inglês adotado:: [cols="3,3,4"] |=== |🇺🇸|🇧🇷|Observações -|❗ _abstract base class_ |✅ classe base abstrata| plural: classes bases abstratas +|❗ _abstract base class_ |✅ classe base abstrata| plural: classes base abstratas |✅ _alias_ (s.m.) |❗ apelido | |✅ _aliasing_ (s.m.) |❗ apelidamento | |✅ _array_ (s.m.) |❗ arranjo | 🔎 a array @@ -130,6 +154,7 @@ Termo em inglês adotado:: |❗ _evaluate_ (verb) |✅ avaliar | contraste com "analisar" (_parse_) |❗ _evaluation_ |✅ avaliação | |❗ _factory_ |✅ fábrica | +|❗ _forward reference_ |✅ referência futura | 🔎 referência adiantada |✅ _framework_ (s.m.) |❗ arcabouço | 🔎 a framework |❗ _Further Reading_ |✅ Para saber mais| título de seção (note: apenas 1ª letra maiúscula) |✅ _future_ (s.m.) |❓ | @@ -141,11 +166,13 @@ Termo em inglês adotado:: |❗ _in place_ | ✅ internamente, interno | 🔎 no mesmo lugar |❗ _keyword argument_ | ✅ argumento nomeado | 🔎 argumento de palavra-chave |❗ _lock_ | ✅ trava | +|❗ _loop_ | ✅ laço | ex: laço `for`, laço de eventos, laço infinito |❗ _match_ (s.m.) | ✅ casamento | substantivo; aplica-se a `match/case` e `re.match` |❗ _match_ (v.) | ✅ casar | verbo; aplica-se a `match/case` e `re.match` +|✅ MRO (s.m.) | Ordem de Resolução de Métodos (_Method Resolution Order_) |✅ _offset_ (s.m.) | ❗ deslocamento | 🔎 a offset |❗ _override_ | ✅ sobrescrever | 🔎 sobrepor -|❗ _overload_ | ✅ sobrecarregar | +|❗ _overload_ | ✅ sobrecarregar | 🔎 sobrepor |❗ _overloading_ | ✅ sobrecarga | |❗ _overloaded signatures_ |✅ assinaturas sobrecarregadas| |❗ _parse_ |✅ analise (ou análise sintática) | contraste com "avaliar" (_evaluate_) @@ -166,6 +193,7 @@ Termo em inglês adotado:: |❗ _statement_ |✅ instrução | |✅ _status_ (s.m.) |❗ situação | |❗ _subject_ |✅ sujeito | no contexto de _pattern matching_ +|✅ _template_ (s.m.) |❗ gabarito | "o template" |✅ _thread_ (s.f.) |❓ | "a thread" |❗ _tuple_ |✅ tupla | usar 🇧🇷 exceto menção específica à classe `tuple` |❗ _type checker_ |✅ checador de tipos| 🔎 verificador de tipos(s) diff --git a/links/FPY.LI.htaccess b/links/FPY.LI.htaccess index 3f5fe27..8c80142 100644 --- a/links/FPY.LI.htaccess +++ b/links/FPY.LI.htaccess @@ -467,7 +467,7 @@ RedirectTemp /11-9 https://docs.python.org/3/tutorial/modules.html#more-on-modul RedirectTemp /11-10 https://docs.python.org/3/library/gettext.html#gettext.NullTranslations RedirectTemp /11-11 https://github.com/fluentpython/example-code-2e/blob/master/11-pythonic-obj/mem_test.py RedirectTemp /11-12 https://docs.python.org/3/reference/datamodel.html#basic-customization -RedirectTemp /11-13 http://esug.org/data/HistoricalDocuments/TheSmalltalkReport/ST07/04wo.pdf +RedirectTemp /11-13 https://rmod-files.lille.inria.fr/FreeBooks/TheSmalltalkReport/PDFS/ST/ST07/04WO.PDF RedirectTemp /11-14 https://www.artima.com/articles/the-simplest-thing-that-could-possibly-work#part3 RedirectTemp /11-15 https://docs.oracle.com/javase/tutorial/essential/environment/security.html RedirectTemp /11-16 https://github.com/fluentpython/example-code-2e/blob/master/11-pythonic-obj/private/Expose.java @@ -777,7 +777,7 @@ RedirectTemp /18-46 https://github.com/fluentpython/example-code-2e/blob/master/ RedirectTemp /18-47 https://github.com/fluentpython/example-code-2e/blob/master/18-with-match/lispy/original/lispy.py RedirectTemp /18-48 http://neopythonic.blogspot.com/2009/04/final-words-on-tail-calls.html RedirectTemp /18-49 https://webkit.org/blog/6240/ecmascript-6-proper-tail-calls-in-webkit/ -RedirectTemp /18-50 http://kangax.github.io/compat-table/es6/ +RedirectTemp /18-50 https://compat-table.github.io/compat-table/es6/ RedirectTemp /18-51 https://world.hey.com/mgmarlow/what-happened-to-proper-tail-calls-in-javascript-5494c256 RedirectTemp /18-52 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html RedirectTemp /18-53 https://github.com/fluentpython/lispy/blob/main/mylis/mylis_2/lis.py @@ -1111,7 +1111,7 @@ RedirectTemp /24 https://pythonfluente.com/2/#ch_class_metaprog RedirectTemp /25 https://pythonfluente.com/2/#virtual_subclass_sec RedirectTemp /26 https://pythonfluente.com/2/#environment_class_ex RedirectTemp /27 https://pythonfluente.com/2/#subclass_builtin_woes -RedirectTemp /28 https://pythonfluente.com/2/#slots_section +RedirectTemp /28 https://pythonfluente.com/2/#slots_sec RedirectTemp /29 https://pythonfluente.com/2/#typeddict_sec RedirectTemp /2a https://pythonfluente.com/2/#problems_annot_runtime_sec RedirectTemp /2b https://pythonfluente.com/2/#legacy_deprecated_typing_box @@ -1197,7 +1197,7 @@ RedirectTemp /4e https://docs.python.org/pt-br/3/library/statistics.html#statist RedirectTemp /4f https://docs.python.org/pt-br/3/library/typing.html#typing.Callable # Prefácio: appended 2025-08-23 19:10:25 -RedirectTemp /4g https://docs.python.org/pt-br/3.10/tutorial/ +RedirectTemp /4g https://docs.python.org/pt-br/3/tutorial/ RedirectTemp /4h https://penseallen.github.io/PensePython2e/ RedirectTemp /4j https://creativecommons.org/licenses/by-nc-nd/4.0/deed.pt_BR RedirectTemp /4k https://github.com/pythonfluente/pythonfluente2e @@ -1288,3 +1288,189 @@ RedirectTemp /6r https://docs.python.org/pt-br/3/library/abc.html RedirectTemp /6s https://docs.python.org/pt-br/dev/library/abc.html#abc.abstractmethod RedirectTemp /6t https://docs.python.org/pt-br/3/library/os.html#os.urandom RedirectTemp /6v https://pt.wikipedia.org/wiki/Princ%C3%ADpio_da_segrega%C3%A7%C3%A3o_de_interface + +# cap13: appended 2025-11-13 14:52:00 +RedirectTemp /6v https://docs.python.org/pt-br/3.14/library/typing.html#protocols + +# cap14: appended 2025-11-17 20:07:32 +RedirectTemp /6w https://docs.python.org/pt-br/3/tutorial/classes.html +RedirectTemp /6x https://docs.python.org/pt-br/3/library/collections.html#ordereddict-examples-and-recipes +RedirectTemp /6y https://pt.wikipedia.org/wiki/Busca_em_largura +RedirectTemp /6z https://docs.python.org/pt-br/3/library/collections.abc.html +RedirectTemp /72 https://docs.python.org/pt-br/3/library/http.server.html +RedirectTemp /73 https://docs.python.org/pt-br/3/library/socketserver.html#socketserver.ForkingMixIn +RedirectTemp /74 https://docs.python.org/pt-br/3/library/os.html#os.fork +RedirectTemp /75 https://pt.wikipedia.org/wiki/Template_Method +RedirectTemp /76 https://docs.python.org/pt-br/3/library/tkinter.html +RedirectTemp /77 https://docs.python.org/pt-br/3/library/tkinter.ttk.html +RedirectTemp /78 https://docs.python.org/pt-br/3/library/socketserver.html +RedirectTemp /79 https://docs.python.org/pt-br/3/library/socketserver.html#socketserver.BaseServer +RedirectTemp /7a https://docs.python.org/pt-br/3/library/typing.html#typing.final +RedirectTemp /7b https://pt.wikipedia.org/wiki/Polimorfismo_(ci%C3%AAncia_da_computa%C3%A7%C3%A3o) +RedirectTemp /7c https://pt.wikipedia.org/wiki/POSIX +RedirectTemp /7d https://docs.python.org/pt-br/3/library/typing.html#typing.Final +RedirectTemp /7e https://github.com/python/cpython/issues/141721 + +# cap15: appended 2025-11-25 13:14:54 +RedirectTemp /7f https://docs.python.org/pt-br/3/library/xml.etree.elementtree.html +RedirectTemp /7g https://docs.python.org/pt-br/3/library/asyncio-stream.html#tcp-echo-server-using-streams +RedirectTemp /7h https://docs.python.org/pt-br/3/library/typing.html#introspection-helpers +RedirectTemp /7j https://docs.python.org/pt-br/3/howto/annotations.html +RedirectTemp /7k https://docs.python.org/pt-br/3/library/typing.html#user-defined-generic-types +RedirectTemp /7m https://docs.python.org/pt-br/3/library/typing.html#typing.FrozenSet + +# cap16: appended 2025-11-25 13:35:49 +RedirectTemp /7n https://docs.python.org/pt-br/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations +RedirectTemp /7p https://pt.wikipedia.org/wiki/Complemento_para_dois +RedirectTemp /7q https://docs.python.org/pt-br/3/library/functools.html#functools.total_ordering + +# cap16: appended 2025-12-02 14:22:00 +RedirectTemp /7r https://docs.python.org/pt-br/3/library/numbers.html#implementing-the-arithmetic-operations + +# vol2, xrefs to secs in other vols, appended 2025-12-08 12:03:33 +RedirectTemp /7s https://pythonfluente.com/2/#anatomy_of_classes_sec +RedirectTemp /7t https://pythonfluente.com/2/#arbitrary_arguments_sec +RedirectTemp /7v https://pythonfluente.com/2/#arrays_sec +RedirectTemp /7w https://pythonfluente.com/2/#bounded_typevar_sec +RedirectTemp /7x https://pythonfluente.com/2/#caching_properties_sec +RedirectTemp /7y https://pythonfluente.com/2/#callable_variance_sec +RedirectTemp /7z https://pythonfluente.com/2/#classic_coroutines_sec +RedirectTemp /82 https://pythonfluente.com/2/#conseq_dict_internal_sec +RedirectTemp /83 https://pythonfluente.com/2/#consistent_with_sec +RedirectTemp /84 https://pythonfluente.com/2/#data_model_emulating_sec +RedirectTemp /85 https://pythonfluente.com/2/#dataclass_further_sec +RedirectTemp /86 https://pythonfluente.com/2/#del_sec +RedirectTemp /87 https://pythonfluente.com/2/#generic_classic_coroutine_types_sec +RedirectTemp /88 https://pythonfluente.com/2/#inconsistent_missing_sec +RedirectTemp /89 https://pythonfluente.com/2/#iter_func_sec +RedirectTemp /8a https://pythonfluente.com/2/#keyword_class_patterns_sec +RedirectTemp /8b https://pythonfluente.com/2/#map_filter_reduce_sec +RedirectTemp /8c https://pythonfluente.com/2/#mapping_type_sec +RedirectTemp /8d https://pythonfluente.com/2/#memoryview_sec +RedirectTemp /8e https://pythonfluente.com/2/#methods_are_descriptors_sec +RedirectTemp /8f https://pythonfluente.com/2/#noreturn_sec +RedirectTemp /8g https://pythonfluente.com/2/#numeric_tower_warning_sec +RedirectTemp /8h https://pythonfluente.com/2/#numpy_sec +RedirectTemp /8j https://pythonfluente.com/2/#operator_module_sec +RedirectTemp /8k https://pythonfluente.com/2/#prop_validation_sec +RedirectTemp /8m https://pythonfluente.com/2/#protocols_in_fn_sec +RedirectTemp /8n https://pythonfluente.com/2/#set_ops_sec +RedirectTemp /8p https://pythonfluente.com/2/#slice_objects_sec +RedirectTemp /8q https://pythonfluente.com/2/#stdlib_generators_sec +RedirectTemp /8r https://pythonfluente.com/2/#type_hint_abc_sec +RedirectTemp /8s https://pythonfluente.com/2/#types_defined_by_ops_sec +RedirectTemp /8t https://pythonfluente.com/2/#what_is_hashable_sec +RedirectTemp /8v https://pythonfluente.com/2/#dc_main_features_sec +RedirectTemp /8w https://pythonfluente.com/2/#ex_vector2d +RedirectTemp /8x https://pythonfluente.com/2/#ex_pythonic_deck +RedirectTemp /8y https://pythonfluente.com/2/#ex_bingo_callable +RedirectTemp /8z https://pythonfluente.com/2/#defensive_argument_sec +RedirectTemp /92 https://pythonfluente.com/2/#ex_top_protocol_test +RedirectTemp /93 https://pythonfluente.com/2/#ex_strkeydict +RedirectTemp /94 https://pythonfluente.com/2/#ex_tcp_mojifinder_main +RedirectTemp /95 https://pythonfluente.com/2/#ex_checked_class_top +RedirectTemp /96 https://pythonfluente.com/2/#ex_primes_procs_top + +# cap 17: appended 2026-01-09 15:15:31 +RedirectTemp /97 https://docs.python.org/pt-br/3.10/library/functions.html#iter +RedirectTemp /98 https://github.com/python/python-docs-pt-br/issues/290 +RedirectTemp /99 https://docs.python.org/pt-br/3/glossary.html +RedirectTemp /9a https://docs.python.org/pt-br/3/glossary.html#term-generator-iterator +RedirectTemp /9b https://docs.python.org/pt-br/3/glossary.html#term-generator-expression +RedirectTemp /9c https://docs.python.org/pt-br/3/library/itertools.html +RedirectTemp /9d https://docs.python.org/pt-br/3/library/exceptions.html#exception-hierarchy +RedirectTemp /9e https://pt.wikipedia.org/wiki/Busca_em_profundidade +RedirectTemp /9f https://docs.python.org/pt-br/3/library/typing.html#typing.Generator +RedirectTemp /9g https://docs.python.org/pt-br/3/reference/expressions.html#yieldexpr +RedirectTemp /9h https://docs.python.org/pt-br/3/library/itertools.html#itertools-recipes +RedirectTemp /9j https://pt.wikipedia.org/wiki/Jogo_da_vida +RedirectTemp /9k https://github.com/fluentpython/language-creators +RedirectTemp /9m https://docs.python.org/pt-br/3/library/exceptions.html#StopIteration +RedirectTemp /9n https://docs.python.org/pt-br/3/reference/expressions.html#yield-expressions +RedirectTemp /9p https://docs.python.org/pt-br/3/reference/index.html + +# cap18: appended 2026-01-09 15:36:28 +RedirectTemp /9q https://docs.python.org/pt-br/3/library/threading.html#using-locks-conditions-and-semaphores-in-the-with-statement +RedirectTemp /9r https://docs.python.org/pt-br/3/library/decimal.html#decimal.localcontext +RedirectTemp /9s https://docs.python.org/pt-br/3/library/unittest.mock.html#patch +RedirectTemp /9t https://docs.python.org/pt-br/3/library/contextlib.html +RedirectTemp /9v https://docs.python.org/pt-br/3/library/fileinput.html#fileinput.input +RedirectTemp /9w https://pt.wikipedia.org/wiki/Algoritmo_de_Euclides +RedirectTemp /9x https://docs.python.org/pt-br/3/reference/compound_stmts.html +RedirectTemp /9y https://docs.python.org/pt-br/3/glossary.html#term-eafp +RedirectTemp /9z https://docs.python.org/pt-br/3/library/stdtypes.html#typecontextmanager +RedirectTemp /a2 https://pt.wikipedia.org/wiki/Recursividade_(ci%C3%AAncia_da_computa%C3%A7%C3%A3o)#Fun%C3%A7%C3%B5es_recursivas_em_cauda +RedirectTemp /a3 https://docs.python.org/3/library/sqlite3.html#using-the-connection-as-a-context-manager +RedirectTemp /a4 https://docs.python.org/pt-br/3/reference/datamodel.html#with-statement-context-managers + +# cap19: appended 2026-01-09 15:48:37 +RedirectTemp /a5 https://docs.python.org/pt-br/3/library/sys.html#sys.getswitchinterval +RedirectTemp /a6 https://pt.wikipedia.org/wiki/Chamada_de_sistema +RedirectTemp /a7 https://docs.python.org/pt-br/3/library/threading.html# +RedirectTemp /a8 https://docs.python.org/pt-br/3/library/multiprocessing.shared_memory.html +RedirectTemp /a9 https://docs.python.org/pt-br/3/library/multiprocessing.shared_memory.html#multiprocessing.shared_memory.ShareableList +RedirectTemp /aa https://docs.python.org/pt-br/3/library/concurrent.futures.html#processpoolexecutor-example +RedirectTemp /ab https://docs.python.org/pt-br/3/library/queue.html#queue.SimpleQueue.get +RedirectTemp /ac https://pt.wikipedia.org/wiki/Condi%C3%A7%C3%A3o_de_corrida +RedirectTemp /ad https://pt.wikipedia.org/wiki/Troca_de_contexto +RedirectTemp /ae https://docs.python.org/pt-br/3/library/multiprocessing.html#programming-guidelines +RedirectTemp /af https://docs.python.org/pt-br/3/faq/library.html#can-t-we-get-rid-of-the-global-interpreter-lock +RedirectTemp /ag https://docs.python.org/pt-br/3/library/sys.html#sys.setswitchinterval +RedirectTemp /ah https://docs.python.org/pt-br/3/library/multiprocessing.html + +# cap20: appended 2026-01-09 16:08:51 +RedirectTemp /aj https://docs.python.org/pt-br/3.10/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor +RedirectTemp /ak https://docs.python.org/pt-br/3/library/concurrent.futures.html#concurrent.futures.as_completed +RedirectTemp /am https://docs.python.org/pt-br/3/library/concurrent.futures.html +RedirectTemp /an https://pt.wikipedia.org/wiki/POSIX_Threads +RedirectTemp /ap https://pt.wikipedia.org/wiki/Aloca%C3%A7%C3%A3o_din%C3%A2mica_de_mem%C3%B3ria_em_C + +# cap21: appended 2026-01-09 16:10:48 +RedirectTemp /aq https://docs.python.org/pt-br/3/library/asyncio-eventloop.html#asyncio.loop.getaddrinfo +RedirectTemp /ar https://docs.python.org/pt-br/3/library/socket.html#socket.getaddrinfo +RedirectTemp /as https://docs.python.org/pt-br/3.10/library/asyncio-eventloop.html#asyncio.get_event_loop +RedirectTemp /at https://docs.python.org/pt-br/3/library/asyncio-task.html#asyncio.create_task +RedirectTemp /av https://pt.wikipedia.org/wiki/Armazenamento_conectado_%C3%A0_rede +RedirectTemp /aw https://pt.wikipedia.org/wiki/Sem%C3%A1foro_(computa%C3%A7%C3%A3o) +RedirectTemp /ax https://docs.python.org/pt-br/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor +RedirectTemp /ay https://pt.wikipedia.org/wiki/Disco_de_Festo +RedirectTemp /az https://pt.wikipedia.org/wiki/Listas_invertidas +RedirectTemp /b2 https://docs.python.org/pt-br/3/library/asyncio-stream.html#asyncio.start_server +RedirectTemp /b3 https://docs.python.org/pt-br/3/library/asyncio-eventloop.html#asyncio.Server.serve_forever +RedirectTemp /b4 https://docs.python.org/pt-br/3/library/asyncio-stream.html#asyncio.StreamWriter.close +RedirectTemp /b5 https://docs.python.org/pt-br/3/library/asyncio-stream.html#streamwriter +RedirectTemp /b6 https://docs.python.org/pt-br/3/library/contextlib.html#contextlib.asynccontextmanager +RedirectTemp /b7 https://docs.python.org/pt-br/3/library/asyncio-task.html#asyncio.gather +RedirectTemp /b8 https://pt.wikipedia.org/wiki/D%C3%ADvida_tecnol%C3%B3gica +RedirectTemp /b9 https://docs.python.org/pt-br/3/library/asyncio.html +RedirectTemp /ba https://docs.python.org/pt-br/3/library/asyncio-dev.html +RedirectTemp /bb https://docs.python.org/pt-br/3/library/asyncio-protocol.html +RedirectTemp /bc https://docs.python.org/pt-br/3/library/asyncio-protocol.html#tcp-echo-server + +# cap22: appended 2026-01-09 16:17:17 +RedirectTemp /bd https://docs.python.org/pt-br/3/library/types.html#types.SimpleNamespace +RedirectTemp /be https://docs.python.org/pt-br/3/library/argparse.html#argparse.Namespace +RedirectTemp /bf https://docs.python.org/pt-br/3/library/multiprocessing.html#multiprocessing.managers.Namespace +RedirectTemp /bg https://docs.python.org/pt-br/3/library/functools.html#functools.cached_property +RedirectTemp /bh https://docs.python.org/pt-br/3.10/library/functools.html#functools.cached_property +RedirectTemp /bj https://docs.python.org/pt-br/3/library/functions.html#dir +RedirectTemp /bk https://docs.python.org/pt-br/3/library/functions.html#hasattr +RedirectTemp /bm https://docs.python.org/pt-br/3.10/reference/datamodel.html#special-method-lookup +RedirectTemp /bn https://docs.python.org/pt-br/3/library/functions.html +RedirectTemp /bp https://docs.python.org/pt-br/3/library/threading.html#rlock-objects +RedirectTemp /bq https://pt.wikipedia.org/wiki/Mutex_recursivo +RedirectTemp /br https://docs.python.org/pt-br/3/reference/datamodel.html#customizing-attribute-access +RedirectTemp /bs https://docs.python.org/pt-br/3/reference/datamodel.html#special-method-lookup +RedirectTemp /bt https://docs.python.org/pt-br/3/library/stdtypes.html#special-attributes + +# cap23: appended 2026-01-09 16:32:34 +RedirectTemp /bv https://docs.python.org/pt-br/3/howto/descriptor.html +RedirectTemp /bw https://docs.python.org/pt-br/3/howto/ + +# cap24: appended 2026-01-09 16:37:23 +RedirectTemp /bx https://docs.python.org/pt-br/3/reference/datamodel.html#creating-the-class-object +RedirectTemp /by https://docs.python.org/pt-br/3/reference/datamodel.html#customizing-class-creation +RedirectTemp /bz https://docs.python.org/pt-br/3/library/types.html +RedirectTemp /c2 https://docs.python.org/pt-br/3/reference/datamodel.html#object.__init_subclass__ +RedirectTemp /c3 https://pt.wikipedia.org/wiki/Programa%C3%A7%C3%A3o_orientada_a_aspecto +RedirectTemp /c4 https://docs.python.org/pt-br/3/library/functions.html#type diff --git a/links/amostra-links.adoc b/links/amostra-links.adoc index c3bf6b3..15c571a 100644 --- a/links/amostra-links.adoc +++ b/links/amostra-links.adoc @@ -1,4 +1,4 @@ -= Python Fluente: dados e funções += Python Fluente: Dados e Funções :doctype: book :media: prepress :hide-uri-scheme: diff --git a/links/fpy-deploy.sh b/links/deploy-fpy.sh similarity index 100% rename from links/fpy-deploy.sh rename to links/deploy-fpy.sh diff --git a/links/encurtar.ipynb b/links/encurtar.ipynb new file mode 100644 index 0000000..ceadd16 --- /dev/null +++ b/links/encurtar.ipynb @@ -0,0 +1,174 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "63ff70d0-34e4-426b-a455-4486922b0a75", + "metadata": {}, + "source": [ + "# Processo para encurtar URLS" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "565ac98b-4f9e-4fd7-b23d-7bddcf2dd60a", + "metadata": {}, + "outputs": [], + "source": [ + "from list_urls import find_urls" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "0f2ce1b8-1c45-41a0-9f38-675e6912d805", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "= /bt\thttps://docs.python.org/pt-br/3/library/stdtypes.html#special-attributes\n", + "+ /bx\thttps://docs.python.org/pt-br/3/reference/datamodel.html#creating-the-class-object\n", + "= /2j\thttps://docs.python.org/pt-br/3/reference/datamodel.html\n", + "+ /by\thttps://docs.python.org/pt-br/3/reference/datamodel.html#customizing-class-creation\n", + "+ /bz\thttps://docs.python.org/pt-br/3/library/types.html\n", + "= /7q\thttps://docs.python.org/pt-br/3/library/functools.html#functools.total_ordering\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "3 directives appended to FPY.LI.htaccess\n" + ] + } + ], + "source": [ + "CAP = 25\n", + "\n", + "adoc_path = f'../online/cap{CAP}.adoc'\n", + "\n", + "with open(adoc_path) as adoc:\n", + " lines = adoc.readlines()\n", + "\n", + "urls = find_urls(lines, fpy=False)\n", + "\n", + "htaccess_path = 'FPY.LI.htaccess'\n", + "\n", + "from shortener import main\n", + "redirects = main(htaccess_path, urls)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "37c13b84-c4a4-4295-8987-205c8da7a2e5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "= /bt\thttps://docs.python.org/pt-br/3/library/stdtypes.html#special-attributes\n", + "+ /bx\thttps://docs.python.org/pt-br/3/reference/datamodel.html#creating-the-class-object\n", + "= /2j\thttps://docs.python.org/pt-br/3/reference/datamodel.html\n", + "+ /by\thttps://docs.python.org/pt-br/3/reference/datamodel.html#customizing-class-creation\n", + "+ /bz\thttps://docs.python.org/pt-br/3/library/types.html\n", + "= /7q\thttps://docs.python.org/pt-br/3/library/functools.html#functools.total_ordering\n" + ] + } + ], + "source": [ + "for line in redirects:\n", + " print(line)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "25b20f6b-471f-4a14-bdd0-22eb21627962", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/bt https://docs.python.org/pt-br/3/library/stdtypes.html#special-attributes\n", + "/bx https://docs.python.org/pt-br/3/reference/datamodel.html#creating-the-class-object\n", + "/2j https://docs.python.org/pt-br/3/reference/datamodel.html\n", + "/by https://docs.python.org/pt-br/3/reference/datamodel.html#customizing-class-creation\n", + "/bz https://docs.python.org/pt-br/3/library/types.html\n", + "/7q https://docs.python.org/pt-br/3/library/functools.html#functools.total_ordering\n" + ] + } + ], + "source": [ + "import io\n", + "\n", + "from replace_urls import main\n", + "\n", + "pairs_file = io.StringIO('\\n'.join(redirects))\n", + "main(pairs_file, adoc_path)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "0eac9caf-6793-41c4-8fa3-4f3511c1d515", + "metadata": {}, + "source": [ + "Regex sugerida pelo DeepSeek, que funcionou no VS code para achar URLs de domínios sem inicial \"f\":\n", + "\n", + "```https://(?!f[^.]+\\.)[^/]+```" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "a66fdca6-e1df-4b1d-8971-1baae54cb9ed", + "metadata": {}, + "outputs": [], + "source": [ + "!rg --pcre2 'https://(?!f[^.]+\\.)[^/]+' ../online/cap24.adoc" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5980e0be-a2e2-42e5-8b6e-efc4a9fe0d3a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8cd03b9-1af6-46ca-a293-58fce7810581", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/links/list_urls.py b/links/list_urls.py index 85c059e..d398952 100755 --- a/links/list_urls.py +++ b/links/list_urls.py @@ -6,18 +6,25 @@ URL_RE = re.compile(r"""(https?://[^\s[<>"']+)\[""") -def find_urls(fpy=True, long=True): - found = 0 - for line in (l.rstrip() for l in fileinput.input()): - if match := URL_RE.search(line): +def find_urls(lines, fpy=True, long=True): + urls = [] + for line in lines: + if match := URL_RE.search(line.rstrip()): url = match.groups()[0] is_fpy = '://fpy.li/' in url if (is_fpy and not fpy) or (not is_fpy and not long): continue - print(url) - found += 1 - # print('FOUND', found, 'URLs') + if url.endswith('fluentpython.com'): + # keep uses in examples in chapter 20 + continue + urls.append(url) + return urls + +def multi_find_urls(fpy=True, long=True): + urls = find_urls(fileinput.input(), fpy, long) + for url in urls: + print(url) if __name__ == '__main__': - find_urls(fpy=False) + multi_find_urls(fpy=False) diff --git a/links/replace_urls.py b/links/replace_urls.py index 5a52d70..3baedb0 100755 --- a/links/replace_urls.py +++ b/links/replace_urls.py @@ -2,20 +2,17 @@ import sys -def main(): - pairs_name = sys.argv[1] - adoc_name = sys.argv[2] +def main(pairs_file, adoc_path): pairs = [] - with open(pairs_name) as fp: - for line in fp.readlines(): - pair = line.split()[-2:] - assert len(pair) == 2, f'pair not found: {line}' - assert pair[0].startswith('/'), f'no path: {line}' - pairs.append(pair) + for line in pairs_file.readlines(): + pair = line.split()[-2:] + assert len(pair) == 2, f'pair not found: {line}' + assert pair[0].startswith('/'), f'no path: {line}' + pairs.append(pair) - assert len(pairs) > 0, f'no pairs found in {pairs_name}' + assert len(pairs) > 0, f'no pairs found in {pairs_path}' - with open(adoc_name) as fp: + with open(adoc_path) as fp: adoc = fp.read() initial_adoc = adoc @@ -25,18 +22,21 @@ def main(): for path, url in pairs: if url in replaced: continue - assert url in adoc, f'{url} not found in {adoc_name}' + assert url in adoc, f'{url} not found in {adoc_path}' print(path, url) - # append [ to get Asciidoc link syntax URLs, not explicit URLs in code etc. + # append [ to match URLs in Asciidoc links `url[text]`, not URLs in code etc. short_url = f'https://fpy.li{path}[' adoc = adoc.replace(url + '[', short_url) replaced.add(url) - assert len(initial_adoc) > len(adoc), f'{adoc_name}: {len(initial_adoc)=} {len(adoc)=}' + assert len(initial_adoc) > len(adoc), f'{adoc_path}: {len(initial_adoc)=} {len(adoc)=}' - with open(adoc_name, 'w') as fp: + with open(adoc_path, 'w') as fp: fp.write(adoc) if __name__ == '__main__': - main() \ No newline at end of file + pairs_path = sys.argv[1] + adoc_path = sys.argv[2] + pairs_file = open(pairs_path) + main(pairs_file, adoc_path) \ No newline at end of file diff --git a/links/shortener.py b/links/shortener.py index 4521680..ee11f80 100755 --- a/links/shortener.py +++ b/links/shortener.py @@ -156,16 +156,14 @@ def gen_unused_short(redirects: dict) -> Iterator[str]: yield short -def main(): - htaccess_path, urls_path = sys.argv[1:3] +def main(htaccess_path, urls): with open(htaccess_path) as f: hta = f.read() assert 'RedirectTemp' in hta, 'No RedirecTemp in {htaccess_path}' - with open(urls_path) as f: - urls = [u.rstrip() for u in f.readlines()] redirects, targets = load_redirects(parse_htaccess(hta)) path_urls = [] + changes = [] path_gen = gen_unused_short(redirects) for url in urls: if url in DO_NOT_SHORTEN: @@ -174,12 +172,17 @@ def main(): path_urls.append(path_url) path, url, new = path_url flag = '+' if new else '=' - print(f'{flag} /{path}\t{url}') + line = f'{flag} /{path}\t{url}' + print(line) + changes.append(line) with open(htaccess_path, 'a') as f: count = update_htaccess(f, path_urls) print(f'{count} directives appended to {htaccess_path}', file=sys.stderr) - + return changes if __name__ == '__main__': - main() \ No newline at end of file + htaccess_path, urls_path = sys.argv[1:3] + with open(urls_path) as f: + urls = [u.rstrip() for u in f.readlines()] + main(htaccess_path, urls) \ No newline at end of file diff --git a/online/Livro.adoc b/online/Livro.adoc index e7c689b..48352b6 100644 --- a/online/Livro.adoc +++ b/online/Livro.adoc @@ -49,7 +49,7 @@ include::cap09.adoc[] include::cap10.adoc[] [[classes_protocols_part]] -= Parte III: Classes e protocolos += Parte III: Classes e Protocolos include::cap11.adoc[] diff --git a/online/Prefacio.adoc b/online/Prefacio.adoc index 5cecc85..67ed8b0 100644 --- a/online/Prefacio.adoc +++ b/online/Prefacio.adoc @@ -271,7 +271,7 @@ Alex Martelli e Anna Ravenscroft foram os primeiros a verem o esquema desse livr Seus livros me ensinaram Python idiomático e são modelos de clareza, precisão e profundidade em escrita técnica. Os https://fpy.li/p-7[6,200+ posts de Alex no Stack Overflow] (EN) são uma fonte de boas ideias sobre a linguagem e seu uso apropriado. -Martelli e Ravenscroft foram também revisores técnicos deste livro, juntamente com Lennart Regebro e Leonardo Rochael. Todos nesta proeminente equipe de revisão técnica têm pelo menos 15 anos de experiência com Python, com muitas contribuições a projetos Python de alto impacto, em contato constante com outros desenvolvedores da comunidade. Em conjunto, eles me enviaram centenas de correções, sugestões, questões e opiniões, acrescentando imenso valor ao livro. Victor Stinner gentilmente revisou o <>, trazendo seu conhecimento especializado, como um dos mantenedores do `asyncio`, para a equipe de revisão técnica. Foi um grande privilégio e um prazer colaborar com eles por estes muitos meses. +Martelli e Ravenscroft foram também revisores técnicos deste livro, juntamente com Lennart Regebro e Leonardo Rochael. Todos nesta proeminente equipe de revisão técnica têm pelo menos 15 anos de experiência com Python, com muitas contribuições a projetos Python de alto impacto, em contato constante com outros desenvolvedores da comunidade. Em conjunto, eles me enviaram centenas de correções, sugestões, questões e opiniões, acrescentando imenso valor ao livro. Victor Stinner gentilmente revisou o <>, trazendo seu conhecimento especializado, como um dos mantenedores do _asyncio_, para a equipe de revisão técnica. Foi um grande privilégio e um prazer colaborar com eles por estes muitos meses. A editora Meghan Blanchette foi uma fantástica mentora, e me ajudou a melhorar a organização e o fluxo do texto do livro, me mostrando que partes estavam monótonas e evitando que eu atrasasse o projeto ainda mais. Brian MacDonald editou os capítulo na <> quando Meghan estava ausente. Adorei trabalhar com eles e com todos na O'Reilly, incluindo a equipe de suporte e desenvolvimento do Atlas (Atlas é a plataforma de publicação de livros da O'Reilly, que eu tive a felicidade de usar para escrever esse livro). diff --git a/online/cap01.adoc b/online/cap01.adoc index b91f9a1..7632acf 100644 --- a/online/cap01.adoc +++ b/online/cap01.adoc @@ -426,7 +426,7 @@ Então, por consistência, nossa API também usa `abs` para calcular a magnitude ---- Podemos((("* (star) operator")))((("multiplication, scalar")))((("star (*) operator"))) -também implementar o operador `*`, para realizar multiplicação escalar +também implementar o operador `*`, para realizar multiplicação por escalar (isto é, multiplicar um vetor por um número para obter um novo vetor de mesma direção e magnitude multiplicada): [source, python] @@ -467,7 +467,7 @@ Vou falar mais sobre esse tópico no <>. ==== Da forma como está implementado, o <> permite multiplicar um `Vector` por um número, mas não um número por um `Vector`, -violando a propriedade comutativa da multiplicação escalar. +violando a propriedade comutativa da multiplicação por escalar. Vamos consertar isso com o método especial `+__rmul__+` no <>. ==== @@ -571,13 +571,6 @@ e `or` devolve um dos seus operandos no formato original: qualquer que seja o valor deste último. ==== -//// -PROD: last time I rendered this chapter in PDF, the NOTE before this comment was rendered -partially orverwriting the start of the following section. -I've seen this happening many times as wrote the book, and sometimes the issue goes away by itself. -Feel free to move the large [[collection_uml]] figure if needed. -//// - [[collection_api]] ==== A API de Collection @@ -686,7 +679,7 @@ como veremos no <>.((("special methods", "special method names a |Comparação rica| `< \<= == != > >=` | `+__lt__ __le__ __eq__ __ne__ __gt__ __ge__+` |Aritmético| `+ - * / // % @ divmod() round() ** pow()` | `+__add__ __sub__ __mul__ __truediv__ __floordiv__ __mod__ __matmul__ __divmod__ __round__ __pow__+` |Aritmética reversa| (idem, com operandos trocados) |`+__radd__ __rsub__ __rmul__ __rtruediv__ __rfloordiv__ __rmod__ __rmatmul__ __rdivmod__ __rpow__+` -|Atribuição aritmética| `+= -= \*= /= //= %= @= **=` | `+__iadd__ __isub__ __imul__ __itruediv__ __ifloordiv__ __imod__ __imatmul__ __ipow__+` +|Atribuição aumentada| `+= -= \*= /= //= %= @= **=` | `+__iadd__ __isub__ __imul__ __itruediv__ __ifloordiv__ __imod__ __imatmul__ __ipow__+` |Bit a bit | `& \| ^ << >> ~` | `+__and__ __or__ __xor__ __lshift__ __rshift__ __invert__+` |Bit a bit reversa| (idem, com operandos trocados) | `+__rand__ __ror__ __rxor__ __rlshift__ __rrshift__+` |Atribuição bit a bit| `&= \|= ^= <<= >>=` | `+__iand__ __ior__ __ixor__ __ilshift__ __irshift__+` diff --git a/online/cap02.adoc b/online/cap02.adoc index 27e5f93..b5dffb1 100644 --- a/online/cap02.adoc +++ b/online/cap02.adoc @@ -204,7 +204,7 @@ Qualquer um que saiba um pouco de Python consegue ler o <>. Entretanto, após aprender sobre as listcomps, acho o <> mais legível, porque deixa sua intenção explícita. -Um loop `for` pode ser usado para muitas coisas diferentes: +Um laço `for` pode ser usado para muitas coisas diferentes: percorrer uma sequência para contar ou encontrar itens, computar valores agregados (somas, médias), ou inúmeras outras tarefas. O código no <> está criando uma lista. @@ -216,7 +216,7 @@ Já vi código Python usando listcomps apenas para repetir um bloco de código p Se você não vai fazer alguma coisa com a lista criada, não deveria usar essa sintaxe. Além disso, tente manter o código curto. Se uma compreensão ocupa mais de duas linhas, -provavelmente seria melhor quebrá-la ou reescrevê-la como um bom e velho loop `for`. +provavelmente seria melhor quebrá-la ou reescrevê-la como um bom e velho laço `for`. Avalie qual o melhor caminho: em Python, como em português, não existem regras absolutas para se escrever bem. @@ -361,13 +361,13 @@ O resultado tem seis itens. ---- ==== <1> Isso gera uma lista de tuplas ordenadas por cor, depois por tamanho. -<2> Observe que a lista resultante é ordenada como se os loops `for` +<2> Observe que a lista resultante é ordenada como se os laços `for` estivessem aninhados na mesma ordem que eles aparecem na listcomp. <3> Para ter os itens ordenados por tamanho e então por cor, apenas rearranje as cláusulas `for`; quebrar a listcomp em duas linhas torna mais fácil ver como o resultado será ordenado. -No <> (<>) +No <> do <> usei a seguinte expressão para inicializar um baralho de cartas com uma lista contendo 52 cartas de todos os 13 valores possíveis para cada um dos quatro naipes, ordenada por naipe e então por valor: @@ -421,10 +421,10 @@ O <> usa uma genexp com um produto cartesiano para gerar uma relação de camisetas de duas cores em três tamanhos. Diferente do <>, aquela lista de camisetas com seis itens nunca é criada na memória: -a expressão geradora alimenta o loop `for` produzindo um item por vez. +a expressão geradora alimenta o laço `for` produzindo um item por vez. Se as duas listas usadas no produto cartesiano tivessem mil itens cada uma, usar uma função geradora evitaria o custo de construir uma lista -com um milhão de itens apenas para passar ao loop `for`. +com um milhão de itens apenas para passar ao laço `for`. [[ex_genexp_cartesian]] .Produto cartesiano em uma expressão geradora @@ -449,7 +449,7 @@ nunca é criada neste exemplo. [NOTE] ==== -O <> explica em detalhes o funcionamento de geradoras. +O <> explica em detalhes o funcionamento de geradores. A ideia aqui é apenas mostrar o uso de expressões geradores para inicializar sequências diferentes de listas, ou produzir uma saída que não precise ser mantida na memória. ==== @@ -518,7 +518,7 @@ uma variável descartável, apenas para coletar valores que não usaremos. Em geral, usar `+_+` como variável descartável (_dummy variable_) é só uma convenção. É apenas um nome de variável estranho mas válido. Entretanto, em uma instrução `match/case`, o `+_+` é um coringa que corresponde a qualquer valor, -mas não está vinculado a um valor +mas não está vinculado a um valor. Veja a <>. No console de Python, o resultado da instrução anterior é atribuído a `+_+`, exceto quando o resultado é `None`. ==== @@ -638,7 +638,7 @@ Quando((("tuples", "versus lists", secondary-sortas="lists")))((("lists", "versu usamos uma tupla como uma variante imutável de `list`, é bom saber o quão similares são suas APIs. Como se pode ver na <>, `tuple` suporta todos os métodos de `list` que não envolvem adicionar ou remover itens, -com uma exceção—`tuple` não possui o método `+__reversed__+`. +com uma exceção—`tuple` não tem o método `+__reversed__+`. Entretanto, `reversed(my_tuple)` funciona sem esse método; ele serve apenas para otimizar. [[list_x_tuple_attrs_tbl]] @@ -865,7 +865,7 @@ Então o primeiro alvo seria `(record,)` e o segundo `((field,),)`. Nos dois casos, esquecer aquela vírgula causa um bug silencioso.footnote:[Agradeço ao revisor técnico Leonardo Rochael por esse exemplo.] -Agora vamos estudar _pattern matching_, +Agora vamos estudar casamento de padrões, que suporta maneiras ainda mais poderosas para desempacotar sequências.((("", startref="Sunpack02")))((("", startref="iterun02"))) @@ -874,21 +874,21 @@ sequências.((("", startref="Sunpack02")))((("", startref="iterun02"))) === Pattern matching com sequências O((("match/case statement")))((("sequences", "pattern matching with", id="Spattern02")))((("pattern matching", "match/case statement"))) -novo recurso mais visível de Python 3.10 é o _pattern matching_ -(casamento de padrões) com a instrução `match/case`, proposta na +novo recurso mais visível de Python 3.10 é o +casamento de padrões (_pattern matching_) com a instrução `match/case`, proposta na https://fpy.li/pep634[PEP 634—Structural Pattern Matching: Specification (_Casamento Estrutural de Padrões: Especificação_)] (EN). [role="man-height2"] [NOTE] ==== Carol Willing, uma das desenvolvedoras principais de Python, -escreveu uma excelente introdução ao _pattern matching_ na seção -https://fpy.li/2r["Casamento de padrão estrutural"]footnote:[NT: A tradução em português da documentação de -Python adotou o termo "casamento de padrões" no lugar de _pattern matching_. +escreveu uma excelente introdução ao casamento de padrões na seção +https://fpy.li/2r["Casamento de padrão estrutural"]footnote:[NT: +Os tradutores da documentação de Python em português do Brasil +adotaram o termo "casamento de padrões" no lugar de _pattern matching_. O termo em inglês é usado nas comunidades brasileiras -de linguagens que implementam _pattern matching_ há muitos anos, como por exemplo Scala, -Elixir e Haskell. -Naturalmente mantivemos os títulos originais nos links externos.] +de linguagens que implementam casamento de padrões há muitos anos, +como por exemplo Scala, Elixir e Haskell.] em https://fpy.li/2s["O que há de novo no Python 3.10"]. Você pode querer ler aquela revisão rápida. Neste livro, dividi o tratamento do casamento de padrões em diferentes capítulos, @@ -947,7 +947,7 @@ Uma melhoria fundamental do `match` sobre o `switch` é((("destructuring"))) a _desestruturação_—uma forma mais avançada de desempacotamento. Desestruturação é uma palavra nova no vocabulário de Python, mas é usada com frequência na documentação de linguagens -que suportam o _pattern matching_—como Scala e Elixir. +que suportam o casamento de padrões—como Scala e Elixir. Como um primeiro exemplo de desestruturação, o <> mostra parte do <> reescrito com `match/case`. @@ -1102,7 +1102,7 @@ https://fpy.li/2-10["A very deep iterable and type match with extraction" (_Um m O <> não é melhor que o <>. É apenas um exemplo para contrastar duas formas de fazer a mesma coisa. -O próximo exemplo mostra como o _pattern matching_ contribui para a criação de código claro, conciso e eficaz. +O próximo exemplo mostra como o casamento de padrões contribui para a criação de código claro, conciso e eficaz. [[pattern_matching_seq_interp_sec]] ==== Casando padrões de sequência em um interpretador @@ -1115,7 +1115,7 @@ https://fpy.li/2-11[_lis.py_]: um interpretador de um subconjunto do dialeto Scheme da linguagem de programação Lisp, em 132 belas linhas de código Python legível. Peguei o código-fonte de Norvig (publicado sob a licença MIT) e o atualizei para Python 3.10, -para exemplificar o _pattern matching_. +para exemplificar o casamento de padrões. Nessa seção, vamos comparar uma parte fundamental do código de Norvig—que usa `if/elif` e desempacotamento—com uma nova versão usando `match/case`. @@ -1166,7 +1166,7 @@ include::../code/02-array-seq/lispy/py3.9/lis.py[tags=EVAL_IF_MIDDLE] ==== Observe como cada instrução `elif` verifica o primeiro item da lista, e então desempacota a lista, ignorando o primeiro item. -O uso extensivo do desempacotamento sugere que Norvig é um fã do _pattern matching_, +O uso extensivo do desempacotamento sugere que Norvig é um fã do casamento de padrões, mas ele originalmente escreveu aquele código em Python 2 (apesar de agora ele funcionar com qualquer Python 3) Usando `match/case` em Python ≥ 3.10, podemos refatorar `evaluate`, como mostrado no <>. @@ -1194,7 +1194,7 @@ Sem o último `case`, para pegar tudo que tiver passado pelos anteriores, o bloco `match` não faz nada quando o sujeito não casa com algum `case`—e isso pode causar uma falha silenciosa. Norvig deliberadamente evitou a checagem e o tratamento de erros em _lis.py_, para manter o código fácil de entender. -Com _pattern matching_, podemos acrescentar mais verificações e ainda manter o programa legível. +Com casamento de padrões, podemos acrescentar mais verificações e ainda manter o programa legível. Por exemplo, no padrão `'define'`, o código original não se assegura que `name` é uma instância de `Symbol`—isso exigiria um bloco `if`, uma chamada a `isinstance`, e mais código. @@ -1275,10 +1275,10 @@ o segundo elemento deve ser um `Symbol` na forma original de `define`, mas deve ser uma sequência começando com um `Symbol` na sintaxe de `define` para definição de função. Agora pense em quanto trabalho teríamos para adicionar o suporte a essa segunda sintaxe de `define` -sem a ajuda do _pattern matching_ no <>. +sem a ajuda do casamento de padrões no <>. A instrução `match` faz mais que o `switch` das linguagens similares ao C. -O _pattern matching_ é um exemplo de programação declarativa: +O casamento de padrões é um exemplo de programação declarativa: o código descreve "o que" você quer casar, em vez de "como" casar. A forma do código segue a forma dos dados, como ilustra a <>. @@ -1295,7 +1295,7 @@ A forma do código segue a forma dos dados, como ilustra a <>. Em vez de encher seu código de fatias explícitas fixas, você pode nomeá-las. -Veja como isso torna legível o loop `for` no final do exemplo. +Veja como isso torna legível o laço `for` no final do exemplo. [[flat_file_invoice]] .Itens de um arquivo tabular de fatura @@ -1681,7 +1681,7 @@ Veja uma demonstração de `*=` com uma sequência mutável e depois com uma seq A concatenação repetida de sequências imutáveis é ineficiente, pois ao invés de apenas acrescentar novos itens, o interpretador tem que copiar toda a sequência alvo para criar um novo objeto com os novos itens concatenados.footnote:[`str` é uma exceção a essa descrição. -Como criar strings com `+=` em loops é tão comum em bases de código reais, +Como criar strings com `+=` em laços é tão comum em bases de código reais, o CPython foi otimizado para esse caso de uso. Instâncias de `str` são alocadas na memória com espaço extra, então a concatenação não exige a cópia da string inteira a cada operação.] @@ -2548,7 +2548,7 @@ https://fpy.li/2-30[Python for Data Analysis], de Wes McKinney. "A Numpy é toda sobre vetorização". Essa é a frase de abertura do livro de acesso aberto https://fpy.li/2-31[From Python to NumPy], de Nicolas P. Rougier. Operações vetorizadas aplicam funções matemáticas a todos os elementos de um array -sem um loop explícito escrito em Python. +sem um laço explícito escrito em Python. Elas podem operar em paralelo, usando instruções especiais de vetor presentes em CPUs modernas, tirando proveito de múltiplos núcleos ou delegando para a GPU, dependendo da biblioteca. O primeiro exemplo no livro de Rougier mostra um aumento de velocidade de 500 vezes, diff --git a/online/cap03.adoc b/online/cap03.adoc index 11b1e12..7f343c7 100644 --- a/online/cap03.adoc +++ b/online/cap03.adoc @@ -25,12 +25,12 @@ os conjuntos que você pode ter encontrado em outras linguagens populares. Em especial, os conjuntos de Python implementam todas as operações fundamentais da teoria dos conjuntos, como união, interseção, testes de subconjuntos, etc. Com eles, podemos expressar algoritmos de forma mais declarativa, -evitando o excesso de loops e condicionais aninhados. +evitando o excesso de laços e condicionais aninhados. Aqui está um breve esquema do capítulo: * A sintaxe moderna((("dictionaries and sets", "topics covered"))) para criar e manipular `dicts` -e mapeamentos, incluindo desempacotamento aumentado e _pattern matching_ (casamento de padrões) +e mapeamentos, incluindo desempacotamento aumentado e casamento de padrões (_pattern matching_) * Métodos comuns dos tipos de mapeamentos * Tratamento especial para chaves ausentes * Variantes de `dict` na biblioteca padrão @@ -202,7 +202,7 @@ https://fpy.li/pep584[PEP 584—Add Union Operators To dict (_Acrescentar Operad (EN) inclui um bom resumo das outras formas de mesclar mapeamentos. ==== -Agora vamos ver como o pattern matching se aplica aos mapeamentos.((("", startref="DASsyntax03"))) +Agora vamos ver como o casamento de padrões se aplica aos mapeamentos.((("", startref="DASsyntax03"))) [[pattern_matching_mappings_sec]] === Pattern matching com mapeamentos @@ -222,7 +222,7 @@ Veja https://fpy.li/2z[`Py_TPFLAGS_MAPPING`] (EN).] No <> nos concentramos apenas nos padrões de sequência, mas tipos diferentes de padrões podem ser combinados e aninhados. -Graças à desestruturação, o pattern matching é uma ferramenta poderosa para +Graças à desestruturação, o casamento de padrões é uma ferramenta poderosa para processar registros estruturados como sequências e mapeamentos aninhados, que frequentemente precisamos ler de APIs JSON ou bancos de dados que suportam registros ou campos semi-estruturados, @@ -292,13 +292,13 @@ Ice cream details: {'flavor': 'vanilla', 'cost': 199} Na <>, vamos estudar o `defaultdict` e outros mapeamentos onde buscas com chaves via `+__getitem__+` (isto é, `d[chave]`) funcionam porque itens ausentes são criados na hora. -No contexto do pattern matching, -um match é bem sucedido apenas se o sujeito já possui as chaves necessárias no início do bloco `match`. +No contexto do casamento de padrões, +um match é bem sucedido apenas se o sujeito já tem as chaves necessárias no início do bloco `match`. [TIP] ==== O tratamento automático de chaves ausentes não é acionado porque -o pattern matching sempre usa o método `d.get(key, sentinel)`—onde o +o casamento de padrões sempre usa o método `d.get(key, sentinel)`—onde o `sentinel` default é um marcador com valor especial, que não pode aparecer nos dados do usuário. ==== @@ -448,7 +448,7 @@ A <> mostra os métodos implementados por `dict` e pelas va A forma como `d.update(m)` lida com seu primeiro argumento, `m`, é um excelente exemplo((("duck typing"))) de _duck typing_ (_tipagem pato_): -ele primeiro verifica se `m` possui um método `keys` e, em caso afirmativo, +ele primeiro verifica se `m` tem um método `keys` e, em caso afirmativo, assume que `m` é um mapeamento. Caso contrário, `update()` reverte para uma iteração sobre `m`, presumindo que seus item são pares `(chave, valor)`. @@ -703,7 +703,7 @@ Classes definidas pelo usuário derivadas de mapeamentos da biblioteca padrão p como explicado na próxima seção. ==== -[[inconsistent_missing]] +[[inconsistent_missing_sec]] ==== O uso inconsistente de +__missing__+ na biblioteca padrão Considere os seguintes cenários, e como eles afetam a busca de chaves ausentes: @@ -1206,7 +1206,7 @@ Assim, dados dois conjuntos `a` e `b`, `a | b` devolve sua união, Quando bem utilizadas, as operações de conjuntos podem reduzir tanto a contagem de linhas quanto o tempo de execução de programas Python, ao mesmo tempo em que tornam o código mais legível e -mais fácil de entender—pela remoção de loops e lógica condicional. +mais fácil de entender—pela remoção de laços e lógica condicional. Por exemplo, imagine que você tem um grande conjunto de endereços de e-mail (o "palheiro"—`haystack`) e um conjunto menor de endereços (as "agulhas"—`needles``), @@ -1376,7 +1376,7 @@ no _http://fluentpython.com_ para mais detalhes. Agora vamos revisar a vasta seleção de operações oferecidas pelos conjuntos. -[[set_op_section]] +[[set_ops_sec]] ==== Operações de conjuntos A <> dá((("dictionaries and sets", "set operations", id="DASset03-ops")))((("sets", @@ -1550,7 +1550,7 @@ Por outro lado, uma view devolvida por `dict_keys` sempre pode ser usada como um pois todas as chaves são hashable—por definição. ==== -Usar operações de conjunto com views pode evitar a necessidade de muitos loops e ifs +Usar operações de conjunto com views pode evitar a necessidade de muitos laços e ifs quando seu código precisa inspecionar o conteúdo de dicionários. Deixe a eficiente implementação de Python em C trabalhar para você! @@ -1562,7 +1562,7 @@ Com isso, encerramos esse capítulo. Dicionários((("dictionaries and sets", "overview of"))) são a pedra fundamental de Python. Ao longo dos anos, a sintaxe literal familiar `{k1: v1, k2: v2}` -foi aperfeiçoada para suportar desempacotamento com `**` e pattern matching, bem como com compreensões de `dict`. +foi aperfeiçoada para suportar desempacotamento com `**` e casamento de padrões, bem como com compreensões de `dict`. Além do `dict` básico, a biblioteca padrão oferece mapeamentos práticos prontos para serem usados, como o `defaultdict`, o `ChainMap`, e o `Counter`, todos definidos no módulo `collections`. diff --git a/online/cap04.adoc b/online/cap04.adoc index 4da24da..917c44f 100644 --- a/online/cap04.adoc +++ b/online/cap04.adoc @@ -215,7 +215,7 @@ causando muitas dores de cabeça nos desenvolvedores que lidam com dados binári A decisão está documentada na https://fpy.li/pep461[PEP 461--Adding % formatting to bytes and bytearray (_Acrescentando formatação com % a bytes e bytearray_)]. (EN)] -As sequências binárias têm um método de classe que `str` não possui, chamado `fromhex`, +As sequências binárias têm um método de classe que `str` não tem, chamado `fromhex`, que cria uma sequência binária a partir da análise de pares de dígitos hexadecimais, separados opcionalmente por espaços: @@ -1602,7 +1602,7 @@ No <>, observe que o comando `if`, na função `find`, usa o método `.issubset()` para testar rapidamente se todas as palavras no conjunto `query` aparecem na lista de palavras criada a partir do nome do caractere. Graças à rica API de conjuntos de Python, -não precisamos de um loop `for` aninhado e de outro `if` para implementar essa verificação +não precisamos de um laço `for` aninhado e de outro `if` para implementar essa verificação [[ex_cfpy]] .cf.py: o utilitário de busca de caracteres diff --git a/online/cap05.adoc b/online/cap05.adoc index 25a08c1..0444d62 100644 --- a/online/cap05.adoc +++ b/online/cap05.adoc @@ -432,7 +432,7 @@ Pule a próxima caixinha se você não estiver interessada em gambiarras. .Remendando uma tupla nomeada para injetar um método **** -Lembre como criamos a classe `Card` class no <> (<>): +Lembre como criamos a classe `Card` class no <> do <>: [source, python] ---- @@ -751,7 +751,7 @@ Vamos examinar uma instância de `DemoNTClass`: Para criar `nt`, precisamos passar pelo menos o argumento `a` para `DemoNTClass`. O construtor também aceita um argumento `b`, mas como este último tem um valor default (de `1.1`), ele é opcional. -Como esperado, o objeto `nt` possui os atributos `a` e `b`; +Como esperado, o objeto `nt` tem os atributos `a` e `b`; ele não tem um atributo `c`, mas Python obtém `c` da classe, como de hábito. Se você tentar atribuir valores para `nt.a`, `nt.b`, `nt.c`, ou mesmo para `nt.z`, @@ -1453,8 +1453,8 @@ id="DCBpattern05")))((("pattern matching", "pattern matching class instances", i são projetados para "casar" com instâncias de classes por tipo e—opcionalmente—por atributos. O sujeito de um padrão de classe pode ser uma instância de qualquer classe, não apenas instâncias de classes de dados.footnote:[Trato desse conteúdo aqui por ser o -primeiro capítulo sobre classes definidas pelo usuário, e acho que _pattern matching_ com classes -é um assunto muito importante para esperar até a <> do livro. +primeiro capítulo sobre classes definidas pelo usuário, e considero o casamento de padrões com classes +um assunto importante demais para esperar até a <> do livro. Minha filosofia: é mais importante saber como usar classes que como defini-las.] Há três variantes de padrões de classes: simples, nomeado e posicional. @@ -1594,7 +1594,7 @@ Isso inclusive funciona se a variável do padrão também se chamar `country`: ---- Padrões de classe nomeados são bastante legíveis, -e funcionam com qualquer classe que possua atributos de instância públicos. +e funcionam com qualquer classe que tenha atributos de instância públicos. Mas eles são um tanto prolixos. Padrões de classe posicionais são mais convenientes em alguns casos, @@ -1701,7 +1701,7 @@ frustrando um princípio básico da programação orientada a objetos: os dados e as funções que acessam os dados devem estar juntos na mesma classe. Classes sem uma lógica podem ser um sinal de uma lógica fora de lugar. -Na última seção, vimos como o _pattern matching_ +Na última seção, vimos como o casamento de padrões funciona com instâncias de qualquer classe como sujeitos—e não apenas das classes criadas com as fábricas apresentadas nesse capítulo. @@ -1790,7 +1790,7 @@ Entre outras coisas, ele diz: [quote] ____ Diz a lenda que o atributo mais importante de Guido, além do próprio Python, -é a máquina do tempo de Guido, um aparelho que dizem que ele possui por causa da +é a máquina do tempo de Guido, um aparelho que dizem que ele tem por causa da frequência irritante com que pedidos de usuários por novos recursos recebem como resposta "Acabei de implementar isso noite passada..." ____ diff --git a/online/cap06.adoc b/online/cap06.adoc index 2b6075a..b9c2550 100644 --- a/online/cap06.adoc +++ b/online/cap06.adoc @@ -563,7 +563,7 @@ então seu atributo `passengers` se refere a outra lista. Em geral, criar cópias profundas não é uma questão simples. Objetos podem conter referências cíclicas que fariam um algoritmo -ingênuo entrar em um loop infinito. +ingênuo entrar em um laço infinito. A função `deepcopy` memoriza os objetos já copiados, e trata referências cíclicas corretamente. Isso é demonstrado no <>. @@ -771,7 +771,7 @@ a implementação correta vincula uma cópia daquele argumento a `self.passenger A próxima seção explica porque copiar o argumento é uma boa prática. -[[defensive_argument]] +[[defensive_argument_sec]] ==== Programação defensiva com argumentos mutáveis Ao escrever uma função que recebe um argumento mutável, diff --git a/online/cap07.adoc b/online/cap07.adoc index 488c6b8..24d8c5f 100644 --- a/online/cap07.adoc +++ b/online/cap07.adoc @@ -354,7 +354,7 @@ Introduzidas no Python 3.5. Funções geradoras assíncronas:: Funções((("asynchronous generators"))) ou métodos definidos com `async def`, contendo `yield` em seu corpo. Quando chamados, devolvem um gerador assíncrono para ser usado com `async for`. Introduzidas no Python 3.6. -Funções geradoras, funções de corrotinas nativas e geradoras assíncronas são diferentes de outros invocáveis: os valores devolvidos por tais funções nunca são dados da aplicação, mas objetos que exigem processamento adicional, seja para produzir dados da aplicação, seja para realizar algum trabalho útil. +Funções geradoras, funções de corrotinas nativas e funções geradoras assíncronas são diferentes de outros invocáveis: os valores devolvidos por tais funções nunca são dados da aplicação, mas objetos que exigem processamento adicional, seja para produzir dados da aplicação, seja para realizar algum trabalho útil. Funções geradoras devolvem iteradores. Ambos são tratados no <>. Funções de corrotinas nativas e funções geradoras assíncronas devolvem objetos que só funcionam com a ajuda de um framework de programação assíncrona, tal como _asyncio_. @@ -520,7 +520,7 @@ Após esse mergulho nos recursos flexíveis de declaração de argumentos no Pyt === Pacotes para programação funcional -Apesar((("functions, as first-class objects", "packages for functional programming", id="FAFfp07")))((("functional programming", "packages for", id="fprogpack07"))) de Guido deixar claro que não projetou Python para ser uma linguagem de programação funcional, o estilo de programação funcional pode ser amplamente utilizado, graças a funções de primeira classe, _pattern matching_ e o suporte de pacotes como `operator` e `functools`, dos quais falaremos nas próximas duas seções. +Apesar((("functions, as first-class objects", "packages for functional programming", id="FAFfp07")))((("functional programming", "packages for", id="fprogpack07"))) de Guido deixar claro que não projetou Python para ser uma linguagem de programação funcional, o estilo de programação funcional pode ser amplamente utilizado, graças a funções de primeira classe, casamento de padrões e o suporte de pacotes como `operator` e `functools`, dos quais falaremos nas próximas duas seções. [[operator_module_sec]] @@ -833,7 +833,7 @@ tratando basicamente dos mesmos conceitos com uma abordagem diferente. Veja a https://fpy.li/pep3102[PEP 3102--Keyword-Only Arguments (_Argumentos somente nomeados_)] (EN) se quiser saber a justificativa e casos de uso desse recurso. Uma ótima introdução à programação funcional em Python é o https://fpy.li/46["Programação Funcional COMO FAZER"], de A. M. Kuchling. -O principal foco daquele texto, entretanto, é o uso de iteradores e geradoras, assunto do <>. +O principal foco daquele texto, entretanto, é o uso de iteradores e geradores, assunto do <>. A questão no StackOverflow, https://fpy.li/7-12["Python: Why is functools.partial necessary?" (_Python: Por que functools.partial é necessária?_)] (EN), tem uma resposta muito informativa (e engraçada) escrita por Alex Martelli, co-autor do clássico _Python in a Nutshell_ (O'Reilly). diff --git a/online/cap08.adoc b/online/cap08.adoc index 2768c8a..7c9ec5a 100644 --- a/online/cap08.adoc +++ b/online/cap08.adoc @@ -15,7 +15,7 @@ Dicas de tipo((("functions, type hints in", foram a maior mudança na história de Python desde https://fpy.li/descr101[a unificação de tipos e classes] no Python 2.2, lançado em 2001. -Entretanto, as dicas de tipo não beneficiam igualmente a todos as pessoas que usam Python. +Entretanto, as dicas de tipo não beneficiam igualmente todas as pessoas que usam Python. Por isso deverão ser sempre opcionais. A @@ -272,7 +272,7 @@ def show_count(count: int, word: str) -> str: [TIP] ==== -Em vez de digitar opções de linha de comando como [.keep-together]#`--disallow-incomplete-defs`#, +Em vez de digitar opções de linha de comando como `--disallow-incomplete-defs`, você pode salvar sua configuração favorita da forma descrita na página https://fpy.li/8-8[Mypy configuration file] (EN) na documentação do Mypy. Você pode incluir configurações globais e configurações específicas para cada módulo. @@ -483,7 +483,7 @@ def double(x: abc.Sequence): return x * 2 ---- -Um checador de tipos irá rejeitar esse código. +Um checador de tipos rejeitará este código. Se você informar ao Mypy que `x` é do tipo `abc.Sequence`, ele vai marcar `x * 2` como erro, pois a https://fpy.li/8-13[`Sequence` ABC] não implementa ou herda o método `+__mul__+`. Durante a execução, o código vai funcionar com sequências concretas @@ -1137,7 +1137,7 @@ Podemos resumir esse processo em quatro etapas: . Tornar aquele comportamento o default a partir de Python 3.9: `list[str]` agora funciona sem que `future` precise ser importado. -. Descontinuar (_deprecate_) todos os tipos genéricos do módulo `typing`.footnote:[Uma de minhas contribuições para a documentação do módulo `typing` foi acrescentar dúzias de avisos de descontinuação, enquanto eu reorganizava as entradas abaixo de https://fpy.li/4b["Conteúdo do Módulo"] em subseções, sob a supervisão de Guido van [.keep-together]#Rossum#.] Avisos de descontinuação não serão emitidos pelo interpretador Python, porque os checadores de tipos devem sinalizar os tipos descontinuados quando o programa sendo checado tiver como alvo Python 3.9 ou posterior. +. Descontinuar (_deprecate_) todos os tipos genéricos do módulo `typing`.footnote:[Uma de minhas contribuições para a documentação do módulo `typing` foi acrescentar dúzias de avisos de descontinuação, enquanto eu reorganizava as entradas abaixo de https://fpy.li/4b["Conteúdo do Módulo"] em subseções, sob a supervisão de Guido van Rossum.] Avisos de descontinuação não serão emitidos pelo interpretador Python, porque os checadores de tipos devem sinalizar os tipos descontinuados quando o programa sendo checado tiver como alvo Python 3.9 ou posterior. . Remover aqueles tipos genéricos redundantes na primeira versão de Python lançada cinco anos após Python 3.9. No ritmo atual, esse deverá ser Python 3.14, também conhecido como Python Pi. **** @@ -1332,7 +1332,7 @@ Isso é tratado na <>, sobre `typing.TypedDict`. [[type_hint_abc_sec]] -==== Classes bases abstratas +==== Classes base abstratas [quote, lei de Postel, ou o Princípio da Robustez] @@ -1408,7 +1408,7 @@ https://fpy.li/pep585[PEP 585—Type Hinting Generics In Standard Collections] ( Para encerrar nossa discussão de ABCs em dicas de tipo, precisamos falar sobre as ABCs `numbers`. -[[numeric_tower_warning]] +[[numeric_tower_warning_sec]] ===== A queda da torre numérica O((("numbers package")))((("numeric tower"))) pacote https://fpy.li/4d[`numbers`] define a assim chamada _torre numérica_ (_numeric tower_) descrita na https://fpy.li/pep3141[PEP 3141—A Type Hierarchy for Numbers] (EN). @@ -1529,7 +1529,7 @@ Uma função deve ser mais precisa sobre o tipo concreto que retorna. O tipo `Iterator`, usado como tipo do retorno no <>, está intimamente relacionado a `Iterable`. -Voltaremos a ele em <>, que trata de geradores e +Voltaremos a ele no <>, que trata de geradores e iteradores clássicos.((("", startref="GTSiterable08")))((("", startref="iterable08"))) [[param_generics_typevar_sec]] @@ -1865,12 +1865,12 @@ include::../code/08-def-type-hints/comparable/top.py[tags=TOP] ---- ==== -Vamos testar `top`. O <> mostra parte de uma bateria de testes para uso com o `pytest`. +Vamos testar `top`. O <> mostra parte de uma bateria de testes para uso com o `pytest`. Ele tenta chamar `top` primeiro com um gerador de expressões que produz `tuple[int, str]`, e depois com uma lista de `object`. Com a lista de `object`, esperamos receber uma exceção de `TypeError`. -[[top_protocol_test]] +[[ex_top_protocol_test]] ._top_test.py_: visão parcial da bateria de testes para `top` ==== [source, python] diff --git a/online/cap09.adoc b/online/cap09.adoc index f098089..9f57e59 100644 --- a/online/cap09.adoc +++ b/online/cap09.adoc @@ -73,8 +73,8 @@ apresento o decorador de _caching_ `functools.cache` do Python A <> apresenta também o uso de `lru_cache` sem argumentos, uma novidade do Python 3.8. -Expandi a <> para incluir dicas de tipo, a sintaxe -recomendada de usar `functools.singledispatch` desde o Python 3.7. +Expandi a <> para incluir dicas de tipo, a sintaxe +recomendada para usar `functools.singledispatch` desde o Python 3.7. A <> agora inclui o <>, que usa uma classe e não uma clausura para implementar um decorador. @@ -255,7 +255,7 @@ frameworks Python para adicionar funções a um registro central—por exemplo, registro mapeando padrões de URLs para funções que geram respostas HTTP. Tais decoradores de registro podem ou não modificar as funções decoradas. -Vamos ver um decorador de registro em ação na <> (<>). +Vamos ver um decorador de registro em ação na <> (<>). A maioria dos decoradores modifica a função decorada. Eles normalmente fazem isso definindo e devolvendo uma função interna para substituir a função @@ -1088,7 +1088,7 @@ def costly_function(a, b): Vamos agora examinar outro decorador poderoso: `functools.singledispatch`. -[[generic_functions_sec]] +[[single_dispatch_sec]] ==== Funções genéricas com despacho único Imagine((("single dispatch generic functions", @@ -1197,7 +1197,7 @@ o Mypy 0.770 reclama quando vê múltiplas funções com o mesmo nome.] <4> Registra uma nova função para cada tipo que precisa de tratamento especial, com uma dica de tipo correspondente no primeiro parâmetro. <5> As ABCs em `numbers` são úteis para uso em conjunto com -`singledispatch`.footnote:[Apesar do alerta em <>, +`singledispatch`.footnote:[Apesar do alerta em <>, as ABCs de `numbers` não foram descontinuadas, e você as encontra em código de Python 3.] <6> `bool` é um _subtipo-de_ `numbers.Integral`, mas a lógica de `singledispatch` busca a implementação com o tipo correspondente mais específico, diff --git a/online/cap10.adoc b/online/cap10.adoc index 4803fee..bfa8ace 100644 --- a/online/cap10.adoc +++ b/online/cap10.adoc @@ -73,7 +73,7 @@ Vamos também discutir uma abordagem similar para simplificar o padrão Comando. Movi((("functions, design patterns with first-class", "significant changes to"))) este capítulo para o final da Parte II, para poder então aplicar o -decorador de registro na <>, e também usar dicas de tipo nos +decorador de registro na <>, e também usar dicas de tipo nos exemplos. A maior parte das dicas de tipo usadas nesse capítulo são simples, e ajudam na legibilidade. @@ -256,9 +256,8 @@ include::../code/10-dp-1class-func/strategy.py[tags=STRATEGY_TESTS] <3> Uma função de promoção diferente é usada aqui e no teste seguinte. -Observe os textos explicativos do <>: não há necessidade -de instanciar um novo objeto `promotion` com cada novo pedido: -as funções já estão prontas para usar. +Note que não precisamos criar uma nova instância de `promotion` a +cada novo pedido: as funções já estão prontas para usar. É interessante notar que no _Padrões de Projetos_, os autores sugerem que: "Objetos Estratégia muitas vezes são bons "peso mosca" @@ -417,8 +416,8 @@ promocional seria usar um decorador simples. É nosso próximo tópico.((("", startref="FDPrefactor10")))((("", startref="RSfind10"))) -[[decorated_strategy]] -=== Estratégia melhorado com decorador +[[decorated_strategy_sec]] +=== Estratégia com decorador de registro Lembre-se((("functions, design patterns with first-class", "decorator-enhanced strategy pattern", id="FDPdecorator10")))((("refactoring strategies", @@ -655,7 +654,7 @@ discussion")))((("Soapbox sidebars", "design patterns"))) tem funções de primeira classe e tipos de primeira classe, e Norvig afima que esses recursos afetam 10 dos 23 padrões (slide 10 de https://fpy.li/norvigdp[_Design Patterns in Dynamic Languages_]). -Na <>, vimos que Python também tem funções +Na <>, vimos que Python também tem funções genéricas de despacho único, uma forma limitada dos multi-métodos do CLOS, que Gamma et al. sugerem como uma maneira mais simples de implementar o padrão clássico Visitante (_Visitor_). Norvig, por outro lado, diz (no slide 10) diff --git a/online/cap11.adoc b/online/cap11.adoc index 94c970a..5967780 100644 --- a/online/cap11.adoc +++ b/online/cap11.adoc @@ -139,11 +139,12 @@ função `copy.copy` é mais segura e rápida.] <9> `bool` usa o método `+__bool__+` para devolver `False` se o `Vector2d` tiver magnitude zero, caso contrário esse método devolve `True`. -O `Vector2d` do <> é implementado em _vector2d_v0.py_ (no -<>). O código está baseado no <>, exceto pelos -métodos para os operadores `+` e `*`, que veremos mais tarde no -<>. Vamos acrescentar o método para `==`, já que ele é útil -para testes. Nesse ponto, `Vector2d` usa vários métodos especiais para oferecer +A classe `Vector2d` do <> é implementada em _vector2d_v0.py_, +(<>). O código está baseado no <> do <>, +exceto pelos +métodos para os operadores `{plus}` e `*`, que veremos mais tarde no +<>. Vamos acrescentar o método para `==`, pois ele facilita +escrever testes. Nesse ponto, `Vector2d` usa vários métodos especiais para oferecer operações que um pythonista espera encontrar em um objeto bem projetado. [[ex_vector2d_v0]] @@ -555,7 +556,7 @@ ou usá-los como chaves em um `dict`.((("", startref="dispform11")))((("", startref="POformat11"))) -[[hashable_vector2d]] +[[hashable_vector2d_sec]] === Um Vector2d _hashable_ Da((("Pythonic objects", "hashable Vector2d", id="POhash11")))((("Vector2d", @@ -685,10 +686,10 @@ construtor embutido `complex()`. Talvez `Vector2d` pudesse oferecer o startref="POhash11"))) [[positional_pattern_implement_sec]] -=== Suportando o pattern matching posicional +=== Suportando o casamento de padrões posicionais Até aqui, instâncias de `Vector2d`((("Pythonic objects", "supporting positional -patterns")))((("positional patterns"))) são compatíveis com o _pattern matching_ +patterns")))((("positional patterns"))) são compatíveis com o casamento de padrões com instâncias de classe—vistos na <>. No <>, todos aqueles padrões nomeados funcionam como esperado. @@ -719,7 +720,7 @@ TypeError: Vector2d() accepts 0 positional sub-patterns (1 given) Para fazer `Vector2d` funcionar com padrões posicionais, precisamos acrescentar um atributo de classe chamado `+__match_args__+`, listando os atributos de -instância na ordem em que eles serão usados no pattern matching posicional. +instância na ordem em que eles serão usados no casamento de padrões posicionais. [source, python] @@ -760,7 +761,7 @@ trabalhando no `Vector2d` há algum tempo, mostrando apenas trechos isolados. O _vector2d_v3.py_, incluindo os doctests que usei durante o desenvolvimento. [[ex_vector2d_v3_full]] -.vector2d_v3.py: o pacote completo +.vector2d_v3.py: o módulo completo ==== [source, python] ---- @@ -834,8 +835,8 @@ O <> mostra o resultado na classe `Vector2d` do <> ilustra outro dispositivo de proteção. +pode evitar acessos acidentais, mas não ataques intencionais. +A <> mostra um dispositivo de proteção. [[safety_fig]] .A capa sobre um interruptor é um dispositivo de proteção, não de segurança: previne acidentes, não sabotagem @@ -849,9 +850,9 @@ atribuir um valor a um componente privado de um `Vector2d`, escrevendo não poderá reclamar se alguma coisa explodir. A funcionalidade de desfiguração de nomes não é amada por todos os pythonistas, -nem tampouco a aparência estranha de nomes escritos como `self.__x`. Muitos +nem tampouco a aparência estranha de nomes escritos como `+self.__x+`. Muitos preferem evitar essa sintaxe e usar apenas um sublinhado no prefixo para -"proteger" atributos por convenção: `self._x`. Críticos da +"proteger" atributos por convenção: `+self._x+`. Críticos da desfiguração automática com o sublinhado duplo dizem que preocupações com modificações acidentais a atributos devem ser tratadas através de convenções de nomenclatura. O criador do _pip_, _virtualenv_ e outros @@ -1032,7 +1033,7 @@ include::../code/11-pythonic-obj/vector2d_v3_slots.py[tags=VECTOR2D_V3_SLOTS] # methods are the same as previous version ---- ==== -<1> `+__match_args__+` lista os nomes dos atributos públicos, para _pattern matching_ posicional. +<1> `+__match_args__+` lista os nomes dos atributos públicos, para casamento de padrões posicionais. <2> `+__slots__+`, por outro lado, lista os nomes dos atributos de instância, que neste caso são atributos privados. Para medir a economia de memória, escrevi o script _mem_test.py_. Ele recebe, @@ -1090,7 +1091,7 @@ vagos com `Foo` e `Bar`. ==== -[[problems_with_slots]] +[[problems_with_slots_sec]] ==== Resumindo os problemas com __slots__ O atributo de classe `+__slots__+` pode proporcionar uma economia significativa @@ -1109,12 +1110,12 @@ menos que nomeiem `+__dict__+` explicitamente em `+__slots__+`. * Instâncias não podem ser alvo de referências fracas, a menos que `+__weakref__+` seja incluído em `+__slots__+`. -O último tópico do capítulo trata da sobreposição de um atributo de classe em -instâncias e subclasses.((("", startref="POslot11")))((("", +O último tópico do capítulo trata de sobrescrever de um atributo de classe +em instâncias e subclasses.((("", startref="POslot11")))((("", startref="slots11")))((("", startref="memsave11"))) [[overriding_class_attributes]] -=== Sobrepondo atributos de classe +=== Sobrescrevendo atributos de classe Um((("Pythonic objects", "overriding class attributes", id="POoverride11")))((("attributes", "overriding class attributes", @@ -1275,8 +1276,8 @@ mas torna a classe mais fácil de testar. Ao expandir o código do `Vector2d` meu objetivo foi criar um contexto para a discussão dos métodos especiais e outras convenções de programação em Python. -Os exemplos neste capítulo demonstraram vários dos métodos especiais vistos -antes na <> (<>): +Os exemplos neste capítulo demonstraram vários dos métodos especiais mencionados +no https://fpy.li/1[«Capítulo 1»] (vol.1): * Métodos de representação de strings e bytes: `+__repr__+`, `+__str__+`, `+__format__+` e `+__bytes__+` @@ -1315,7 +1316,7 @@ grande de instâncias—pense em milhões de instâncias, não apenas milhares. muitos destes casos, usar a https://fpy.li/pandas[pandas] pode ser a melhor opção. -O último tópico tratado foi a sobreposição de um atributo de classe acessado +O último tópico tratado foi a sobrescrita de um atributo de classe acessado através das instâncias (por exemplo, `self.typecode`). Fizemos isso primeiro criando um atributo de instância, depois criando uma subclasse e sobrescrevendo o atributo no nível da classe. @@ -1326,7 +1327,7 @@ resumido em uma só frase, seria essa: [quote, Antigo provérbio chinês] ____ -Para criar objetos pythônicos, observe como se comportam objetos reais de Python. +Para criar objetos pythônicos, observe como se comportam os objetos do Python. ____ [[pythonic_reading_sec]] diff --git a/online/cap12.adoc b/online/cap12.adoc index 7b5d69a..ae98bc0 100644 --- a/online/cap12.adoc +++ b/online/cap12.adoc @@ -117,12 +117,11 @@ Quando um `Vector` tem mais de seis componentes, a string produzida por `repr()` `\...`, como visto na última linha do <>. Isso é fundamental para qualquer tipo de coleção que possa conter um número grande de itens, pois `repr` é usado na depuração—e você não quer que um único objeto grande ocupe milhares de linhas em seu console ou arquivo de log. Use o módulo `reprlib` para produzir representações de tamanho limitado, como no <>. O módulo `reprlib` se chamava `repr` no Python 2.7. ==== -O <> lista a implementação de nossa primeira versão de `Vector` -(esse exemplo usa como base o código mostrado no <> e -<> do <>). +O <> é a primeira versão de `Vector` +baseada no <> e <> do <>. [[ex_vector_v1]] -.vector_v1.py: derived from vector2d_v1.py +.vector_v1.py: baseado em vector2d_v1.py ==== [source, python] ---- @@ -161,8 +160,8 @@ dentro de um `Vector` é um detalhe de implementação. Como essas chamadas ao construtor criam objetos `Vector` idênticos, preferi a sintaxe mais simples, usando um argumento `list`. -Ao escrever o `+__repr__+`, eu poderia construir uma string para exibição -simplificada de `components` com este código: +Ao escrever o `+__repr__+`, eu poderia construir uma string para exibir +`components` com este código: `reprlib.repr(list(self._components))`. Mas isto teria um custo adicional, pois eu estaria copiando cada item de `self._components` para uma `list` só para usar a `list` no `repr`. Em vez disso, decidi aplicar @@ -212,10 +211,11 @@ protocolo de sequência no Python implica apenas no métodos `+__len__+` e assinatura e a semântica padrão, pode ser usada em qualquer lugar onde uma sequência é esperada. É irrelevante se `Spam` é uma subclasse dessa ou daquela outra classe; tudo o que importa é que ela fornece os métodos necessários. Vimos -isso no <>, reproduzido aqui no <>. +isso no <> do <>, +reproduzido no <>. [[ex_pythonic_deck_rep]] -.Código do <>, reproduzido aqui por conveniência +.Código do <> do <>, reproduzido aqui por conveniência ==== [source, python] ---- @@ -239,24 +239,25 @@ se exatamente como a classe será utilizada. Por exemplo, apenas `+__getitem__+` é necessário para suportar iteração; não é preciso implemtar `+__len__+`. -[role="man-height4"] + [TIP] ==== + Com((("protocol classes")))((("protocols", "static protocols"))) a https://fpy.li/pep544[_PEP 544—Protocols: Structural subtyping (static duck typing)_] (Protocolos: sub-tipagem estrutural (tipagem pato estática))], o Python 3.8 suporta _classes protocolo_: subclasses de `typing.Protocol`, -que estudamos na <>. -Esse novo uso da palavra protocolo no Python tem um significado parecido, mas não idêntico. +que estudamos na https://fpy.li/8m[«Seção 8.5.10»] (vol.1). +Este novo uso da palavra "protocolo" no Python tem um significado parecido, mas não idêntico. Quando preciso diferenciá-los, escrevo((("static protocols", "versus dynamic protocols", secondary-sortas="dynamic protocols"))) - -_protocolo estático_ para me referir aos protocolos formalizados em classes -protocolo (subclasses de `typing.Protocol`), e((("dynamic protocols"))) -_protocolos dinâmicos_ para o sentido tradicional. Uma diferença fundamental é +"protocolo estático" para me referir a um protocolos formalizado por uma classe +subclasse de `typing.Protocol`, e((("dynamic protocols"))) +"protocolo dinâmico" para me referir ao sentido tradicional. +Uma diferença fundamental é que uma implementação de um protocolo estático precisa oferecer todos os métodos definidos na classe protocolo. A <> -(<>) traz mais detalhes. +apresentará muito mais detalhes. ==== @@ -280,8 +281,7 @@ são um bom começo: [source, python] ---- class Vector: - # many lines omitted - # ... + # muitas linhas omitidas... def __len__(self): return len(self._components) @@ -290,7 +290,7 @@ class Vector: return self._components[index] ---- -Após tais acréscimos, agora as seguintes operações funcionam: +Com tais acréscimos, as seguintes operações funcionam: [source, python] ---- @@ -373,14 +373,14 @@ Vamos agora olhar mais de perto a própria classe `slice`, no <>. 'indices', 'start', 'step', 'stop'] ---- ==== -<1> `slice` é um tipo embutido (que já vimos antes na <>). +<1> `slice` é um tipo embutido (que já vimos antes na <>). <2> Inspecionando uma `slice` descobrimos os atributos de dados `start`, `stop`, e `step`, e um método `indices`. No <>, a chamada `dir(slice)` revela um atributo `indices`, um método pouco conhecido mas muito interessante. Eis o que diz `help(slice.indices)`: -`S.indices(len) -> (start, stop, stride)`:: +`S.indices(len) {rt-arrow} (start, stop, stride)`:: Supondo uma sequência de tamanho `len`, calcula os índices `start` (_início_) e `stop` (_fim_), e a extensão do `stride` (_passo_) da fatia estendida descrita por `S`. Índices fora dos limites são recortados, exatamente como acontece em uma fatia normal. Em outras palavras, o método `indices` expõe a lógica complexa implementada nas @@ -492,7 +492,7 @@ primeiros componentes de um vetor: ---- No `Vector2d`, oferecemos acesso somente para leitura a `x` e `y` através do -decorador `@property` (veja o <>). Poderíamos incluir quatro +decorador `@property` (veja o <> do <>). Poderíamos incluir quatro propriedades no `Vector`, mas isso seria tedioso. O método especial `+__getattr__+` é uma opção melhor. @@ -501,7 +501,7 @@ atributo falha. Simplificando, dada a expressão `my_obj.x`, Python verifica se instância de `my_obj` tem um atributo chamado `x`; em caso negativo, a busca passa para a classe (`+my_obj.__class__+`) e depois sobe pelo diagrama de herança.footnote:[A pesquisa de atributos é mais complicada que isso; veremos -todos detalhes terríveis do processo no <>. Por ora, essa +todos os detalhes sinistros na <>. Por ora, esta explicação simplificada nos serve.] Se por fim o atributo `x` não for encontrado, o método `+__getattr__+`, definido na classe de `my_obj`, é chamado com `self` e o nome do atributo em formato de string (por exemplo, `'x'`). @@ -519,9 +519,9 @@ include::../code/12-seq-hacking/vector_v3.py[tags=VECTOR_V3_GETATTR] ---- ==== -<1> Define `+__match_args__+` para permitir _pattern matching_ posicional sobre +<1> Define `+__match_args__+` para permitir casamento de padrões posicionais sobre os atributos dinâmicos suportados por `+__getattr__+`.footnote:[Apesar de -`+__match_args__+` existir para suportar _pattern matching_ desde o Python 3.10, +`+__match_args__+` existir para suportar casamento de padrões desde o Python 3.10, é inofensivo definir este atributo em versões anteriores da linguagem. Na primeira edição chamei este atributo de `shortcut_names`. Com o novo nome, ele cumpre dois papéis: suportar padrões posicionais em instruções `case` e guardar @@ -622,7 +622,7 @@ fornece uma maneira de acessar dinamicamente métodos de superclasses, uma necessidade em uma linguagem dinâmica que suporta herança múltipla, como Python. Ela é usada para delegar alguma tarefa de um método em uma subclasse para um método adequado em uma superclasse, como visto no <>. -Falaremos mais sobre `super` na <>. +Falaremos mais sobre `super` na <>. ==== @@ -632,7 +632,7 @@ um par de atributos de dados `real` and `imag`. Tentar mudar qualquer um dos dois em uma instância de `complex` gera um `AttributeError` com a mensagem `+"can't set attribute"+` ("não é possível setar o atributo"). Por outro lado, a tentativa de modificar um atributo protegido por uma propriedade, como -fizemos no <>, produz a mensagem `"read-only attribute"` +fizemos no <>, produz a mensagem `"read-only attribute"` ("atributo apenas para leitura"). Eu me inspirei em ambas as frases para definir a string `error` em `+__setitem__+`, mas fui mais explícito sobre os atributos proibidos. @@ -647,7 +647,7 @@ conflitos com os atributos suportados apenas para leitura: `x`, `y`, `z`, e `t`. Sabendo((("__slots__"))) que declarar `+__slots__+` no nível da classe impede a definição de novos atributos de instância, é tentador usar esse recurso em vez de implementar `+__setattr__+` como fizemos. -Entretanto, por todas as ressalvas discutidas na <>, usar +Entretanto, por todas as ressalvas discutidas na <>, usar `+__slots__+` apenas para prevenir a criação de atributos de instância não é recomendado. `+__slots__+` deve ser usado apenas para economizar memória, e apenas quando isso for um problema real. @@ -674,7 +674,7 @@ id="hash12")))((("__eq__", id="eq12"))) novamente implementar um método `+__hash__+`. Juntamente com o `+__eq__+` existente, isso tornará as instâncias de `Vector` _hashable_. -O `+__hash__+` do `Vector2d` (no <>) computava o _hash_ de +O `+__hash__+` do `Vector2d` (no <> do <>) computava o _hash_ de uma `tuple` construída com os dois componentes, `self.x` and `self.y`. Agora podemos estar lidando com milhares de componentes, então criar uma `tuple` pode ser caro demais. Em vez disso, vou aplicar sucessivamente o operador `^` (xor) @@ -715,7 +715,7 @@ Veja como `reduce` pode ser usada para computar `5!` (o fatorial de 5): Voltando a nosso problema de _hash_, o <> demonstra a ideia da computação de um xor agregado, fazendo isso de três formas diferente: com um -loop `for` e com dois modos diferentes de usar `reduce`. +laço `for` e com dois modos diferentes de usar `reduce`. [[ex_reduce_xor]] .Três maneiras de calcular o xor acumulado de inteiros de 0 a 5 @@ -736,12 +736,12 @@ loop `for` e com dois modos diferentes de usar `reduce`. 1 ---- ==== -<1> xor agregado com um loop `for` e uma variável de acumulação. +<1> xor agregado com um laço `for` e uma variável de acumulação. <2> `functools.reduce` usando uma função anônima. <3> `functools.reduce` substituindo a `lambda` customizada por `operator.xor`. Das alternativas apresentadas no <>, a última é minha favorita, e -o loop `for` vem a seguir. Qual sua preferida? +o laço `for` vem a seguir. Qual sua preferida? Como visto na <>, `operator` oferece a funcionalidade de todos os operadores infixos de Python em formato de função, diminuindo a @@ -796,19 +796,19 @@ Ao usar `reduce`, é uma boa prática fornecer o terceiro argumento, ("reduce() de uma sequência vazia sem valor inicial", uma mensagem bem escrita: explica o problema e diz como resolvê-lo). O `initializer` é o valor devolvido se a sequência for vazia e -é usado como primeiro argumento no loop de redução, +é usado como primeiro argumento no laço de redução, e portanto deve ser o elemento neutro da operação. -Assim, o `initializer` para `+`, `|`, `^` (xor) deve ser `0`, +Assim, o `initializer` para `{plus}`, `|`, `^` (xor) deve ser `0`, mas para `*` e `&` deve ser `1`. ==== Da forma como está implementado, o método `+__hash__+` no <> é um -exemplo perfeito de uma computação de map-reduce (_mapeia e reduz_). Veja a +exemplo perfeito de uma do padrão _map-reduce_ (mapear e reduzir). Veja a (<>). [[map_reduce_fig]] -.Map-reduce: aplica uma função a cada item para gerar uma nova série (map), e então computa o agregado (reduce). +.Map-reduce: `map` aplica uma função a cada item, gerando uma nova série , `reduce` computa o agregado. image::../images/flpy_1202.png[align="center",pdfwidth=7cm] A etapa de mapeamento produz um _hash_ para cada componente, e a etapa de @@ -834,7 +834,7 @@ do <>. ==== E enquanto estamos falando de funções de redução, podemos substituir nossa implementação apressada de `+__eq__+` com uma outra, menos custosa em termos de processamento e uso de memória, pelo menos para vetores grandes. -Como visto no <>, temos esta implementação bastante concisa de `+__eq__+`: +Como visto no <> do <>, temos esta implementação bastante concisa de `+__eq__+`: [source, python] ---- @@ -853,7 +853,7 @@ Mas não para grandes vetores multidimensionais. Uma forma melhor de comparar um `Vector` com outro `Vector` ou iterável seria o código do <>. [[ex_eq_loop]] -.A implementação de `+Vector.__eq__+` usando `zip` em um loop `for`, para uma comparação mais eficiente +.A implementação de `+Vector.__eq__+` usando `zip` em um laço `for`, para uma comparação mais eficiente ==== [source, python] ---- @@ -866,9 +866,8 @@ Mas não para grandes vetores multidimensionais. Uma forma melhor de comparar um return True # <4> ---- ==== -<1> Se as `len` dos objetos são diferentes, eles não são iguais. -Este teste é importante porque o `zip` encerra a iteração -assim que o primeiro argumento é consumido. +<1> Objetos de tamanho diferentes não são iguais. +Teste necessário porque `zip` retorna quando termina o iterável menor. <2> `zip` produz um gerador de tuplas criadas a partir dos itens em cada argumento iterável. <3> Sai assim que dois componentes sejam diferentes, devolvendo `False`. <4> Caso contrário, os objetos são iguais. @@ -896,7 +895,7 @@ um dos argumentos é consumido. Veja a caixa <> logo adiante para saber mais sobre zip. ==== -O <> é eficiente, mas a função `all` pode produzir a mesma computação de um agregado do loop `for` em apenas uma linha: +O <> é eficiente, mas a função `all` pode produzir a mesma computação de um agregado do laço `for` em apenas uma linha: se todas as comparações entre componentes correspoendentes nos operandos forem `True`, o resultado é `True`. Assim que uma comparação é `False`, `all` devolve `False`. O <> mostra um `+__eq__+` usando `all`. @@ -923,7 +922,7 @@ startref="SSMhasheq12")))((("", startref="VCMhasheq12"))) .O fantástico zip **** -Ter um loop `for` que itera sobre itens sem perder tempo com variáveis de índice +Ter um laço `for` que itera sobre itens sem perder tempo com variáveis de índice é muito bom e evita muitos bugs, mas exige algumas funções utilitárias especiais. Uma delas é a função embutida `zip`, que facilita a iteração em paralelo sobre dois ou mais iteráveis, devolvendo tuplas que você pode @@ -973,7 +972,7 @@ Se você quiser entender `zip`, passe algum tempo considerando como esses exemplos funcionam. A função embutida `enumerate` é outra função geradora usada com frequência em -loops `for`, para evitar manipulação direta de variáveis índice. Quem não +laços `for`, para evitar manipulação direta de variáveis índice. Quem não estiver familiarizado com `enumerate` deve estudar a seção dedicada a ela na documentação das https://fpy.li/6d[Funções embutidas]. @@ -1280,9 +1279,9 @@ o uso do `*` como opção de formatação. Na((("Soapbox sidebars", "Pythonic sums", id="SSpysum12")))((("Pythonic sums", id="pysum12"))) -https://fpy.li/12-11[Python-list], há uma thread de abril de 2003 chamada -https://fpy.li/12-12[Pythonic Way to Sum n-th List Element?] (A forma -pythônica de somar o n-ésimo elemento em listas). +https://fpy.li/12-11[_python-list_], há uma thread de abril de 2003 intitulada +https://fpy.li/12-12[_Pythonic Way to Sum n-th List Element?_] +(A forma pythônica de somar o n-ésimo elemento em listas). Não há uma resposta única para a "O que é pythônico?", da mesma forma que não há uma resposta única para "O que é belo?" @@ -1372,7 +1371,7 @@ de David Eppstein sobre ele: ____ Se você quer a soma de uma lista de itens, deveria escrever algo como -"a soma de uma lista de itens", não como "faça um loop sobre +"a soma de uma lista de itens", não como "faça um laço sobre esses itens, mantenha uma variável `ac`, execute uma série de somas". Por que temos linguagens de alto nível, senão para expressar nossas intenções em um nível mais alto e deixar a linguagem se preocupar diff --git a/online/cap13.adoc b/online/cap13.adoc index cd946c7..6f0b655 100644 --- a/online/cap13.adoc +++ b/online/cap13.adoc @@ -14,37 +14,37 @@ A programação orientada a objetos((("interfaces", "role in object-oriented pro tem tudo a ver com interfaces. A melhor forma de entender um tipo em Python é conhecer os métodos que aquele tipo oferece—sua interface—como vimos na -<> (<>). +<>. Desde o Python 3.8, temos quatro maneiras de definir e usar interfaces. Elas estão ilustradas no _Mapa de Sistemas de Tipagem_ (<>). [[type_systems_described]] -.Na metade superior, checagens de tipo dinâmicas (em tempo de execução) usando só o interpretador Python; a metade inferior requer um checador estático externo como o Mypy, ou um IDE como a PyCharm. Os quadrantes da esquerda se referem a tipagem baseada na estrutura do objeto—isto é, os métodos oferecidos pelo objeto, independente de sua classe ou superclasses; os quadrantes da direita dependem de tipos explicitamente nomeados no código: a classe do objeto, ou suas superclasses. +.Na metade superior, checagens de tipo dinâmicas (em tempo de execução) usando só o interpretador Python; a metade inferior requer um checador estático externo como o Mypy, ou um IDE como o PyCharm. Os quadrantes da esquerda se referem à tipagem baseada na estrutura do objeto—isto é, os métodos oferecidos pelo objeto, independente de sua classe ou superclasses; os quadrantes da direita dependem de tipos explicitamente nomeados no código: a classe do objeto, ou suas superclasses. image::../images/mapa-da-tipagem.png[align="center",pdfwidth=12cm] Podemos as quatro abordagens assim: Tipagem pato (_duck typing_):: - A((("duck typing"))) tratamento padrão para tipos em Python desde o início. + O((("duck typing"))) tratamento padrão para tipos em Python desde o início. Estamos estudando tipagem pato desde o primeiro capítulo do volume 1. Tipagem ganso (_goose typing_):: A((("goose typing", "definition of term"))) abordagem suportada pelas classes base abstratas (ABCs, _sigla em inglês para Abstract Base Classes_) desde Python 2.6, que depende de checar objetos contra ABCs durante a execução. - A tipagem ganso é um dos principais temas desse capítulo. + A tipagem ganso é um dos principais temas deste capítulo. Tipagem estática:: A((("static typing"))) abordagem tradicional das linguagens de tipos estáticos como C e Java; suportada desde o Python 3.5 pelo módulo `typing`, e aplicada por checadores de tipos externos compatíveis com a https://fpy.li/pep484[PEP 484—Type Hints]. - Este não é o foco desse capítulo. + Este não é o foco deste capítulo. A maior parte do <> e do <> mais adiante são sobre tipagem estática. Tipagem pato estática (_static duck typing_):: Uma((("static duck typing"))) abordagem popularizada pela linguagem Go; suportada por subclasses de `typing.Protocol`—lançada no Python 3.8 e também aplicada com o suporte de checadores de tipos externos. - Tratamos desse tema pela primeira vez em <> (<>), + Tratamos desse tema pela primeira vez na <>, e continuamos nesse capítulo. @@ -54,7 +54,7 @@ As((("interfaces", "typing map")))((("typing map"))) quatro abordagens retratada <> são complementares: elas têm diferentes prós e contras. Não faz sentido descartar qualquer uma delas. -Cada uma dessas quatro abordagens dependem de interfaces para funcionarem, mas a +Cada uma dessas quatro abordagens depende de interfaces para funcionar, mas a tipagem estática pode ser implementada de forma limitada usando apenas tipos concretos em vez de abstrações de interfaces como protocolos e classes base abstratas. @@ -66,14 +66,13 @@ capítulo está dividido em quatro seções principais, tratando de três dos qu quadrantes no Mapa de Sistemas de Tipagem. (<>): * A <> compara duas formas de tipagem estrutural com -protocolos + -—o lado esquerdo do Mapa. +protocolos—o lado esquerdo do Mapa. -* A <> se aprofunda na tipagem pato que já é familiar para +* A <> se aprofunda na tipagem pato, que já é familiar para quem programa em Python. Vamos ver como fazê-la mais segura, preservando sua melhor qualidade: a flexibilidade. -* A <> explica o uso de ABCs para um checagem de tipo mais +* A <> explica o uso de ABCs para uma checagem de tipo mais estrita durante a execução do código. É a seção mais longa, não por ser a mais importante, mas porque há mais seções sobre tipagem pato, tipagem pato estática e tipagem estática em outras partes do livro. @@ -100,19 +99,19 @@ conteúdo novo—e de todos os outros capítulos relacionados à tipagem em Pyth * A <> explica as semelhanças e diferenças entre protocolos dinâmicos e estáticos. -* A <> praticamente reproduz o conteúdo da primeira +* A <> reproduz praticamente o conteúdo da primeira edição, mas foi atualizada e agora tem um título de seção que enfatiza sua importância. * A <> é toda nova. Ela se apoia na apresentação inicial na -<> (<>). +<>. * Os diagramas de classe de `collections.abc` neste capítulo foram atualizados para incluir a `Collection` ABC, do Python 3.6. Na primeira edição de _Python Fluente_ escrevi uma seção encorajando o uso das ABCs do módulo `numbers` para tipagem ganso. -Na <> explico porque, atualmente, é melhor usar +Na <> explico por que, atualmente, é melhor usar protocolos numéricos estáticos do módulo `typing` como `SupportsFloat` se você planeja usar checadores de tipos estáticos, ou checagem durante a execução no estilo da tipagem ganso. @@ -198,7 +197,7 @@ Agora, com a adoção da https://fpy.li/pep544[_PEP 544—Protocols: Structural subtyping (static duck typing)_] no Python 3.8, a palavra "protocolo" ganhou um novo sentido em Python—um sentido próximo, mas diferente. -Como vimos na <> (<>), +Como vimos na <>, a PEP 544 nos permite criar subclasses de `typing.Protocol` para definir um ou mais métodos que uma classe deve implementar (ou herdar) para satisfazer um checador de tipos estático. @@ -228,7 +227,7 @@ precise de todos eles. * Protocolos estáticos podem ser inspecionados por checadores de tipos estáticos, protocolos dinâmicos não. -Os dois tipos de protocolo compartilham um característica essencial: uma classe +Os dois tipos de protocolo compartilham uma característica essencial: uma classe nunca precisa declarar que suporta um protocolo pelo nome, isto é, por herança. Antes dos protocolos estáticos, Python já oferecia outra forma de definir uma @@ -267,7 +266,7 @@ image::../images/flpy_1302.png[align="center",pdfwidth=10cm] [TIP] ==== -A maior parte das ABCs no módulo `collections.abc` existem para formalizar +A maior parte das ABCs no módulo `collections.abc` existe para formalizar interfaces que já eram implementadas por objetos nativos e implicitamente suportadas pelo interpretador, muito antes daquele módulo existir. As ABCs são úteis como pontos de partida para novas classes, e @@ -294,12 +293,12 @@ Em resumo, dada a importância das sequências como estruturas de dados, Python fazer a iteração e o operador `in` funcionarem invocando `+__getitem__+` quando `+__iter__+` e `+__contains__+` não estão presentes. -O `FrenchDeck` original de <> também não é subclasse de +O `FrenchDeck` original do <> também não é subclasse de `abc.Sequence`, mas ele implementa os dois métodos do protocolo de sequência: `+__getitem__+` e `+__len__+`. Veja o <>. [[ex_pythonic_deck_repeat]] -.Um deque como uma sequência de cartas (igual ao <>) +.Um baralho como uma sequência de cartas, como o <> do <>. ==== [source, python] ---- @@ -308,11 +307,11 @@ include::../code/01-data-model/frenchdeck.py[] ==== Muitos dos exemplos no <> funcionam por causa do tratamento -especial que Python dá a qualquer estrutura vagamente semelhante a uma -sequência. O protocolo iterável em Python representa uma forma extrema de tipagem pato: +especial que Python dá a estruturas vagamente semelhantes a uma sequência. +O protocolo iterável em Python representa uma forma extrema de tipagem pato: o interpretador tenta dois métodos diferentes para iterar sobre objetos. -Para deixar mais claro, os comportamentos que que descrevi nessa seção estão +Para deixar mais claro, os comportamentos que descrevi nessa seção estão implementados no próprio interpretador, na maioria dos casos em C. Eles não dependem dos métodos da ABC `Sequence`. Por exemplo, os métodos concretos `+__iter__+` e `+__contains__+` na classe `Sequence` emulam comportamentos @@ -320,7 +319,7 @@ internos do interpretador Python. Se tiver curiosidade, veja o código-fonte destes métodos em https://fpy.li/13-3[_Lib/_collections_abc.py_]. Agora vamos estudar um exemplo que demonstra por que checadores de tipos -estáticos não têm lidar com protocolos +estáticos não têm como lidar com protocolos dinâmicos.((("", startref="Pseqit13")))((("", startref="seqpro13")))((("", startref="itpro13"))) @@ -331,9 +330,9 @@ _Monkey patching_((("protocols", "implementing at runtime", id="Prun13")))((("monkey-patching", id="monkey13"))) é o ato de remendar (_patch_) dinamicamente um programa durante a execução do código (_runtime_), para acrescentar funcionalidade ou corrigir bugs. Por exemplo, a biblioteca de -rede https://www.gevent.org/api/gevent.monkey.html[_gevent_] faz "monkey patch" +rede https://fpy.li/13-5[_gevent_] faz "monkey patch" em partes da biblioteca padrão de Python, para permitir concorrência sem threads ou -`async`/`await`.footnote:[O artigo https://fpy.li/13-4["Monkey patch"] na +`async`/`await`.footnote:[O artigo https://fpy.li/13-4[«Monkey patch»] na Wikipedia tem um exemplo engraçado em Python.] O monkey patch não lê nem altera o código-fonte do programa, apenas os objetos na memória que representam as partes do programa, @@ -342,7 +341,7 @@ como módulos, classes e funções. Vamos fazer _monkey patch_ na classe `FrenchDeck` do <> para superar uma grande limitação: ela não pode ser embaralhada. Anos atrás, quando escrevi pela primeira vez o exemplo `FrenchDeck`, implementei um método -`shuffle`. Depois tive um insight pythônico: se um `FrenchDeck` funciona como +`shuffle`. Depois tive uma sacada pythônica: se um `FrenchDeck` funciona como uma sequência, não precisa ter um método `shuffle`, pois já existe a função `random.shuffle`, que "embaralha a sequência x internamente" conforme a https://fpy.li/6m[documentação oficial]. @@ -385,10 +384,10 @@ TypeError: 'FrenchDeck' object does not support item assignment ---- ==== -A mensagem de erro é clara: `O objeto 'FrenchDeck' não suporta a atribuição de -itens`. O problema é que +shuffle+ opera _internamente_, trocando os itens de +A mensagem de erro é clara: "o objeto 'FrenchDeck' não suporta a atribuição de +itens". O problema é que `shuffle` opera internamente, trocando os itens de lugar dentro da coleção, mas `FrenchDeck` só implementa o protocolo de sequência -_imutável_. Para ser uma sequências mutável, `FrenchDeck` precisa oferecer um +imutável. Para ser uma sequência mutável, `FrenchDeck` precisa oferecer um método `+__setitem__+`. Como Python é dinâmico, podemos consertar isso durante a execução, até mesmo no @@ -423,13 +422,13 @@ https://fpy.li/6n[Emulando tipos contêineres]. Aqui nomeei os argumentos `deck, position, card`—e não `self, key, value` como na referência da linguagem—para mostrar que todo método Python começa sua vida como uma função comum, e nomear o primeiro argumento `self` é só uma convenção. -Fujir da convenção é OK em uma sessão no console onde o código é descartável, +Fugir da convenção é OK em uma sessão no console onde o código é descartável, mas em um arquivo de código-fonte de Python é muito melhor usar `self`, `key`, e `value`, seguindo a documentação. O truque é que `set_card` pressupõe que o `deck` tem um atributo chamado `+_cards+`, e seu valor deve ser uma sequência mutável. A função `set_cards` é -então anexada à classe `FrenchDeck` class como o método especial +então anexada à classe `FrenchDeck` como o método especial `+__setitem__+`. Isso é um exemplo de _monkey patching_: modificar uma classe ou módulo durante a execução, sem tocar no código-fonte. O "monkey patching" é poderoso, mas o código que executa a modificação fica muito intimamente acoplado @@ -468,7 +467,7 @@ função. Exemplo prático: quando você escreve código que aceita uma sequência de itens para processar internamente como uma `list`, não valide o argumento -só através de checagem de tipo. Em vez disso, receba o argumento e construa +só por checagem de tipo. Em vez disso, receba o argumento e construa imediatamente uma `list` a partir dele. Um exemplo desse padrão de programação é o método `+__init__+` no <>, que veremos mais à frente nesse capítulo: @@ -482,7 +481,7 @@ Desta forma você torna seu código mais flexível, pois o construtor de `list() processa qualquer iterável que caiba na memória. Se o argumento não for iterável, a chamada vai falhar logo com uma exceção de `TypeError` bastante clara, no exato momento em que o objeto for inicializado. Se -quiser ser mais explícito, pode colocr a chamada a `list()` em um +quiser ser mais explícito, pode colocar a chamada a `list()` em um `try/except`, para adequar a mensagem de erro—mas eu escreveria este código extra apenas em uma API externa, pois a falha já estaria bem visível para os mantenedores que conhecem a base de código. De toda forma, a chamada errônea vai aparecer @@ -508,11 +507,11 @@ um erro na hora. Por outro lado, se qualquer iterável for aceitável, chame `iter(x)` assim que possível, para obter um iterador, como veremos na <>. E -novamente, se `x` não for iterável, isso falhará logo com um exceção +novamente, se `x` não for iterável, isso falhará logo com uma exceção fácil de depurar. Nos casos que acabei de descrever, uma dica de tipo poderia apontar alguns -problemas mais cedo, mas não todos os problemas. Lembre-se que o tipo `Any` é +problemas mais cedo, mas não todos os problemas. Lembre-se de que o tipo `Any` é _consistente-com_ qualquer outro tipo. Inferência de tipo pode fazer com que uma variável seja marcada com o tipo `Any`. Quando isso acontece, o checador de tipos se torna inútil. Além disso, dicas de tipo não são aplicadas durante a @@ -523,7 +522,7 @@ com tipos diferentes sem usar testes com `isinstance()` e `hasattr()`. Um exemplo é como poderíamos imitar como https://fpy.li/13-8[`collections.namedtuple`] lida com o argumento -`field_names`: ele aceita um única string com identificadores +`field_names`: ele aceita uma única string com identificadores separados por espaços ou vírgulas, ou uma sequência de identificadores. O <> mostra como eu faria isso usando tipagem pato. @@ -552,23 +551,24 @@ tem um `.replace` que devolve algo que não funciona com `.split` <4> Se um `AttributeError` aconteceu, então `field_names` não é uma `str`. Supomos que já é um iterável de nomes. -<5> Para ter certeza que é um iterável e para manter nossas própria cópia, -criamos uma tupla com o que temos. Uma `tuple` é mais compacta que uma `list`, e -também impede que meu código troque os nomes por engano. +<5> Para ter certeza de que é um iterável e para manter nossa própria cópia, +criamos uma tupla com o que temos. Uma tuple é mais compacta que uma lista, e +também impede que meu código troque os nomes por acidente. -<6> Usamos `str.isidentifier` para se assegurar que todos os nomes são válidos. +<6> Usamos `str.isidentifier` para garantir que todos os nomes são válidos. -O passo `②` é uma aplicação de EAFP ou Princípio de Hopper.footnote:[A pioneira +O passo `②` do <> é uma aplicação de EAFP ou +Princípio de Hopper.footnote:[A pioneira da computação Grace Hopper dizia que, para inovar em uma burocracia, é mais fácil pedir perdão do que permissão -(_"It's easier to ask forgiveness than permission"_ ou _EAFP_).] +(_"(It's) Easier to Ask Forgiveness than Permission"_ ou _EAFP_).] Em vez de testar se `+field_names+` é uma string, invocamos métodos como se fosse uma string, e se não der certo, tratamos a exceção. Não pedimos licença: fazemos o que temos que fazer e pedimos perdão se for necessário. -O <> mostra uma situação onde a tipagem pato é mais +O <> mostra uma situação em que a tipagem pato é mais expressiva que dicas de tipo estáticas. Não há como escrever uma dica de tipo que diga "o argumento `field_names` deve ser uma string de identificadores separados por espaços ou vírgulas." Esta é a parte relevante da assinatura de `namedtuple` no @@ -596,7 +596,7 @@ startref="defprog13"))) [[goose_typing_sec]] === Tipagem ganso -[quote, Bjarne Stroustrup, creator of {cpp}] +[quote, Bjarne Stroustrup, criador do {cpp}] ____ Uma classe abstrata representa uma interface.footnote:[No original: "An abstract @@ -607,7 +607,7 @@ ____ Python((("goose typing", "abstract base classes (ABCs)", id="GTabcs13")))((("ABCs (abstract base classes)", "goose typing and", -id="ABCgoose13"))) não a uma palavra-chave `interface`. Usamos classes base +id="ABCgoose13"))) não tem uma palavra-chave `interface`. Usamos classes base abstratas (ABCs) para definir interfaces úteis para checagem explícita de tipo durante a execução, e também para anotações compatíveis com checadores de tipos estáticos. @@ -621,7 +621,7 @@ valor dessas estruturas para linguagens que usam tipagem pato: [quote] ____ -Classes bases abstratas complementam a tipagem pato, fornecendo uma maneira de +Classes base abstratas complementam a tipagem pato, fornecendo uma maneira de definir interfaces quando outras técnicas, como `hasattr()`, seriam desajeitadas ou sutilmente erradas (por exemplo, com métodos mágicos). ABCs introduzem subclasses virtuais, classes que não herdam de uma classe mas ainda são @@ -630,13 +630,13 @@ reconhecidas por `isinstance()` e `issubclass()`; veja a documentação do módu ____ A tipagem ganso é uma abordagem à checagem de tipo durante a execução que se -apoia nas ABCs. Vou deixar que Alex Martelli explique, no <>. +apoia nas ABCs. Vou deixar que Alex Martelli explique, no texto _<>_. [NOTE] ==== -Sou muito grato a meus amigos Alex MArtekli e Anna Ravenscroft. Mostrei a eles a +Sou muito grato a meus amigos Alex Martelli e Anna Ravenscroft. Mostrei a eles a primeira lista de tópicos do _Python Fluente_ na OSCON 2013, e eles me -encorajaram a submeter à O'Reilly para publicação. Mais tarde os dois +encorajaram a submeter à O'Reilly para publicação. Depois os dois contribuíram com revisões técnicas minuciosas. Alex já era a pessoa mais citada nesse livro quando se ofereceu para escrever este ensaio. ==== @@ -658,7 +658,7 @@ checar, por exemplo, se `type(foo) is bar`—que é corretamente considerado um anátema, pois inibe até as formas mais simples de herança!). No geral, a abordagem da tipagem pato continua muito útil em inúmeros -contextos—mas em muitos outros, um nova abordagem muitas vezes preferível +contextos—mas em muitos outros, uma nova abordagem muitas vezes preferível evoluiu ao longo do tempo. E aqui começa nossa história... Em gerações recentes, a taxonomia de gênero e espécies (incluindo, mas não @@ -707,7 +707,7 @@ rápido vem tornando a cladística bastante prática em mais casos). Por exemplo, os Chloephaga, gênero de gansos sul-americanos (antes classificados como próximos a outros gansos) e as tadornas (gênero de patos sul-americanos) estão agora agrupados juntos na subfamília Tadornidae (sugerindo que eles são -mais próximos entre si que de qualquer outro Anatidae, pois compartilham um +mais próximos entre si do que de qualquer outro Anatidae, pois compartilham um ancestral comum mais próximo). Além disso, a análise de DNA mostrou que o Asarcornis (pato da floresta ou pato de asas brancas) não é tão próximo do Cairina moschata (pato-do-mato), esse último uma tadorna, como as similaridades @@ -715,7 +715,7 @@ corporais e comportamentais sugeriram por tanto tempo—então o pato da florest foi reclassificado em um gênero próprio, inteiramente fora da subfamília! Isso importa? Depende do contexto! Para o propósito de decidir como cozinhar uma -ave depois de caçá-la, por exemplo, características observáveis específicas (mas +ave após caçá-la, por exemplo, características observáveis específicas (mas nem todas—a plumagem, por exemplo, é de mínima importância nesse contexto), especialmente textura e sabor (a boa e velha fenética), podem ser mais relevantes que a cladística. Mas para outros problemas, tal como a @@ -724,8 +724,8 @@ cativeiro, ou preservá-las na natureza), a proximidade do DNA pode ser mais importante. Então, a partir dessa analogia aproximada com as revoluções taxonômicas no mundo -das aves aquáticas, estou recomendando suplementar (não substitui -inteiramente—em determinados contexto ela ainda servirá) o bom e velho _duck +das aves aquáticas, estou recomendando suplementar (não substituir +inteiramente—em determinados contextos ela ainda servirá) o bom e velho _duck typing_ por... _goose typing_ (tipagem ganso)! _Goose typing_ significa o seguinte: `isinstance(obj, cls)` agora é plenamente @@ -737,11 +737,11 @@ outras no módulo `numbers` da Biblioteca Padrão de Python)footnote:[Você tamb pode, claro, definir suas próprias ABCs—mas eu não recomendaria esse caminho a ninguém, exceto aos mais avançados pythonistas, da mesma forma que os desencorajaria de definir suas próprias metaclasses customizadas... e mesmo para -os ditos "mais avançados pythonistas", aqueles de nós que exibem o domínio de -todos os recantos por mais obscuros da linguagem, essas não são ferramentas de +os ditos "mais avançados pythonistas", aqueles que exibem o domínio de todos +os recantos por mais obscuros da linguagem, essas não são ferramentas de uso frequente. Este tipo de "metaprogramação profunda", se alguma vez for -apropriada, o será no contexto dos autores de frameworks abrangentes, projetadas -para serem estendidas de forma independente por inúmeras equipes de +apropriada, o será no contexto dos autores de frameworks abrangentes, projetados +para serem estendidos de forma independente por inúmeras equipes de desenvolvimento diferentes... menos que 1% dos "mais avançados pythonistas" precisará disso alguma vez na vida!—_A.M_] @@ -756,8 +756,8 @@ métodos e assinatura da ABC e, mais importante, o contrato semântico subjacente—mas não precisa ter sido desenvolvida com qualquer conhecimento da ABC, e especificamente não precisa herdar dela!). Isso é um longo caminho andado na direção de quebrar a rigidez e o acoplamento forte que torna herança algo -para ser usado com mais cautela que aquela tipicamente praticada pela maioria do -programadores orientados a objetos. +para ser usado com mais cautela que aquela tipicamente praticada pela maioria +dos programadores orientados a objetos. Em algumas ocasiões você sequer precisa registrar uma classe para que uma ABC a reconheça como uma subclasse! @@ -782,12 +782,12 @@ necessidade de registro, já que implementar o método especial chamado semântica corretas—deve poder ser chamado sem argumentos e retornar um inteiro não-negativo indicando o "comprimento" do objeto; mas qualquer código que implemente um método com nome especial, como `+__len__+`, com uma sintaxe e uma -semântica arbitrárias e incompatíveis tem problemas muitos maiores que esses). +semântica arbitrárias e incompatíveis tem problemas bem maiores que estes). Então, aqui está minha mensagem de despedida: sempre que você estiver -implementando uma classe que incorpore qualquer dos conceitos representados nas -ABCs de `number`, `collections.abc` ou em outro framework que estiver usando, se -assegure (caso necessário) de ser uma subclasse ou de registrar sua classe com a +implementando uma classe que incorpore quaisquer dos conceitos representados nas +ABCs de `number`, `collections.abc` ou em outro framework que estiver usando, +assegure-se (caso necessário) de ser uma subclasse ou de registrar sua classe com a ABC correspondente. No início de seu programa que utiliza uma biblioteca ou framework que define classes que omitiram esse passo, registre você mesmo as classes. Daí, quando precisar checar se (tipicamente) um argumento é, por @@ -823,7 +823,7 @@ subclasse virtual. [NOTE] ==== -Detalhes sobre o uso de `register` são tratados em <>, +Detalhes sobre o uso de `register` são tratados na <>, mais adiante. Por hora, aqui está um pequeno exemplo: dada a classe `FrenchDeck`, se eu quiser que ela passe em uma checagem como `issubclass(FrenchDeck, Sequence)`, posso torná-la uma _subclasse virtual_ da @@ -845,29 +845,29 @@ necessários—ele sempre pode ser registrado posteriormente e passar naquelas checagens de tipo explícitas. Entretanto, mesmo com ABCs, você deve se precaver contra o uso excessivo de -checagens com `isinstance`, pois isso poder ser sintoma de um design ruim. +checagens com `isinstance`, pois isso pode ser sintoma de um design ruim. Normalmente, não é bom ter uma série de `if/elif/elif` com checagens de -`isinstance` executando ações diferentes, dependendo do tipo de objeto: nesse +`isinstance` executando ações diferentes, dependendo do tipo de objeto: neste caso você deveria estar usando polimorfismo—isto é, projetando suas classes para -permitir ao interpretador enviar chamadas para os métodos corretos, em vez de +permitir ao interpretador invocar os métodos corretos, em vez de codificar diretamente a lógica de despacho em blocos `if/elif/elif`. Por outro lado, não há problema em executar uma checagem com `isinstance` contra uma ABC se você quer garantir um contrato de API: "Cara, você precisa implementar isso se quiser me chamar," como costuma dizer o revisor técnico Lennart Regebro. Isso é especialmente útil em sistemas com arquiteturas -modulares extensíveis por plug-ins. Fora dos frameworks, tipagem pato é muitas -vezes mais simples e flexível que checagens de tipo. +modulares extensíveis por plug-ins. Fora dos frameworks, tipagem pato muitas +vezes é mais simples e flexível que checagens de tipo explícitas. Por fim, em seu ensaio Alex reforça mais de uma vez a necessidade de limitar a criação de ABCs. Uso excessivo de ABCs imporia cerimônia a uma linguagem que se tornou popular por ser prática e pragmática. Durante o processo de revisão do -_Python Fluente_, Alex me enviou uma e-mail: +_Python Fluente_, Alex colocou num e-mail: [quote] ____ -ABCs servem para encapsular conceitos muito genéricos, abstrações introduzidos +ABCs servem para encapsular conceitos muito genéricos, abstrações introduzidas por um framework—coisa como "uma sequência" e "um número exato". [Os leitores] quase certamente não precisam escrever alguma nova ABC, apenas usar as já existentes de forma correta, para obter 99% dos benefícios sem qualquer risco @@ -876,7 +876,7 @@ ____ Agora vamos ver a tipagem ganso na prática. -==== Criando uma Subclasse de uma ABC +==== Criando uma subclasse de uma ABC Seguindo((("inheritance and subclassing", "subclassing ABCs", id="IASabcs13")))((("goose typing", "subclassing ABCs", id="GTsub13")))((("ABCs (abstract base classes)", "subclassing ABCs", id="ABCsub13"))) o conselho de Martelli, vamos aproveitar uma ABC existente, `collections.MutableSequence`, antes de ousar inventar uma nova. No <>, `FrenchDeck2` é explicitamente declarada como subclasse de `collections.MutableSequence`. @@ -892,30 +892,38 @@ include::../code/13-protocol-abc/frenchdeck2.py[] [role="pagebreak-before less_space"] <1> `+__setitem__+` é tudo que precisamos para possibilitar o embaralhamento... <2> ...mas uma subclasse de `MutableSequence` é forçada a implementar `+__delitem__+`, um método abstrato daquela ABC. -<3> Também precisamos implementar `insert`, o terceiro método abstrato de pass:[MutableSequence]. +<3> Também precisamos implementar `insert`, o terceiro método abstrato de `MutableSequence`. Python não verifica a implementação de métodos abstratos durante a importação (quando o módulo _frenchdeck2.py_ é carregado na memória e compilado), mas apenas durante a execução, quando tentamos de fato instanciar `FrenchDeck2`. -Ali, se deixamos de implementar qualquer [.keep-together]#dos# métodos abstratos, +Ali, se deixamos de implementar qualquer um dos métodos abstratos, recebemos uma exceção de `TypeError` com uma mensagem como -`Can't instantiate abstract class FrenchDeck2 with abstract methods __delitem__, insert` -("Impossível instanciar a classe abstrata FrenchDeck2 com os métodos abstratos `+__delitem__+`, ``insert``"). +_Can't instantiate abstract class FrenchDeck2 with abstract methods `+__delitem__+`, ``insert``_ +(Impossível instanciar a classe abstrata `FrenchDeck2` com os métodos abstratos `+__delitem__+`, ``insert``). Por isso precisamos implementar `+__delitem__+` e `insert`, -mesmo se nossos exemplos usando `FrenchDeck2` não precisem desses comportamentos: a ABC `MutableSequence` os exige. +mesmo se nossos exemplos usando `FrenchDeck2` não precisem desses comportamentos: +a ABC `MutableSequence` os exige. -Como((("UML class diagrams", "MutableSequence ABC and superclasses"))) <> mostra, nem todos os métodos das ABCs `Sequence` e `MutableSequence` ABCs são abstratos. +Como((("UML class diagrams", "MutableSequence ABC and superclasses"))) +mostra a <>, nem todos os métodos das ABCs `Sequence` +e `MutableSequence` ABCs são abstratos. [[mutablesequence_uml]] .Diagrama de classe UML para a ABC `MutableSequence` e suas superclasses em `collections.abc` (as setas de herança apontam das subclasses para as ancestrais; nomes em itálico são classes e métodos abstratos). image::../images/flpy_1303.png[] -Para escrever `FrenchDeck2` como uma subclasse de `MutableSequence`, tive que pagar o preço de implementar `+__delitem__+` e `insert`, desnecessários em meus exemplos. -Em troca, `FrenchDeck2` herda cinco métodos concretos de `Sequence`: -`+__contains__+`, `+__iter__+`, `+__reversed__+`, `index`, e `count`. -De `MutableSequence`, ela recebe outros seis métodos: `append`, `reverse`, `extend`, `pop`, `remove`, e `+__iadd__+`— que suporta o operador `+=` para concatenação direta. +Para escrever `FrenchDeck2` como uma subclasse de `MutableSequence`, tive que +pagar o preço de implementar `+__delitem__+` e `insert`, desnecessários em meus +exemplos. Em troca, `FrenchDeck2` herda cinco métodos concretos de `Sequence`: +`+__contains__+`, `+__iter__+`, `+__reversed__+`, `index`, e `count`. De +`MutableSequence`, ela recebe outros seis métodos: `append`, `reverse`, +`extend`, `pop`, `remove`, e `+__iadd__+`— que suporta o operador `+=` para +concatenação direta. -Os métodos concretos em cada ABC de `collections.abc` são implementados nos termos da interface pública da classe, então funcionam sem qualquer conhecimento da estrutura interna das instâncias. +Os métodos concretos em cada ABC de `collections.abc` são implementados nos +termos da interface pública da classe, então funcionam sem qualquer conhecimento +da estrutura interna das instâncias. [TIP] @@ -954,7 +962,7 @@ Há dois módulos chamados `abc` na biblioteca padrão. Aqui estamos falando sobre o `collections.abc`. Para reduzir o tempo de carregamento, desde o Python 3.4 aquele módulo é implementado fora do pacote `collections` — em https://fpy.li/13-14[_Lib/_collections_abc.py_] — então é importado separado de `collections`. O((("abc.ABC class"))) outro módulo `abc` é apenas `abc` (i.e., https://fpy.li/13-15[_Lib/abc.py_]), onde a classe `abc.ABC` é definida. -Toda ABC depende do módulo `abc`, mas não precisamos importá-lo nós mesmos, exceto para criar um nova ABC. +Toda ABC depende do módulo `abc`, mas não precisamos importá-lo nós mesmos, exceto para criar uma nova ABC. ==== @@ -963,7 +971,7 @@ diagrama de classe resumido (sem os nomes dos atributos) das 17 ABCs definidas em `collections.abc`. A documentação de `collections.abc` inclui https://fpy.li/13-16[uma ótima tabela] resumindo as ABCs, suas relações e seus métodos abstratos e concretos (chamados "métodos mixin"). Há muita herança -múltipla acontecendo na <>. Vamos dedicar a maior parte de +múltipla acontecendo na <>. Vamos dedicar a maior parte do <> à herança múltipla, mas por hora é suficiente dizer que isso normalmente não causa problemas no caso das ABCs.footnote:[Herança múltipla foi _considerada nociva_ e excluída do Java, exceto para interfaces: @@ -978,9 +986,9 @@ Vamos rever os grupos na <>: `Iterable`, `Container`, `Sized`:: Toda coleção deveria herdar destas ABCs ou implementar protocolos compatíveis. -`Iterable` suporta iteração com `+__iter__+`, -`Container` suporta o operador `in` com `+__contains__+`, -e `Sized` suporta `len()` with `+__len__+`. +`Iterable` define `+__iter__+` para suportar iteração, +`Container` define `+__contains__+` para o operador `in`, +e `Sized` define `+__len__+` para `len()`. `Collection`:: Essa ABC não tem nenhum método próprio, mas foi acrescentada no Python 3.6 para @@ -988,41 +996,41 @@ facilitar a criação de subclasses de `Iterable`, `Container`, e `Sized`. `Sequence`, `Mapping`, `Set`:: Esses são os principais tipos de coleções imutáveis, e cada um tem uma subclasse -mutável. Um diagrama detalhado de `MutableSequence` é apresentado em +mutável. Um diagrama detalhado de `MutableSequence` é apresentado na <>; para `MutableMapping` e `MutableSet`, veja a -<> e a <> em <>. +<> e a <> no <>. `MappingView`:: No Python 3, os objetos devolvidos pelos métodos de mapeamentos `.items()`, `.keys()`, e `.values()` implementam as interfaces definidas em `ItemsView`, `KeysView`, e `ValuesView`, respectivamente. Os dois primeiros também implementam a rica interface de `Set`, com todos os operadores que vimos na -<>. +<>. `Iterator`:: Observe que iterator é subclasse de `Iterable`. -Discutiremos este detalhe em <>. +Discutiremos este detalhe no <>. `Callable`, `Hashable`:: -Essas não são coleções, mas `collections.abc` foi o primeiro pacote a definir -ABCs na biblioteca padrão, e essas duas foram consideradas importante o -suficiente para serem incluídas. Elas suportam a checagem de tipos de objetos -que precisam ser "chamáveis" ou hashable. +Estas não são coleções, mas `collections.abc` foi o primeiro pacote a definir +ABCs na biblioteca padrão, e estas duas foram incluídas por serem importantes. +Elas suportam a checagem de tipos de objetos +que precisam ser invocáveis ou _hashable_. -Para a detecção de 'callable', a função nativa `callable(obj)` é mais +Para a detecção de invocável, a função embutida `callable(obj)` é mais conveniente que `insinstance(obj, Callable)`. -Se `insinstance(obj, Hashable)` retornar `False`, você pode ter certeza que -`obj` não é hashable. Mas se ela retornar `True`, pode ser um falso positivo. +Se `insinstance(obj, Hashable)` devolver `False`, pode ter certeza de que +`obj` não é _hashable_. Mas se ela devolver `True`, pode ser um falso positivo. Isso é explicado no box seguinte. [[isinstance_mislead_box]] -.isinstance com Hashable e Iterable pode enganar você +.`isinstance` com `Hashable` e `Iterable` pode te enganar **** É fácil interpretar errado os resultados de testes usando `isinstance` e -`issubclass` com as ABCs `Hashable` and `Iterable`. Se `isinstance(obj, -Hashable)` retorna `True`, is significa apenas que a classe de `obj` implementa +`issubclass` com as ABCs `Hashable` e `Iterable`. Quando +`isinstance(obj, Hashable)` devolve `True`, significa apenas que a classe de `obj` implementa ou herda `+__hash__+`. Mas se `obj` é uma tupla contendo itens _unhashable_, então `obj` não é _hashable_, apesar do resultado positivo da checagem com `isinstance`. @@ -1031,74 +1039,93 @@ o modo mais preciso de determinar se uma instância é _hashable_: chamar `hash( Essa chamada vai levantar um `TypeError` se `obj` não for _hashable_. Por outro lado, mesmo quando `isinstance(obj, Iterable)` retorna `False`, -o Python ainda pode ser capaz de iterar sobre `obj` usando `+__getitem__+` com índices baseados em 0, -como vimos em <> e na <>. -A documentação de +o Python ainda pode ser capaz de iterar sobre `obj` usando `+__getitem__+` +com índices baseados em 0, como vimos no <> e +na <>. A documentação de https://fpy.li/6q[`collections.abc.Iterable`] afirma: [quote] ____ -A única maneira confiável de determinar se um objeto é iterável é chamar iter(obj). +A única maneira confiável de determinar se um objeto é iterável é chamar `iter(obj)`. ____ **** -Após vermos algumas das ABCs existentes, vamos praticar tipagem ganso implementando uma ABC do zero, e a colocando em uso. -O objetivo aqui não é encorajar todo mundo a ficar criando ABCs a torto e a direito, mas aprender como ler o código-fonte das ABCs encontradas na biblioteca padrão e em outros pacotes.((("", startref="GTstlib13")))((("", startref="ABCstndlib13"))) +Após vermos algumas das ABCs existentes, vamos praticar tipagem ganso +implementando uma ABC do zero, e a colocando em uso. O objetivo aqui não é +encorajar todo mundo a criar ABCs a torto e a direito, mas mostrar como ler o +código-fonte das ABCs encontradas na biblioteca padrão e em outros +pacotes.((("", startref="GTstlib13")))((("", startref="ABCstndlib13"))) [[defining_using_abc_sec]] ==== Definindo e usando uma ABC -Essa((("goose typing", "defining and using ABCs", id="GTdef13")))((("ABCs (abstract base classes)", "defining and using ABCs", id="ABCdef13"))) advertência estava no capítulo "Interfaces" da primeira edição de _Python Fluente_: +Essa((("goose typing", "defining and using ABCs", id="GTdef13")))((("ABCs (abstract base classes)", +"defining and using ABCs", id="ABCdef13"))) +advertência estava no capítulo "Interfaces" da primeira edição de _Python +Fluente_: [quote] ____ -ABCs, como os descritores e as metaclasses, são ferramentas para criar frameworks, -Assim, apenas uma pequena minoria dos desenvolvedores Python podem criar ABCs sem impor limitações pouco razoáveis e trabalho desnecessário a seus colegas programadores. -____ - -Agora ABCs têm mais casos de uso potenciais, em dicas de tipo para permitir tipagem estática. -Como discutido na <>, -usar ABCs em vez de tipo concretos em dicas de tipos de argumentos de função dá mais flexibilidade a quem chama a função. -Para justificar a criação de uma ABC, precisamos pensar em um contexto para usá-la como um ponto de extensão em um framework. -Então aqui está nosso contexto: imagine que você precisa exibir publicidade em um site ou em uma app de celular, em ordem aleatória, mas sem repetir um anúncio antes que o inventário completo de anúncios tenha sido exibido. -Agora vamos presumir que estamos desenvolvendo um gerenciador de publicidade chamado `ADAM`. -Um dos requisitos é permitir o uso de classes de escolha aleatória não repetida fornecidas pelo usuário.footnote:[Talvez o cliente precise auditar o randomizador ou a agência queira fornecer um randomizador "viciado". Nunca se sabe...] -Para deixar claro aos usuário do `ADAM` o que se espera de um componente de "escolha aleatória não repetida", vamos definir uma ABC. +ABCs, como os descritores e as metaclasses, são ferramentas para criar +frameworks. Assim, só uma pequena minoria dos desenvolvedores Python tem +a oportunidade de criar ABCs sem impor limitações pouco razoáveis e +trabalho desnecessário a seus colegas programadores. +____ -Na bibliografia sobre estruturas de dados, "stack" e "queue" descrevem interfaces abstratas em termos dos arranjos físicos dos objetos. -Vamos seguir o mesmo caminho e usar uma metáfora do mundo real para batizar nossa ABC: -gaiolas de bingo e sorteadores de loteria são máquinas projetadas para escolher aleatoriamente itens de um conjunto, finito sem repetições, até o conjunto ser exaurido. -Vamos chamar a ABC de `Tombola`, seguindo o nome italiano do bingo, e do recipiente giratório que mistura os números. +Agora ABCs têm mais casos de uso potenciais, em dicas de tipo para permitir +tipagem estática. Como discutido na <>, usar ABCs em vez de +tipos concretos em dicas de tipos de argumentos de função dá mais flexibilidade a +quem chama a função. + +Para justificar a criação de uma ABC, precisamos pensar em um contexto para +usá-la como um ponto de extensão em um framework. Então aqui está nosso +contexto: imagine que você precisa exibir publicidade em um site ou em uma app +de celular, em ordem aleatória, mas sem repetir um anúncio antes que o +inventário completo de anúncios tenha sido exibido. Agora vamos presumir que +estamos desenvolvendo um gerenciador de publicidade. Um dos +requisitos é permitir o uso de classes de escolha aleatória não repetida +fornecidas pelo usuário.footnote:[Talvez o cliente precise auditar o +randomizador ou a agência queira fornecer um randomizador "viciado". Nunca se +sabe...] Para deixar claro aos usuários do framework de anúncios o que se espera +de um componente de "escolha aleatória não repetida", vamos definir uma ABC. + +Na bibliografia sobre estruturas de dados, _stack_ (pilha) e _queue_ (fila) descrevem +interfaces abstratas em termos dos arranjos físicos dos objetos. Vamos seguir o +mesmo caminho e usar uma metáfora do mundo real para batizar nossa ABC: gaiolas +de bingo e sorteadores de loteria são máquinas projetadas para escolher +aleatoriamente itens de um conjunto finito, sem repetir, até o conjunto ser +esgotado. Vamos chamar a ABC de `Tombola`, seguindo o nome italiano do bingo, e +do recipiente giratório que mistura os números. A ABC `Tombola` tem quatro métodos. Os dois métodos abstratos são: -`.load(…)`:: Coloca itens no container. -`.pick()`:: Remove e retorna um item aleatório do container. +`.load(…)`:: Coloca itens na coleção. +`.pick()`:: Remove e devolve um item aleatório da coleção. Os métodos concretos são: -`.loaded()`:: Retorna `True` se existir pelo menos um item no container. -`.inspect()`:: Retorna uma `tuple` construída a partir dos itens atualmente no container, sem modificar o conteúdo (a ordem interna não é preservada). +`.loaded()`:: Devolve `True` se existir pelo menos um item na coleção. +`.inspect()`:: Devolve uma `tuple` construída a partir dos itens atualmente na coleção, +sem modificar o conteúdo (a ordem interna não é preservada). A <> mostra a ABC `Tombola` e três implementações concretas. -Vale notar que _registered_ (registarada) e _virtual subclass_ (subclasse virtual) +Vale notar que _registered_ (registrada) e _virtual subclass_ (subclasse virtual) não são termos da UML padrão, mas representam uma relação de classe específica de Python, como veremos na <>. [role="width-80"] [[tombola_uml]] -.Diagrama UML para uma ABC e três subclasses. O nome da ABC `Tombola` e de seus métodos abstratos estão escritos em _itálico_, segundo as convenções da UML. A seta tracejada indica que `TomboList` implementa a interface `Tombola`, e também está registrada como _subclasse virtual_. -`TomboList` também está registrada como uma _subclasse virtual_ de `Tombola`. -image::../images/flpy_1305.png[align="center",pdfwidth=10cm] +.Diagrama UML para uma ABC e três subclasses. O nome da ABC `Tombola` e de seus métodos abstratos estão escritos em _itálico_, segundo as convenções da UML. A seta tracejada indica que `TomboList` implementa a interface `Tombola`, e também está registrada como _subclasse virtual_ daquela ABC. +image::../images/flpy_1305.png[align="center",pdfwidth=9cm] O <> mostra a definição da ABC `Tombola`. [[ex_tombola_abc]] -.tombola.py: `Tombola` é uma ABC com dois métodos abstratos e dois métodos concretos. +.tombola.py: ABC com dois métodos abstratos e dois métodos concretos. ==== [source, python] ---- @@ -1106,19 +1133,34 @@ include::../code/13-protocol-abc/tombola.py[tags=TOMBOLA_ABC] ---- ==== <1> Para definir uma ABC, crie uma subclasse de `abc.ABC`. -<2> Um método abstrato é marcado com o decorador `@abstractmethod`, e muitas vezes seu corpo é vazio, exceto por uma docstring.footnote:[Antes das ABCs existirem, métodos abstratos levantariam um `NotImplementedError` para sinalizar que as subclasses eram responsáveis por suas implementações. No Smalltalk-80, o corpo dos métodos abstratos invocaria `subclassResponsibility`, um método herdado de `object` que gerava um erro com a mensagem "Minha subclasse deveria ter sobrescrito uma de minhas mensagens."] -<3> A docstring instrui os implementadores a levantarem `LookupError` se não existirem itens para escolher. + +<2> Um método abstrato é marcado com o decorador `@abstractmethod`, e muitas +vezes seu corpo é vazio, exceto por uma docstring.footnote:[Antes das ABCs +existirem, métodos abstratos levantariam um `NotImplementedError` para sinalizar +que as subclasses eram responsáveis por suas implementações. No Smalltalk-80, o +corpo dos métodos abstratos invocaria `subclassResponsibility`, um método +herdado de `object` que gerava um erro com a mensagem "Minha subclasse deveria +ter sobrescrito uma de minhas mensagens."] + +<3> A docstring instrui os implementadores a levantarem `LookupError` se não +existirem itens para escolher. + <4> Uma ABC pode incluir métodos concretos. -<5> Métodos concretos em uma ABC devem depender apenas da interface definida pela ABC (isto é, outros métodos concretos ou abstratos ou propriedades da ABC). -<6> Não sabemos como as subclasses concretas vão armazenar os itens, mas podemos escrever o resultado de `inspect` esvaziando a `Tombola` com chamadas sucessivas a -`.pick()`... + +<5> Métodos concretos em uma ABC devem depender apenas da interface definida +pela ABC (isto é, outros métodos concretos ou abstratos ou propriedades da ABC). + +<6> Não sabemos como as subclasses concretas vão armazenar os itens, mas podemos +escrever o resultado de `inspect` esvaziando a `Tombola` com chamadas sucessivas +a `.pick()`... + <7> ...e então usando `.load(…)` para colocar tudo de volta. [role="pagebreak-before less_space"] [TIP] ==== -Um método abstrato na verdade pode ter uma implementação. +Um método abstrato pode ter uma implementação. Mas mesmo que tenha, as subclasses ainda são obrigadas a sobrescrevê-lo, mas poderão invocar o método abstrato com `super()`, acrescentando funcionalidade em vez de implementar do zero. @@ -1126,14 +1168,15 @@ Veja os detalhes do uso de `@abstractmethod` na https://fpy.li/6r[documentação do módulo `abc`]. ==== -O código do o método `.inspect()` é simplório, mas mostra que podemos confiar em -`.pick()` e `.load(…)` para inspecionar o que está dentro de `Tombola`, puxando -e devolvendo os itens—sem saber como eles são efetivamente armazenados. O -objetivo desse exemplo é ressaltar que não há problema em oferecer métodos +O código do método `.inspect()` é ridículo mas funciona. +Ele serve para mostrar que podemos usar `.pick()` e `.load(…)` +para inspecionar o que está dentro de `Tombola`, puxando +e devolvendo os itens—sem saber como eles são realmente armazenados. O +objetivo deste exemplo é ressaltar que não há problema em oferecer métodos concretos em ABCs, desde que eles dependam apenas de outros métodos na interface. Conhecendo suas estruturas de dados internas, as subclasses concretas -de `Tombola` podem sempre sobrescrever `.inspect()` com uma implementação mais -adequada, mas não são obrigadas a fazer isso. +de `Tombola` podem sobrescrever `.inspect()` com uma implementação mais +eficiente, mas não são obrigadas a fazer isso. O método `.loaded()` no <> tem uma linha, mas é custoso: ele chama `.inspect()` para criar a `tuple` apenas para aplicar `bool()` nela. @@ -1220,7 +1263,7 @@ Logo vamos criar uma subclasse de `Tombola`, mas primeiro temos que falar sobre A((("goose typing", "ABC syntax details")))((("ABCs (abstract base classes)", "ABC syntax details"))) forma padrão de declarar uma ABC é criar uma subclasse de `abc.ABC` ou de alguma outra ABC. Além da classe base ABC e do decorador `@abstractmethod`, o módulo `abc` define -os decoradores `@abstractclassmethod`, `@abstractstaticmethod`, and `@abstractproperty`. +os decoradores `@abstractclassmethod`, `@abstractstaticmethod`, e `@abstractproperty`. Entretanto, os três últimos foram descontinuados no Python 3.3, quando se tornou possível empilhar decoradores sobre `@abstractmethod`, tornando os outros redundantes. Por exemplo, a maneira preferível de declarar um método de classe abstrato é: @@ -1240,22 +1283,30 @@ A ordem dos decoradores de função empilhados importa, e no caso de `@abstractm [quote] ____ -Quando `@abstractmethod` é aplicado em combinação com outros descritores de método, -ele deve ser aplicado como o decorador mais interno...footnote:[O verbete https://fpy.li/6s[`@abc.abstractmethod`] na https://docs.python.org/pt-br/dev/library/abc.html[documentação do módulo `abc`].] +Quando `@abstractmethod` é aplicado em combinação com outros descritores de +método, ele deve ser aplicado como o decorador mais interno...footnote:[O +verbete https://fpy.li/6s[`@abc.abstractmethod`] na +https://fpy.li/6r[documentação do módulo `abc`].] ____ Em outras palavras, nenhum outro decorador pode aparecer entre `@abstractmethod` e a instrução `def`. ==== -Agora que abordamos essas questões de sintaxe das ABCs, vamos colocar `Tombola` em uso, implementando dois descendentes concretos dessa classe. +Agora que abordamos essas questões de sintaxe das ABCs, vamos colocar `Tombola` +em uso, implementando duas subclasses concretas. -==== Criando uma subclasse de ABC +==== Criando uma subclasse de `Tombola` -Dada((("goose typing", "subclassing ABCs", id="GTsubclass13")))((("ABCs (abstract base classes)", "subclassing ABCs", id="ABCsubclass13")))((("inheritance and subclassing", "subclassing ABCs", id="IASsubclass13"))) a ABC `Tombola`, vamos agora desenvolver duas subclasses concretas que satisfazem a interface. -Essas classes estão ilustradas na <>, junto com a subclasse virtual que será discutida na seção seguinte. +Dada((("goose typing", "subclassing ABCs", id="GTsubclass13")))((("ABCs (abstract base classes)", +"subclassing ABCs", id="ABCsubclass13")))((("inheritance and subclassing", "subclassing ABCs", +id="IASsubclass13"))) a ABC `Tombola`, vamos desenvolver duas subclasses +concretas que satisfazem a interface. Essas classes estão ilustradas na +<>, junto com a subclasse virtual que será discutida na seção +seguinte. -A classe `BingoCage` no <> é uma variação da <> usando um randomizador melhor. -`BingoCage` implementa os métodos abstratos obrigatórios `load` e `pick`. +A classe `BingoCage` no <> é uma variação do +<> do <> usando um randomizador melhor. `BingoCage` implementa os +métodos abstratos obrigatórios `load` e `pick`. [[ex_tombola_bingo]] .bingo.py: `BingoCage` é uma subclasse concreta de `Tombola` @@ -1266,19 +1317,34 @@ include::../code/13-protocol-abc/bingo.py[tags=TOMBOLA_BINGO] ---- ==== <1> Essa classe `BingoCage` estende `Tombola` explicitamente. -<2> Finja que vamos usar isso para um jogo online. `random.SystemRandom` implementa -a API `random` sobre a função `os.urandom(…)`, que fornece bytes aleatórios "adequados para uso em criptografia", segundo https://fpy.li/6t[a documentação do módulo `os`]. + +<2> Faz de conta que vamos usar isso para um jogo online. `random.SystemRandom` +implementa a API `random` sobre a função `os.urandom(…)`, que fornece bytes +aleatórios "adequados para uso em criptografia", segundo https://fpy.li/6t[a +documentação do módulo `os`]. + <3> Delega o carregamento inicial para o método `.load()` -<4> Em vez da função `random.shuffle()` normal, usamos o método `.shuffle()` de nossa instância de `SystemRandom`. -<5> `pick` é implementado como em <>. -<6> `+__call__+` também é de <>. Ele não é necessário para satisfazer a interface de `Tombola`, mas não há nenhum problema em adicionar métodos extra. + +<4> Em vez da função `random.shuffle()` normal, usamos o método `.shuffle()` de +nossa instância de `SystemRandom`. + +<5> `pick` é implementado como no <> do <>. + +<6> `+__call__+` também é do <> do <>. Ele não é necessário para +satisfazer a interface de `Tombola`, mas é comum que subclasses tenham mais +métodos. `BingoCage` herda o custoso método `loaded` e o tolo `inspect` de `Tombola`. -Ambos poderiam ser sobrepostos com métodos de uma linha mais rápidos, como no <>. A questão é: podemos ser preguiçosos e escolher apenas herdar os método concretos menos que ideais de uma ABC. -Os métodos herdados de `Tombola` não são tão rápidos quanto poderia ser em `BingoCage`, mas fornecem os resultados esperados para qualquer subclasse de `Tombola` que implemente `pick` e `load` corretamente. +Ambos poderiam ser sobrescritos com métodos de uma linha mais rápidos, como no +<>. A questão é: podemos decidir apenas herdar os +métodos concretos de uma ABC. Os métodos herdados de `Tombola` +não são tão rápidos quanto poderiam ser na `BingoCage` concreta, +mas fornecem os resultados esperados para qualquer subclasse de +`Tombola` que implemente `pick` e `load` corretamente. -O <> mostra uma implementação muito diferente mas igualmente válida da interface de `Tombola`. -Em vez de misturar as "bolas" e tirar a última, `LottoBlower` tira um item de uma posição aleatória.. +O <> mostra uma implementação muito diferente, mas também válida, +da interface de `Tombola`. Em vez de misturar as "bolas" e tirar a última, +`LottoBlower` tira um item de uma posição aleatória.. [[ex_lotto]] .lotto.py: `LottoBlower` é uma subclasse concreta que sobrecarrega os métodos `inspect` e `loaded` de `Tombola` @@ -1291,7 +1357,7 @@ include::../code/13-protocol-abc/lotto.py[tags=LOTTERY_BLOWER] <1> O construtor aceita qualquer iterável: o argumento é usado para construir uma lista. -<2> a função `random.randrange(…)` levanta um `ValueError` se a faixa de valores +<2> A função `random.randrange(…)` levanta um `ValueError` se a faixa de valores estiver vazia, então capturamos esse erro e trocamos por `LookupError`, para ser compatível com `Tombola`. <3> Caso contrário, o item selecionado aleatoriamente é retirado de @@ -1302,28 +1368,42 @@ faz no <>). Podemos fazer isso mais rápido acessando <5> Sobrescreve `inspect` com uma linha de código. O <> ilustra um idioma que vale a pena mencionar: -em `+__init__+`, `self._balls` armazena `list(iterable)`, e não apenas uma referência para `iterable` -(isto é, nós não meramente atribuímos `self._balls = iterable`, apelidando o argumento). -Como mencionado na <>, isso torna nossa `LottoBlower` flexível, pois o argumento `iterable` pode ser de qualquer tipo iterável. -Ao mesmo tempo, garantimos que os itens serão armazenados em uma `list`, da onde podemos `pop` os itens.ƒ -E mesmo se nós sempre recebêssemos listas no argumento `iterable`, -`list(iterable)` produz uma cópia do argumento, o que é uma boa prática, -considerando que vamos remover itens dali, -e o cliente pode não estar esperando que a lista passada seja -modificada.footnote:[<> (<>) -trata da questão de apelidamento que acabamos de evitar aqui.] - -Chegamos agora à característica dinâmica crucial da tipagem ganso: -declarar subclasses virtuais com o método `register`((("", startref="GTsubclass13")))((("", startref="ABCsubclass13")))((("", startref="IASsubclass13"))) +em `+__init__+`, `self._balls` armazena `list(iterable)`, e não apenas uma +referência para `iterable` (isto é, nós não apenas atribuímos +`self._balls = iterable`, apelidando o argumento). +Como mencionado na <>, isso torna a `LottoBlower` +flexível, pois o argumento `iterable` pode ser de qualquer tipo iterável. +Ao mesmo tempo, garantimos que os itens serão armazenados em uma `list`, +de onde podemos retirar itens com `.pop()`. +E mesmo quando recebemos uma lista no argumento `iterable`, +`list(iterable)` produz uma cópia, o que é uma boa prática, +considerando que vamos remover itens da lista, +e o cliente pode não estar esperando +que a lista passada seja modificada.footnote:[A +<> trata do problema de apelidamento +que acabamos de evitar aqui.] + +Chegamos agora à característica dinâmica da tipagem ganso: +declarar subclasses virtuais com o método `register`. +((("", startref="GTsubclass13")))((("", startref="ABCsubclass13")))((("", startref="IASsubclass13"))) [[virtual_subclass_sec]] ==== Uma subclasse virtual de uma ABC -Uma((("goose typing", "virtual subclasses of ABCs", id="GTvsub13")))((("ABCs (abstract base classes)", "virtual subclasses of ABCs", id="ABCvirt13")))((("virtual subclasses", id="virtsub13")))((("inheritance and subclassing", "virtual subclasses of ABCs", id="IASvirtualabc13"))) característica essencial da tipagem ganso—e uma razão pela qual ela merece um nome de ave aquática—é a habilidade de registrar uma classe como uma _subclasse virtual_ de uma ABC, mesmo se a classe não herde da ABC. -Ao fazer isso, prometemos que a classe implementa fielmente a interface definida na ABC—e Python vai acreditar em nós sem checar. -Se mentirmos, vamos ser capturados pelas exceções de tempo de execução conhecidas. - -Isso é feito chamando um método de classe `register` da ABC, e será reconhecido assim por `issubclass`, mas não implica na herança de qualquer método ou atributo da ABC. +Uma((("goose typing", "virtual subclasses of ABCs", id="GTvsub13")))((("ABCs +(abstract base classes)", "virtual subclasses of ABCs", +id="ABCvirt13")))((("virtual subclasses", id="virtsub13")))((("inheritance and subclassing", +"virtual subclasses of ABCs", id="IASvirtualabc13"))) +característica essencial da tipagem ganso—e uma razão pela qual ela merece um +nome de ave aquática—é a habilidade de registrar uma classe como uma _subclasse +virtual_ de uma ABC, mesmo se a classe não herde da ABC. Ao fazer isso, +prometemos que a classe implementa fielmente a interface definida na ABC—e +Python vai acreditar em nós sem checar. Se mentirmos, vamos enfrentar +exceções de tempo de execução. + +Isso é feito invocando um método de classe `register` da ABC. +A subclasse registrada será reconhecida por `issubclass`, +mas herdará qualquer método ou atributo da ABC. [WARNING] ==== @@ -1334,14 +1414,16 @@ E mais, neste momento checadores de tipos estáticos não conseguem tratar subcl Mais detalhes em https://fpy.li/13-22[Mypy issue 2922—ABCMeta.register support]. ==== -O método `register` normalmente é invocado como uma função comum (veja <>), -mas também pode ser usado como decorador. -No ((("UML class diagrams", "TomboList"))) <>, usamos a sintaxe de decorador e implementamos `TomboList`, uma subclasse virtual de `Tombola`, ilustrada em <>. +O método `register` é normalmente invocado como uma função comum +(veja a <>), mas também pode ser usado como decorador. +No ((("UML class diagrams", "TomboList"))) <>, +usamos a sintaxe de decorador e implementamos `TomboList`, +uma subclasse virtual de `Tombola`, ilustrada na <>. [role="width-50"] [[tombolist_uml]] -.Diagrama de classe UML para `TomboList`, subclasse real de `list` e subclassse virtual de `Tombola`. -image::../images/flpy_1307.png[align="center",pdfwidth=5cm] +.Diagrama de classe UML para `TomboList`, subclasse real de `list` e subclasse virtual de `Tombola`. +image::../images/flpy_1307.png[align="center",pdfwidth=5.5cm] [[ex_tombolist]] .tombolist.py: a classe `TomboList` é uma subclasse virtual de `Tombola` @@ -1352,15 +1434,29 @@ include::../code/13-protocol-abc/tombolist.py[] ---- ==== <1> `TomboList` é registrada como subclasse virtual de `Tombola`. + <2> `TomboList` estende `list`. -<3> `TomboList` herda seu comportamento booleano de `list`, e isso retorna `True` se a lista não estiver vazia. -<4> Nosso `pick` chama `self.pop`, herdado de `list`, passando um índice aleatório para um item. + +<3> `TomboList` herda seu comportamento booleano de `list`, devolvendo +`True` se a lista não estiver vazia. + +<4> Nosso `pick` invoca `self.pop`, herdado de `list`, passando um índice +aleatório para um item. + <5> `TomboList.load` é o mesmo que `list.extend`. -<6> `loaded` delega para `bool`.footnote:[O truque usado com `load()` não funciona com `loaded()`, pois o tipo `list` não implementa `+__bool__+`, o método que eu teria de vincular a `loaded`. O `bool()` nativo não precisa de `+__bool__+` para funcionar, porque pode também usar `+__len__+`. Veja https://fpy.li/2g["4.1. Teste do Valor Verdade"] no capítulo "Tipos Embutidos" da documentação de Python.] -<7> É sempre possível chamar `register` dessa forma, e é útil fazer assim quando você precisa registrar uma classe que você não mantém, mas que implementa a interface. -Note que, por causa do registro, -as funções `issubclass` e `isinstance` agem como se `TomboList` fosse uma subclasse de `Tombola`: +<6> `loaded` delega para `bool`.footnote:[O truque usado com `load()` não +funciona com `loaded()`, pois o tipo `list` não implementa `+__bool__+`, o +método que eu teria de vincular a `loaded`. O `bool()` nativo não precisa de +`+__bool__+` para funcionar, porque pode também usar `+__len__+`. Veja +https://fpy.li/2g[4.1. Teste do Valor Verdade] na documentação de Python.] + +<7> É sempre possível invocar `register` dessa forma, e é útil fazer assim +quando você precisa registrar uma classe cujo código você não mantém, +mas que implementa a interface. + +Note que, por causa do registro, as funções `issubclass` e `isinstance` agem +como se `TomboList` fosse uma subclasse de `Tombola`: [source, python] ---- @@ -1373,10 +1469,15 @@ True True ---- -Entretanto, a herança é guiada por um atributo de classe especial chamado `+__mro__+`—a Ordem de Resolução do Método (_mro é a sigla de Method Resolution Order_). -Esse atributo basicamente lista a classe e suas superclasses na ordem que Python usa para procurar métodos.footnote:[Há toda uma explicação sobre o atributo de classe `+__mro__+` na <>. Por agora, essas informações básicas são o suficiente.] -Se você inspecionar o `+__mro__+` de `TomboList`, -verá que ele lista apenas as superclasses "reais"—`list` e `object`: +Entretanto, a herança é guiada por um atributo de classe especial chamado +`+__mro__+`—sigla de _Method Resolution Order_ +(Ordem de Resolução de Métodos). Esse atributo lista a +classe e suas superclasses na ordem que Python segue para procurar +métodos.footnote:[Há toda uma explicação +sobre o atributo de classe `+__mro__+` na <>. Por agora, essas +informações básicas são o suficiente.] Se você inspecionar o `+__mro__+` de +`TomboList`, verá que ele lista apenas as superclasses "reais"—`list` e +`object`: [source, python] ---- @@ -1390,13 +1491,17 @@ Isso conclui nosso estudo de caso da ABC `Tombola`. Na próxima seção, vamos falar sobre como a função `register` das ABCs é usada na vida real.((("", startref="GTvsub13")))((("", startref="ABCvirt13")))((("", startref="virtsub13")))((("", startref="IASvirtualabc13"))) [[register_usage]] -==== O Uso de register na Prática - -No <>, usamos((("goose typing", "usage of register")))((("ABCs (abstract base classes)", "usage of register"))) `Tombola.register` como um decorador de classe. -Antes de Python 3.3, `register` não podia ser usado dessa forma—ele tinha que ser chamado, como uma função normal, após a definição da classe, como sugerido pelo comentário no final do <>. -Entretanto, ainda hoje ele mais usado como uma função para registrar classes definidas em outro lugar. -Por exemplo, no https://fpy.li/13-24[código-fonte] do módulo `collections.abc`, -os tipos nativos `tuple`, `str`, `range`, e `memoryview` são registrados como subclasses virtuais de `Sequence` assim: +==== O uso de `register` na prática + +No <>, usamos((("goose typing", "usage of register")))((("ABCs (abstract base classes)", +"usage of register"))) `Tombola.register` como um +decorador de classe. Antes de Python 3.3, `register` não podia ser usado dessa +forma—ele tinha que ser invocado como uma função normal após a definição da +classe, como sugerido pelo comentário no final do <>. Mas `register` +continua sendo usado como uma função para registrar classes definidas em +outro lugar. Por exemplo, no https://fpy.li/13-24[código-fonte] do módulo +`collections.abc`, os tipos nativos `tuple`, `str`, `range`, e `memoryview` são +registrados como subclasses virtuais de `Sequence` assim: [source, python] ---- @@ -1418,19 +1523,25 @@ A próxima seção explica isso. [[subclasshook_sec]] ==== Tipagem estrutural com ABCs -As ABCs((("goose typing", "structural typing with ABCs", id="GTstruct13")))((("ABCs (abstract base classes)", "structural typing with", id="ABCstruct13")))((("structural typing", id="strtype13"))) +As ABCs((("goose typing", "structural typing with ABCs", id="GTstruct13")))((("ABCs (abstract base classes)", +"structural typing with", id="ABCstruct13")))((("structural typing", id="strtype13"))) são usadas principalmente com tipagem nominal. -Quando uma classe `Sub` herda explicitamente de `AnABC`, ou está registrada com `AnABC`, o nome de -`AnABC` fica ligado ao da classe `Sub`— e é assim que, durante a execução, `issubclass(AnABC, Sub)` retorna `True`. +Quando uma classe `Sub` herda explicitamente de `UmaABC`, ou está registrada com +`UmaABC`, o nome de `UmaABC` fica ligado ao da classe `Sub`—é assim que, durante +a execução, `issubclass(UmaABC, Sub)` devolve `True`. -Em contraste, a tipagem estrutural diz respeito a olhar para a estrutura da interface pública de um objeto para determinar seu tipo: -um objeto é _consistente-com_ um tipo se implementa os métodos definidos no tipo.footnote:[O conceito de consistência de tipo é explicado na <>.] -A tipagem pato estática e a tipagem pato dinâmica são duas abordagens à tipagem estrutural. +Em contraste, a tipagem estrutural diz respeito a olhar para a estrutura da +interface pública de um objeto para determinar seu tipo: um objeto é +_consistente-com_ um tipo se implementa os métodos definidos no tipo.footnote:[O +conceito de consistência de tipo é explicado na <>.] A +tipagem pato estática e a tipagem pato dinâmica são duas abordagens à tipagem +estrutural. -E ocorre que algumas ABCs também suportam tipagem estrutural, -Em seu ensaio, <>, Alex mostra que uma classe pode ser reconhecida como subclasse de uma ABC mesmo sem registro. -Aqui está novamente o exemplo dele, com um teste adicional usando `issubclass`: +Acontece que algumas ABCs também suportam tipagem estrutural. +Em seu ensaio _<>_, Alex mostra que uma classe pode ser +reconhecida como subclasse de uma ABC mesmo sem registro. Aqui está novamente o +exemplo dele, com um teste adicional usando `issubclass`: [source, python] ---- @@ -1444,12 +1555,13 @@ True True ---- -A classe `Struggle` é considerada uma subclasse de `abc.Sized` pela função `issubclass` -(e, consequentemente, também por `isinstance`) porque `abc.Sized` implementa um método de classe especial chamado `+__subclasshook__+`. +A classe `Struggle` é considerada uma subclasse de `abc.Sized` pela função +`issubclass` (e, consequentemente, também por `isinstance`) porque `abc.Sized` +implementa um método de classe especial chamado `+__subclasshook__+`. -O `+__subclasshook__+` de `Sized` verifica se o argumento classe tem um atributo chamado `+__len__+`. -Se tiver, então a classe é considerada uma subclasse virtual de `Sized`. -Veja <>. +O `+__subclasshook__+` de `Sized` verifica se o argumento classe tem um atributo +chamado `+__len__+`. Se tiver, então a classe é considerada uma subclasse +virtual de `Sized`. Veja o <>. [[sized_source_code]] .Definição de `Sized` no código-fonte de https://fpy.li/13-25[Lib/_collections_abc.py] @@ -1472,43 +1584,52 @@ class Sized(metaclass=ABCMeta): return NotImplemented # <3> ---- ==== -<1> Se há um atributo chamado `+__len__+` no `+__dict__+` de qualquer classe listada em `+C.__mro__+` (isto é, `C` e suas superclasses)... -<2> ...retorna `True`, sinalizando que `C` é uma subclasse virtual de `Sized`. -<3> Caso contrário retorna `NotImplemented`, para permitir que a checagem de subclasse continue. +<1> Se há um atributo chamado `+__len__+` no `+__dict__+` de qualquer classe +listada em `+C.__mro__+` (isto é, `C` e suas superclasses)... +<2> ...devolve `True`, sinalizando que `C` é uma subclasse virtual de `Sized`. +<3> Caso contrário devolve `NotImplemented`, para permitir que a checagem de subclasse continue. [NOTE] ==== Se você tiver interesse nos detalhes da checagem de subclasse, estude o código-fonte do método `+ABCMeta.__subclasscheck__+` no Python 3.6: https://fpy.li/13-26[_Lib/abc.py_]. -Cuidado: ele tem muitos ifs e duas chamadas recursivas. -No Python 3.7, Ivan Levkivskyi and Inada Naoki reescreveram em C a maior parte da lógica do módulo `abc`, para melhorar o desempenho. +Saiba que é complicado: lá há muitos ifs e duas chamadas recursivas. +No Python 3.7, Ivan Levkivskyi e Inada Naoki reescreveram em C +a maior parte da lógica do módulo `abc`, para melhorar o desempenho. Veja https://fpy.li/13-27[Python issue #31333]. A implementação atual de `+ABCMeta.__subclasscheck__+` simplesmente chama `_abc_subclasscheck`. O código-fonte em C relevante está em https://fpy.li/13-28[_cpython/Modules/_abc.c#L605_]. ==== -É assim que `+__subclasshook__+` permite às ABCs suportarem a tipagem estrutural. -Você pode formalizar uma interface com uma ABC, -você pode fazer checagens `isinstance` com aquela ABC, -e ainda ter um classe sem qualquer relação de herança -aprovada por uma checagem de `issubclass` porque ela implementa um certo método. -(ou porque ela faz o que quer que seja necessário para convencer um `+__subclasshook__+` a dar a ela seu aval). - -É uma boa ideia implementar `+__subclasshook__+` em nossas próprias ABCs? Provavelmente não. -Todas as implementações de `+__subclasshook__+` que vi no código-fonte de Python estão em ABCs como `Sized`, que declara apenas um método especial, e elas simplesmente verificam a presença do nome daquele método especial. -Dado seu status "especial", é quase certeza que qualquer método chamado `+__len__+` faz o que se espera. -Mas mesmo no reino dos métodos especiais e ABCs fundamentais, -pode ser arriscado fazer tais suposições. -Por exemplo, mapeamentos implementam `+__len__+`, `+__getitem__+`, e `+__iter__+`, mas corretamente não são considerados subtipos de `Sequence`, -pois você não pode recuperar itens usando deslocamentos inteiros ou faixas. -Por isso a classe https://fpy.li/13-29[`abc.Sequence`] não implementa `+__subclasshook__+`. - -Para ABCs que você ou eu podemos escrever, um `+__subclasshook__+` seria ainda menos confiável. -Não estou preparado para acreditar que qualquer classe chamada `Spam` que implemente ou herde -`load`, `pick`, `inspect`, e `loaded` vai necessariamente se comportar como uma `Tombola`. -É melhor deixar o programador afirmar isso, fazendo de `Spam` uma subclasse de `Tombola`, ou registrando a classe com `Tombola.register(Spam)`. -Claro, o seu `+__subclasshook__+` poderia também verificar assinaturas de métodos e outras características, mas não creio que valha o esforço.((("", startref="GTstruct13")))((("", startref="ABCstruct13")))((("", startref="strtype13"))) +É assim que `+__subclasshook__+` permite às ABCs suportarem a tipagem +estrutural. Você pode formalizar uma interface com uma ABC, pode fazer checagens +`isinstance` com aquela ABC, e ainda ter uma classe sem qualquer relação de +herança aprovada por uma checagem de `issubclass` porque ela implementa um certo +método (ou porque ela faz o necessário para convencer o +`+__subclasshook__+` da ABC aprová-la como subclasse virtual). + +É uma boa ideia implementar `+__subclasshook__+` em nossas próprias ABCs? +Provavelmente não. Todas as implementações de `+__subclasshook__+` que vi no +código-fonte de Python estão em ABCs como `Sized`, que declara apenas um método +especial, e elas simplesmente verificam a presença do nome daquele método +especial. Dado seu status "especial", é quase certeza que qualquer método +chamado `+__len__+` faz o que se espera. Mas mesmo no reino dos métodos +especiais e ABCs fundamentais, pode ser arriscado fazer tais suposições. Por +exemplo, mapeamentos implementam `+__len__+`, `+__getitem__+`, e `+__iter__+`, +mas corretamente não são considerados subtipos de `Sequence`, pois não podemos +recuperar itens usando índices a partir de zero ou obter fatias. Por isso a classe +https://fpy.li/13-29[`abc.Sequence`] não implementa `+__subclasshook__+`. + +Para ABCs que você ou eu podemos escrever, um `+__subclasshook__+` seria ainda +menos confiável. Não estou preparado para acreditar que qualquer classe chamada +`Spam` que implemente ou herde `load`, `pick`, `inspect`, e `loaded` vai +necessariamente se comportar como uma `Tombola`. É melhor deixar o programador +afirmar isso, fazendo de `Spam` uma subclasse de `Tombola`, ou registrando a +classe com `Tombola.register(Spam)`. Claro, o seu `+__subclasshook__+` poderia +também verificar assinaturas de métodos e outras características, mas não creio +que valha o esforço.((("", startref="GTstruct13")))((("", +startref="ABCstruct13")))((("", startref="strtype13"))) [[static_protocols_sec]] @@ -1516,20 +1637,29 @@ Claro, o seu `+__subclasshook__+` poderia também verificar assinaturas de méto [NOTE] ==== -Vimos algo sobre protocolos estáticos((("protocols", "static protocols", id="Pstatic13"))) -em <> (<>). -Até considerei deixar toda a discussão sobre protocolos para esse capítulo, -mas decidi que a apresentação inicial de dicas de tipo em funções precisava incluir protocolos, pois a tipagem pato é uma parte essencial de Python, -e checagem de tipos estática sem protocolos não consegue lidar muito bem com as APIs pythônicas. + +Vimos algo sobre protocolos estáticos((("protocols", "static protocols", +id="Pstatic13"))) na <>. Pensei em deixar toda a discussão +sobre protocolos para este capítulo, mas decidi que a apresentação inicial de +dicas de tipo em funções precisava incluir protocolos, pois a tipagem pato é uma +parte essencial de Python, e a checagem de tipos estática sem protocolos não +consegue lidar muito bem com muitas APIs pythônicas. + ==== -Vamos encerrar esse capítulo ilustrando os protocolos estáticos com dois exemplos simples, e uma discussão sobre as ABCs numéricas e protocolos. -Começaremos mostrando como um protocolo estático torna possível anotar e checar tipos na função `double()`, que vimos antes na <>. +Vamos encerrar este capítulo ilustrando os protocolos estáticos com dois +exemplos simples, e uma discussão sobre as ABCs numéricas e protocolos. +Começaremos mostrando como um protocolo estático possibilita anotar e checar +tipos na função `double()`, que vimos antes na <>. [[typed_double_sec]] ==== A função double tipada -Quando((("static protocols", "typed double function", id="SPtypeddouble13")))((("typed double function", id="typdblf13")))((("double() function", id="double13")))((("functions", "double() function"))) eu apresento Python para programadores mais acostumados com uma linguagem de tipagem estática, um de meus exemplos favoritos é essa função `double` simples: +Quando((("static protocols", "typed double function", +id="SPtypeddouble13")))((("typed double function", id="typdblf13")))((("double() +function", id="double13")))((("functions", "double() function"))) apresento +Python para programadores mais habituados à tipagem estática, um de meus +exemplos é esta função `double` capaz de lidar com uma variedade de tipos: [source, python] ---- @@ -1547,12 +1677,21 @@ Quando((("static protocols", "typed double function", id="SPtypeddouble13")))((( Fraction(4, 5) ---- -Antes da introdução dos protocolos estáticos, não havia uma forma prática de acrescentar dicas de tipo a `double` sem limitar seus usos possíveis.footnote:[Certo, double()` não é muito útil, exceto como um exemplo. -Mas a biblioteca padrão de Python tem muitas funções que não poderiam ser anotadas de modo apropriado antes dos protocolos estáticos serem adicionados, no Python 3.8. Ajudei a corrigir alguns bugs no _typeshed_ acrescentando dicas de tipo com o uso de protocolos. -Por exemplo, o _pull request_ (nome do processo de pedido de envio de modificações a um repositório de código) que consertou https://fpy.li/shed4051["Should Mypy warn about potential invalid arguments to `max`? (Deveria o Mypy avisar sobre argumentos potencialmente inválidos passados a `max`?)"] aproveitava um protocolo `_SupportsLessThan`, que usei para melhorar as anotações de `max`, `min`, `sorted`, e `list.sort`.] +Antes da introdução dos protocolos estáticos, não havia uma forma prática de +acrescentar dicas de tipo a `double` sem limitar seus usos +possíveis.footnote:[Concordo que `double()` não é muito útil, exceto como um exemplo. +Mas a biblioteca padrão de Python tem muitas funções que não poderiam ser +anotadas de modo apropriado antes dos protocolos estáticos serem adicionados, no +Python 3.8. Ajudei a corrigir alguns bugs no _typeshed_ acrescentando dicas de +tipo com protocolos. Por exemplo, no _pull request_ que consertou +https://fpy.li/shed4051[_Should Mypy warn about potential invalid arguments to`max`?_] +(Deveria o Mypy avisar sobre argumentos potencialmente inválidos passados a `max`?) +defini um protocolo `_SupportsLessThan`, que usei para melhorar +as anotações de `max`, `min`, `sorted`, e `list.sort`.] -Graças à tipagem pato, `double` funciona mesmo com tipos do futuro, tal como a classe `Vector` aprimorada que veremos no <> (<>): +Graças à tipagem pato, `double` funciona mesmo com tipos inventados depois, tal +como a classe `Vector` aprimorada que veremos na <>: [source, python] ---- @@ -1561,11 +1700,15 @@ Graças à tipagem pato, `double` funciona mesmo com tipos do futuro, tal como a Vector([22.0, 24.0, 26.0]) ---- -A implementação inicial de dicas de tipo no Python era um sistema de tipos nominal: -o nome de um tipo em uma anotação tinha que corresponder ao nome do tipo do argumento real—ou com o nome de uma de suas superclasses. -Como é impossível nomear todos os tipos que implementam um protocolo (suportando as operações requeridas), a tipagem pato não podia ser descrita por dicas de tipo antes de Python 3.8. +A implementação inicial de dicas de tipo no Python era um sistema de tipos +nominal: o nome de um tipo em uma anotação tinha que corresponder ao nome do +tipo do argumento real—ou com o nome de uma de suas superclasses. Como é +impossível nomear todos os tipos que implementam um protocolo (suportando as +operações requeridas), a tipagem pato não podia ser descrita por dicas de tipo +antes do Python 3.8. -Agora, com `typing.Protocol`, podemos informar ao Mypy que `double` recebe um argumento `x` que suporta `x * 2`. +Agora, com `typing.Protocol`, podemos informar ao Mypy que `double` recebe um +argumento `x` que suporta `x * 2`. O <> mostra como. @@ -1581,37 +1724,46 @@ include::../code/13-protocol-abc/double/double_protocol.py[] <2> `+__mul__+` é a essência do protocolo `Repeatable`. O parâmetro `self` normalmente não é anotado—presume-se que seu tipo seja a classe. Aqui usamos `T` para assegurar que o tipo do resultado é o mesmo tipo de `self`. -Além disso observe que `repeat_count` está limitado nesse protocolo a `int`. +Além disso observe que decidi limitar `repeat_count` ao tipo `int` neste protocolo. <3> A variável de tipo `RT` é vinculada pelo protocolo `Repeatable`: o checador de tipos vai exigir que o tipo efetivo implemente `Repeatable`. -<4> Agora o checador de tipos pode checar que o parâmetro `x` é um objeto que pode ser multiplicado por um inteiro, e que o valor retornado tem o mesmo tipo que `x`. +<4> Agora o checador de tipos pode checar que o parâmetro `x` é um objeto que +pode ser multiplicado por um inteiro, e que o valor retornado tem o mesmo tipo +que `x`. -Este exemplo mostra porque o título da https://fpy.li/pep544[PEP 544] é -"_Protocols: Structural subtyping (static duck typing)._ (Protocolos: Subtipagem estrutural (tipagem pato estática))." -O tipo nominal de `x`, argumento efetivamente passado a `double`, é irrelevante, desde que grasne—ou seja, desde que implemente `+__mul__+`.((("", startref="SPtypeddouble13")))((("", startref="typdblf13")))((("", startref="double13"))) +Este exemplo mostra por que o subtítulo da https://fpy.li/pep544[PEP 544] é +_static duck typing_ (tipagem pato estática). O tipo nominal do argumento +concreto `x` passado a `double`, é irrelevante, desde que grasne—ou seja, +desde que implemente `+__mul__+`.((("", startref="SPtypeddouble13")))((("", +startref="typdblf13")))((("", startref="double13"))) [[runtime_checkable_proto_sec]] -==== Protocolos estáticos checados durante a Execução +==== Protocolos estáticos checados durante a execução -No ((("static protocols", "runtime checkable", id="SPruntime13"))) Mapa de Tipagem (<>), `typing.Protocol` aparece na área de checagem estática—a metade inferior do diagrama. -Entretanto, ao definir uma subclasse de `typing.Protocol`, você pode usar o decorador `@runtime_checkable` para fazer aquele protocolo aceitar checagens com `isinstance/issubclass` durante a execução. -Isso funciona porque `typing.Protocol` é uma ABC, assim suporta o `+__subclasshook__+` que vimos na <>. +No ((("static protocols", "runtime checkable", id="SPruntime13"))) Mapa de +Tipagem (<>), `typing.Protocol` aparece na área de +checagem estática—a metade inferior do diagrama. Entretanto, ao definir uma +subclasse de `typing.Protocol`, você pode usar o decorador `@runtime_checkable` +para fazer aquele protocolo aceitar checagens com `isinstance/issubclass` +durante a execução. Isso funciona porque `typing.Protocol` é uma ABC, assim +suporta o `+__subclasshook__+` que vimos na <>. -No Python 3.9, o módulo `typing` inclui sete protocolos prontos para uso que são verificáveis durante a execução. -Aqui estão dois deles, citados diretamente da https://fpy.li/13-30[documentação de `typing`]: +No Python 3.9, o módulo `typing` inclui sete protocolos prontos para uso que são +verificáveis durante a execução. Aqui estão dois deles, citados diretamente da +https://fpy.li/gv[documentação de `typing`]: `class typing.SupportsComplex`:: - An ABC with one abstract method, `+__complex__+`. - ("Uma ABC com um método abstrato, `+__complex__+`.") + Um ABC com um método abstrato __complex__. `class typing.SupportsFloat`:: - An ABC with one abstract method, `+__float__+`. - ("Uma ABC com um método abstrato, `+__float__+`.") + Um ABC com um método abstrato __float__. -Esse((("numeric types", "checking for convertibility"))) protocolos foram projetados para checar a "convertibilidade" de tipos numéricos: -se um objeto `o` implementa `+__complex__+`, -então deveria ser possível obter um `complex` invocando `complex(o)`— pois o método especial `+__complex__+` existe para suportar a função embutida [.keep-together]#`complex()`#. +Estes((("numeric types", "checking for convertibility"))) protocolos foram +projetados para checar a "convertibilidade" de tipos numéricos: se um objeto `n` +implementa `+__complex__+`, então deveria ser possível obter um `complex` +invocando `complex(n)`, pois o método especial `+__complex__+` existe para +suportar a função embutida `complex()`. <> mostra o https://fpy.li/13-31[código-fonte] @@ -1633,11 +1785,15 @@ class SupportsComplex(Protocol): ---- ==== -A chave é o método abstrato `+__complex__+`.footnote:[O atributo `+__slots__+` é irrelevante para nossa discussão aqui—é uma otimização sobre a qual falamos na <>.] -Durante a checagem de tipo estática, um objeto será considerado _consistente-com_ o protocolo `SupportsComplex` se implementar um método `+__complex__+` que recebe apenas `self` e retorna um `complex`. +A chave é o método abstrato `+__complex__+`.footnote:[O atributo `+__slots__+` é +irrelevante para nossa discussão aqui—é uma otimização sobre a qual falamos na +<>.] Durante a checagem de tipo estática, um objeto será considerado +_consistente-com_ o protocolo `SupportsComplex` se implementar um método +`+__complex__+` que recebe apenas `self` e retorna um `complex`. -Graças ao decorador de classe `@runtime_checkable`, aplicado a `SupportsComplex`, -aquele protocolo também pode ser utilizado em checagens com `isinstance` no <>. +Graças ao decorador de classe `@runtime_checkable`, aplicado a +`SupportsComplex`, aquele protocolo também pode ser utilizado em checagens com +`isinstance` no <>. [[repeatable_protocol_demo_ex]] .Usando `SupportsComplex` durante a execução @@ -1664,10 +1820,13 @@ False <2> Nenhum dos tipos complexos da NumPy é subclasse do `complex` embutido. <3> Mas os tipos complexos de NumPy implementam `+__complex__+`, então cumprem o protocolo `SupportsComplex`. <4> Portanto, você pode criar objetos `complex` a partir deles. -<5> Infelizmente, o tipo `complex` embutido não implementa `+__complex__+`, apesar de [.keep-together]#`complex(c)`# funcionar sem problemas se `c` for um `complex`. +<5> O tipo `complex` embutido não implementa `+__complex__+`, +mas `complex(c)` funciona sem problemas se `c` for uma instância de +`complex`. -Como consequência deste último ponto, se você quiser testar se um objeto `c` é um `complex` ou `SupportsComplex`, -você pode passar uma tupla de tipos como segundo argumento para [.keep-together]#`isinstance`#, assim: +Como consequência deste último ponto, se você quiser testar se um objeto `c` é +um `complex` ou `SupportsComplex`, você deve passar uma tupla de tipos como +segundo argumento para `isinstance`, assim: [source, python] ---- @@ -1675,7 +1834,9 @@ isinstance(c, (complex, SupportsComplex)) ---- Uma outra alternativa seria usar a ABC `Complex`, definida no módulo `numbers`. -O tipo embutido `complex` e os tipos `complex64` e `complex128` da NumPy são todos registrados como subclasses virtuais de `numbers.Complex`, então isso aqui funciona: +O tipo embutido `complex` e os tipos `complex64` e `complex128` da NumPy são +todos registrados como subclasses virtuais de `numbers.Complex`, então isso aqui +funciona: [source, python] ---- @@ -1686,15 +1847,24 @@ True True ---- -Na primeira edição de _Python Fluente_ eu recomendava o uso das ABCs de `numbers`, mas agora esse não é mais um bom conselho, pois aquelas ABCs não são reconhecidas pelos checadores de tipos estáticos, como veremos na <>. +Na primeira edição de _Python Fluente_ eu recomendava o uso das ABCs de +`numbers`, mas agora esse não é mais um bom conselho, pois aquelas ABCs não são +reconhecidas pelos checadores de tipos estáticos, como veremos na +<>. -Nessa seção eu queria demonstrar que um protocolo verificável durante a execução funciona com `isinstance`, mas na verdade esse exemplo não é um caso de uso particularmente bom de `isinstance`, como a barra lateral <> explica. +Nesta seção eu queria demonstrar que um protocolo verificável durante a execução +funciona com `isinstance`, mas na verdade esse exemplo não é um caso de uso +particularmente bom de `isinstance`, como a barra lateral +<> explica. [TIP] ==== -Se você estiver usando um checador de tipos externo, há uma vantagem nas checagens explícitas com `isinstance`: -quando você escreve uma instrução `if` onde a condição é `isinstance(o, MyType)`, -então o Mypy pode inferir que dentro do bloco `if`, o tipo do objeto `o` é _consistente-com_ `MyType`. + +Se você estiver usando o Mypy, há uma vantagem nas checagens explícitas com +`isinstance`: quando você escreve uma instrução `if` onde a condição é +`isinstance(n, MyType)`, então o Mypy infere que dentro do bloco `if`, o tipo do +objeto `n` é _consistente-com_ `MyType`. + ==== [[duck_typing_friend_box]] @@ -1707,25 +1877,25 @@ apenas tente realizar as operações que você precisa com o objeto, e trate as exceções conforme necessário. Segue um exemplo concreto. Continuando a discussão anterior, -dado um objeto `o` que preciso usar como número complexo, +dado um objeto `n` que preciso usar como número complexo, essa seria uma abordagem: [source, python] ---- -if isinstance(o, (complex, SupportsComplex)): - # do something that requires `o` to be convertible to complex +if isinstance(n, (complex, SupportsComplex)): + # código que precisa converter `n` para `complex` else: - raise TypeError('o must be convertible to complex') + raise TypeError('n must be convertible to complex') ---- A abordagem da tipagem ganso seria usar a ABC `numbers.Complex`: [source, python] ---- -if isinstance(o, numbers.Complex): - # do something with `o`, an instance of `Complex` +if isinstance(n, numbers.Complex): + # código que assume que `n` é instância de `Complex` else: - raise TypeError('o must be an instance of Complex') + raise TypeError('n must be an instance of Complex') ---- Mas eu prefiro aproveitar a tipagem pato e pedir perdão @@ -1734,22 +1904,22 @@ em vez de permissão (Princípio de Hopper): [source, python] ---- try: - c = complex(o) + c = complex(n) except TypeError as exc: - raise TypeError('o must be convertible to complex') from exc + raise TypeError('n must be convertible to complex') from exc ---- -E se de qualquer forma tudo que você vai fazer é levantar um `TypeError`, -eu então omitiria o bloco `try/except/raise` e escreveria apenas isso: +Mas se o único tratamento que você vai dar para o `TypeError` +é levantar `TypeError`, eu escreveria só isso: [source, python] ---- -c = complex(o) +c = complex(n) ---- -Nesse último caso, se `o` não for de um tipo aceitável, -o Python vai levantar uma exceção com uma mensagem bem clara. -Por exemplo, se `o` for uma `tuple`, esse é o resultado: +Neste último caso, se `n` não é de um tipo aceitável, +o Python levantará uma exceção com uma mensagem bem clara. +Por exemplo, se `n` é uma `tuple`, esse é o resultado: [source] ---- @@ -1758,21 +1928,24 @@ TypeError: complex() first argument must be a string or a number, not 'tuple' Em português: "O primeiro argumento de `complex()` deve ser uma string ou um número, não 'tuple'". -Acho a abordagem da tipagem pato muito melhor nesse caso. +A abordagem da tipagem pato é simples e correta neste caso. **** -Agora que vimos como usar protocolos estáticos durante a execução com tipos pré-existentes como `complex` e `numpy.complex64`, -precisamos discutir as limitações de protocolos verificáveis durante a execução.((("", startref="SPruntime13"))) +Agora que vimos como usar protocolos estáticos durante a execução com tipos +pré-existentes como `complex` e `numpy.complex64`, precisamos discutir as +limitações de protocolos verificáveis durante a execução.((("", +startref="SPruntime13"))) [[protocol_type_hints_ignored]] ==== Limitações das checagens de protocolo durante a execução -Vimos((("static protocols", "limitations of runtime protocol checks"))) que dicas de tipo são geralmente ignoradas durante a execução, -e isso também afeta o uso de checagens com `isinstance` or `issubclass` com protocolos estáticos. +Vimos((("static protocols", "limitations of runtime protocol checks"))) que +dicas de tipo são geralmente ignoradas durante a execução, e isso também afeta o +uso de checagens com `isinstance` ou `issubclass` com protocolos estáticos. Por exemplo, qualquer classe com um método `+__float__+` é considerada—durante a execução—uma subclasse virtual de `SupportsFloat`, -mesmo se seu método `+__float__+` não retorne um `float`. +mesmo se seu método `+__float__+` não devolver um `float`. Veja essa sessão no console: @@ -1790,13 +1963,14 @@ Traceback (most recent call last): TypeError: can't convert complex to float ---- -Em Python 3.9, o tipo `complex` tem um método `+__float__+`, -mas ele existe apenas para gerar `TypeError` com uma mensagem de erro explícita. -Se aquele método `+__float__+` tivesse anotações, -o tipo de retorno seria `NoReturn`— que vimos na <>. +Em Python 3.9, o tipo `complex` tem um método `+__float__+`, mas ele existe +apenas para gerar `TypeError` com uma mensagem de erro explícita. Se aquele +método `+__float__+` tivesse anotações, o tipo de retorno seria `NoReturn`— que +vimos na <>. -Mas incluir dicas de tipo em `+complex.__float__+` no _typeshed_ não resolveria esse problema, -porque o interpretador Python em geral ignora dicas de tipo—e também não acessa os arquivos stub do _typeshed_. +Mas incluir dicas de tipo em `+complex.__float__+` no _typeshed_ não resolveria +esse problema, porque o interpretador Python em geral ignora dicas de tipo—e +também não acessa os arquivos de anotações de tipo do _typeshed_. Continuando da sessão anterior de Python 3.9: @@ -1810,34 +1984,46 @@ True True ---- -Então temos resultados enganosos: as checagens durante a execução usando `SupportsFloat` -sugerem que você pode converter um `complex` para `float`, mas na verdade isso gera um erro de tipo. +Então temos resultados enganosos: as checagens durante a execução usando +`SupportsFloat` sugerem que você pode converter um `complex` para `float`, mas +na verdade isso gera um erro de tipo. [WARNING] ==== -O problema específico com o tipo `complex` foi resolvido no Python 3.10.0b4, com a remoção do método `+complex.__float__+`. -Mas o problema geral persiste: -checagens com `isinstance`/`issubclass` só olham para a presença ou ausência de métodos, -sem checar sequer suas assinaturas, muito menos suas anotações de tipo. -E isso não vai mudar tão cedo, porque este tipo de checagem de tipos durante a execução traria um custo de processamento inaceitável.footnote:[Agradeço a Ivan Levkivskyi, -co-autor da https://fpy.li/pep544[PEP 544] (sobre Protocolos), -por apontar que checagem de tipo não é apenas uma questão de checar se o tipo de `x` é `T`: é sobre determinar que o tipo de `x` é _consistente-com_ `T`, o que pode ser caro. -Não é de se espantar que o Mypy leve alguns segundos para fazer uma checagem de tipos, mesmo em scripts Python curtos.] +O problema específico com o tipo `complex` foi resolvido no Python 3.10, com +a remoção do método `+complex.__float__+`. + +Mas o problema geral persiste: checagens com `isinstance`/`issubclass` só olham +para a presença ou ausência de métodos, sem checar sequer suas assinaturas, +muito menos suas anotações de tipo. E isso não vai mudar tão cedo, porque este +tipo de checagem de tipos durante a execução traria um custo de processamento +inaceitável.footnote:[Agradeço a Ivan Levkivskyi, co-autor da +https://fpy.li/pep544[PEP 544] (sobre protocolos), por apontar que checagem de +tipo não é apenas uma questão de checar se o tipo de `x` é `T`: é sobre +determinar que o tipo de `x` é _consistente-com_ `T`, o que pode ser custoso. +Não é de se espantar que o Mypy leve alguns segundos para fazer uma checagem +de tipos, mesmo em scripts Python curtos.] + ==== -Agora veremos como implementar um protocolo estático em uma classe definida pelo usuário. +Agora veremos como implementar um protocolo estático em uma classe definida pelo +usuário. [[support_typing_proto]] ==== Suportando um protocolo estático -Lembra((("static protocols", "supporting", id="SPsupport13"))) da classe `Vector2d`, que desenvolvemos em <>? -Dado que tanto um número `complex` quanto uma instância de `Vector2d` consistem em um par de números de ponto flutuante, faz sentido suportar a conversão de `Vector2d` para `complex`. +Lembra((("static protocols", "supporting", id="SPsupport13"))) da classe +`Vector2d`, que desenvolvemos no <>? Dado que tanto um número +`complex` quanto uma instância de `Vector2d` consistem em um par de números de +ponto flutuante, faz sentido suportar a conversão de `Vector2d` para `complex`. O <> mostra a implementação do método `+__complex__+`, -para melhorar a última versão de `Vector2d`, vista no <>. -Para deixar o serviço completo, podemos suportar a operação inversa, com um método de classe `fromcomplex`, que constrói um `Vector2d` a partir de um `complex`. +para melhorar a última versão de `Vector2d`, vista no <> do <>. +Para deixar o serviço completo, podemos suportar a operação inversa, com um +método de classe `fromcomplex`, que constrói um `Vector2d` a partir de um +`complex`. [[ex_vector2d_complex_v4]] ._vector2d_v4.py_: métodos para conversão de e para `complex` @@ -1847,9 +2033,12 @@ Para deixar o serviço completo, podemos suportar a operação inversa, com um m include::../code/13-protocol-abc/typing/vector2d_v4.py[tags=VECTOR2D_V4_COMPLEX] ---- ==== -<1> Presume que `datum` tem atributos `.real` e `.imag`. Veremos uma implementação melhor no <>. -Dado o código acima, e o método `+__abs__+` que o `Vector2d` já tinha em <>, temos o seguinte: +<1> Presume que `n` tem atributos `.real` e `.imag`. Veremos uma +implementação melhor no <>. + +Dado o código acima, e o método `+__abs__+` que o `Vector2d` já tinha em +<> do <>, temos o seguinte: [source, python] ---- @@ -1868,9 +2057,10 @@ True Vector2d(3.0, 4.0) ---- -Para checagem de tipos durante a execução, o <> serve bem, -mas para uma cobertura estática e relatório de erros melhores com o Mypy, os métodos -`+__abs__+`, `+__complex__+`, e `fromcomplex` deveriam receber dicas de tipo, como mostrado no <>. +Para checagem de tipos durante a execução, o <> serve +bem, mas para uma cobertura estática e relatório de erros melhores com o Mypy, +os métodos `+__abs__+`, `+__complex__+`, e `fromcomplex` deveriam receber dicas +de tipo, como mostrado no <>. [[ex_vector2d_complex_v5]] ._vector2d_v5.py_: acrescentando anotações aos métodos mencionados @@ -1880,45 +2070,61 @@ mas para uma cobertura estática e relatório de erros melhores com o Mypy, os m include::../code/13-protocol-abc/typing/vector2d_v5.py[tags=VECTOR2D_V5_COMPLEX] ---- ==== -[role="pagebreak-before less_space"] -<1> A anotação de retorno `float` é necessária, senão o Mypy infere `Any`, e não checa o corpo do método. -<2> Mesmo sem a anotação, o Mypy foi capaz de inferir que isso retorna um `complex`. A anotação evita um aviso, dependendo da sua configuração do Mypy. -<3> Aqui `SupportsComplex` garante que `datum` é conversível. -<4> Essa conversão explícita é necessária, pois o tipo `SupportsComplex` não declara os atributos `.real` e `.img`, usados na linha seguinte. -Por exemplo, `Vector2d` não tem esses atributos, mas implementa `+__complex__+`. - -O tipo de retorno de `fromcomplex` pode ser `Vector2d` se a linha `from __future__ import annotations` aparecer no início do módulo. -Aquela importação faz as dicas de tipo serem armazenadas como strings, sem serem processadas durante a importação, quando as definições de função são tratadas. -Sem o `+__future__+` import of `annotations`, -`Vector2d` é uma referência inválida neste momento (a classe não está inteiramente definida ainda) e deveria ser escrita como uma string: -`'Vector2d'`, como se fosse uma referência adiantada. -Essa importação de `+__future__+` foi introduzida na -https://fpy.li/pep563[PEP 563—Postponed Evaluation of Annotations], implementada no Python 3.7. -Aquele comportamento estava marcado para se tornar default no 3.10, mas a mudança foi adiada para uma versão futura.footnote:[Leia a https://fpy.li/13-32[decisão] (EN) de Python Steering Council no python-dev.] + +<1> A anotação de resultado `float` é necessária, senão o Mypy infere `Any`, e não +checa o corpo do método. + +<2> Mesmo sem a anotação, o Mypy inferiu que isto devolve um +`complex`. A anotação evita um aviso, dependendo da configuração do Mypy. + +<3> Aqui `SupportsComplex` garante que `n` é conversível. + +<4> Esta conversão explícita é necessária, pois um tipo _consistente-com_ +`SupportsComplex` não necessariamente tem os atributos `.real` e `.img`, +que usamos na linha seguinte. A própria classe `Vector2d` não tem estes +atributos, mas implementa `+__complex__+`. + +O tipo de retorno de `fromcomplex` pode ser `Vector2d` se a linha `from +{dunder}future{dunder} import annotations` aparecer no início do módulo. Aquela +importação faz as dicas de tipo serem armazenadas como strings, sem serem +processadas durante a importação, quando as definições de função são tratadas. +Sem o `+__future__+` import of `annotations`, `Vector2d` é uma referência +inválida neste momento (a classe não está inteiramente definida ainda) e deveria +ser escrita como uma string: `'Vector2d'`, como se fosse uma referência +adiantada. Essa importação de `+__future__+` foi introduzida na +https://fpy.li/pep563[PEP 563—Postponed Evaluation of Annotations], implementada +no Python 3.7. Aquele comportamento estava marcado para se tornar default no +3.10, mas a mudança foi adiada para uma versão futura.footnote:[Leia a +https://fpy.li/13-32[decisão] do Python Steering Council na lista _python-dev_.] Quando isso acontecer, a importação será redundante mas inofensiva. -Agora vamos criar—e depois estender—um novo protocolo estático.((("", startref="SPsupport13"))) +Agora vamos criar—e depois estender—um novo protocolo estático.((("", +startref="SPsupport13"))) [[designing_static_proto_sec]] ==== Projetando um protocolo estático -Quando((("static protocols", "designing", id="SPdesign13"))) estudamos tipagem ganso, vimos a ABC `Tombola` em <>. -Aqui vamos ver como definir uma interface similar usando um protocolo estático. - -A ABC `Tombola` especifica dois métodos: `pick` e `load`. -Poderíamos também definir um protocolo estático com esses dois métodos, -mas aprendi com a comunidade Go que protocolos de apenas um método tornam a tipagem pato estática mais útil e flexível. -A biblioteca padrão do Go tem inúmeras interfaces, -como `Reader`, uma interface para I/O que requer apenas um método `read`. -Após algum tempo, se você entender que um protocolo mais complexo é necessário, -você pode combinar dois ou mais protocolos para definir um novo. - -Usar um container que escolhe itens aleatoriamente pode ou não exigir o recarregamento do container, mas ele certamente precisa de um método para fazer a efetiva escolha do item, então o método `pick` será o escolhido para o protocolo mínimo `RandomPicker`. -O código do protocolo está no <>, e -seu uso é demonstrado por testes no <>. +Quando((("static protocols", "designing", id="SPdesign13"))) estudamos tipagem +ganso, vimos a ABC `Tombola` na <>. Aqui vamos ver como +definir uma interface similar usando um protocolo estático. + +A ABC `Tombola` especifica dois métodos: `pick` e `load`. Poderíamos também +definir um protocolo estático com esses dois métodos, mas aprendi com a +comunidade Go que protocolos de apenas um método tornam a tipagem pato estática +mais útil e flexível. A biblioteca padrão do Go tem inúmeras interfaces, como +`Reader`, uma interface para E/S que requer apenas um método `read`. +Depois, se você concluir que um protocolo mais complexo é necessário, +pode combinar dois ou mais protocolos para definir um novo. + +Usar um componente que escolhe itens aleatoriamente pode ou não exigir o +recarregamento do componente, mas ele certamente precisa de um método para +sortear um item, então escolhi o método `pick` para o +protocolo mínimo `RandomPicker`. O código do protocolo está no +<>, e seu uso é demonstrado por testes no +<>. [[ex_randompick_protocol]] -._randompick.py_: definition of `RandomPicker` +._randompick.py_: definição de `RandomPicker` ==== [source, python] ---- @@ -1928,10 +2134,12 @@ include::../code/13-protocol-abc/typing/randompick.py[] [NOTE] ==== -O método `pick` retorna `Any`. -Em <> -veremos como tornar `RandomPicker` um tipo genérico, -com um parâmetro que permite aos usuários do protocolo especificarem o tipo de retorno do método `pick`. + +O método `pick` retorna `Any`. Na <> +veremos como tornar `RandomPicker` um tipo genérico, com um parâmetro que +permite aos usuários do protocolo especificarem o tipo de retorno do método +`pick`. + ==== [[ex_randompick_protocol_demo]] @@ -1942,23 +2150,43 @@ com um parâmetro que permite aos usuários do protocolo especificarem o tipo de include::../code/13-protocol-abc/typing/randompick_test.py[] ---- ==== -<1> Não é necessário importar um protocolo estático para definir uma classe que o implementa, Aqui eu importei `RandomPicker` apenas para usá-lo em `test_isinstance` mais tarde. -<2> `SimplePicker` implementa `RandomPicker` — mas não é uma subclasse dele. Isso é a tipagem pato estática em ação. -<3> `Any` é o tipo de retorno default, então essa anotação não é estritamente necessária, mas deixa mais claro que estamos implementando o protocolo `RandomPicker`, como definido em <>. -<4> Não esqueça de acrescentar dicas `-> None` aos seus testes, se você quiser que o Mypy olhe para eles. -<5> Acrescentei uma dica de tipo para a variável `popper`, para mostrar que o Mypy entende que o `SimplePicker` é _consistente-com_. -<6> Esse teste prova que uma instância de `SimplePicker` também é uma instância de `RandomPicker`. -Isso funciona por causa do decorador `@runtime_checkable` aplicado a [.keep-together]#`RandomPicker`#, e porque o `SimplePicker` tem um método `pick`, como exigido. -<7> Esse teste invoca o método `pick` de `SimplePicker`, -verifica que ele retorna um dos itens dados a `SimplePicker`, -e então realiza testes estáticos e de execução sobre o item obtido. -<8> Essa linha gera uma obervação no relatório do Mypy. -Como vimos no <>, `reveal_type` é uma função "mágica" reconhecida pelo Mypy. -Por isso ela não é importada e só conseguimos chamá-la de dentro de blocos `if` protegidos por `typing.TYPE_CHECKING`, que só é `True` para os olhos de um checador de tipos estático, mas é `False` durante a execução. +<1> Não é necessário importar um protocolo estático para definir uma classe que +o implementa; aqui eu importei `RandomPicker` apenas para usá-lo em +`test_isinstance` mais tarde. + +<2> `SimplePicker` implementa `RandomPicker`, mas não é uma subclasse dele. +Isso é a tipagem pato estática em ação. + +<3> `Any` é o tipo de retorno default, então essa anotação não é estritamente +necessária, mas deixa mais claro que estamos implementando o protocolo +`RandomPicker`, como definido em <>. + +<4> Não esqueça de acrescentar dicas `-> None` aos seus testes, se quiser +que o Mypy olhe para eles. + +<5> Acrescentei uma dica de tipo para a variável `popper`, para mostrar que o +Mypy entende que o `SimplePicker` é _consistente-com_. + +<6> Teste provando que uma instância de `SimplePicker` também é uma instância +de `RandomPicker`. Isso funciona por causa do decorador `@runtime_checkable` +aplicado a `RandomPicker`, e porque o `SimplePicker` tem um +método `pick`, como exigido. + +<7> Este teste invoca o método `pick` de `SimplePicker`, verifica que ele +retorna um dos itens dados a `SimplePicker`, e então realiza testes estáticos e +de execução sobre o item obtido. + +<8> Esta linha gera uma observação no relatório do Mypy. -Os dois testes em <> passam. -O Mypy também não vê nenhum erro naquele código, +Como vimos no <> do <>, `reveal_type` é uma função "mágica" +reconhecida pelo Mypy. Por isso ela não é importada e só conseguimos chamá-la +de dentro de blocos `if` protegidos por `typing.TYPE_CHECKING`, que só é `True` +aos olhos de um checador de tipos estático, mas é `False` durante a +execução. + +Os dois testes no <> passam. +O Mypy também não encontra erro naquele código, e mostra o resultado de `reveal_type` sobre o `item` retornado por `pick`: @@ -1968,53 +2196,71 @@ $ mypy randompick_test.py randompick_test.py:24: note: Revealed type is 'Any' ---- -Tendo criado nosso primeiro protocolo, -vamos estudar algumas recomendações sobre essa prática.((("", startref="SPdesign13"))) +Tendo criado nosso primeiro protocolo, vamos estudar algumas recomendações sobre +essa prática.((("", startref="SPdesign13"))) [[best_protocol_design_sec]] ==== Melhores práticas no desenvolvimento de protocolos -Após((("static protocols", "best practices for protocol design"))) 10 anos de experiência com tipagem pato estática em Go, está claro que protocolos estreitos são mais úteis—muitas vezes tais protocolos têm um único método, raramente mais que um par de métodos. -Martin Fowler descreve uma boa ideia para se ter em mente ao desenvolver protocolos: a https://fpy.li/13-33[Role Interface], -(__interface papel__footnote:[NT: "papel" aqui é usado no sentido de incorporação de um personagem]). A ideia é que um protocolo deve ser definido em termos de um papel que um objeto pode desempenhar, e não em termos de uma classe específica. - -Além disso, é comum ver um protocolo definido próximo a uma função que o usa-ou seja, -definido em "código do cliente" em vez de ser definido em uma biblioteca separada. -Isso torna mais fácil criar novos tipos para chamar aquela função, -bom para a extensibilidade e para testes com simulações ou protótipos. - -Ambas as práticas, protocolos estreitos e protocolos em código cliente, evitam um acoplamento muito firme, em acordo com o -https://fpy.li/6v[Princípio da Segregação de Interface], -que podemos resumir como "Clientes não devem ser forçados a depender de interfaces que não usam." - -A página https://fpy.li/13-35["Contributing to typeshed"] (EN) -recomenda a seguinte convenção de nomenclatura para protocolos estáticos (os três pontos a seguir foram traduzidos o mais fielmente possível): - -* Use nomes simples para protocolos que representam um conceito claro (e.g., `Iterator`, [.keep-together]#`Container`#). - -* Use `SupportsX` para protocolos que oferecem métodos que podem ser chamados (e.g., `SupportsInt`, `SupportsRead`, `SupportsReadSeek`).footnote:[Qualquer método pode ser chamado, então essa recomendação não diz muito. Talvez "forneça um ou dois métodos"? De qualquer forma, é uma recomendação, não uma regra absoluta.] -* Use `HasX` para protocolos que tem atributos que podem ser lidos ou escritos, ou métodos _getter/setter_(e.g., `HasItems`, `HasFileno`). - -A biblioteca padrão do Go tem uma convenção de nomenclatura que gosto: -para protocolos de método único, se o nome do método é um verbo, acrescente o sufixo adequado (em inglês, "-er" ou "-or", em geral) para torná-lo um substantivo. -Por exemplo, em vez de `SupportsRead`, temos `Reader`. -Outros exemplos incluem `Formatter`, `Animator`, e `Scanner`. -Para se inspirar, veja -https://fpy.li/13-36["Go (Golang) Standard Library Interfaces (Selected)"] (EN) de Asuka Kenji. - -Uma boa razão para se criar protocolos minimalistas é a habilidade de estendê-los posteriormente, se necessário. -Veremos a seguir que não é difícil criar um protocolo derivado com um método adicional - -==== Estendendo um Protocolo +Após((("static protocols", "best practices for protocol design"))) 10 anos de +experiência com tipagem pato estática em Go, está claro que protocolos estreitos +são mais úteis—muitas vezes tais protocolos têm um único método, raramente mais +que um par de métodos. Martin Fowler descreve uma boa ideia para se ter em mente +ao desenvolver protocolos: a https://fpy.li/13-33[_Role Interface_], +(interface papel—no sentido de incorporar uma personagem). +A ideia é que um protocolo deve ser definido em termos de um papel +que um objeto pode desempenhar, e não em termos de uma classe específica. + +Além disso, é comum ver um protocolo definido próximo a uma função que o usa +para anotar um argumento, em, vez de forçar os clientes da função a importar +uma definição de interface de alguma biblioteca central. +Isso facilita a criação de novos tipos compatíveis com aquela função, +favorecendo a extensibilidade e facilitando testes com _mocks_ +(simulacros). + +As duas práticas, protocolos estreitos e protocolos em código cliente, evitam +um acoplamento muito forte, em acordo com o https://fpy.li/6v[Princípio da +Segregação de Interface], que podemos resumir como "Clientes não devem ser +forçados a depender de interfaces que não usam." + +A página https://fpy.li/13-35[_Contributing to typeshed_] (Colaborando com o typeshed) +recomenda a seguinte convenção de nomenclatura para protocolos estáticos: + +* Use nomes simples para protocolos que representam um conceito claro (e.g., +`Iterator`, `Container`). + +* Use `SupportsX` para protocolos que oferecem métodos que podem ser chamados +(e.g., `SupportsInt`, `SupportsRead`, `SupportsReadSeek`).footnote:[Qualquer +método pode ser chamado, então essa recomendação não diz muito. Talvez "forneça +um ou dois métodos"? De qualquer forma, é uma recomendação, não uma regra +absoluta.] + +* Use `HasX` para protocolos que têm atributos de dados que podem ser lidos ou +escritos, ou métodos _getter/setter_ (e.g., `HasItems`, `HasFileno`). + +A biblioteca padrão do Go tem uma convenção de nomenclatura que eu gosto: para +protocolos de método único, se o nome do método é um verbo, acrescente o sufixo +adequado (em inglês, "-er" ou "-or", em geral) para torná-lo um substantivo. Por +exemplo, em vez de `SupportsRead`, temos `Reader`. Outros exemplos incluem +`Formatter`, `Animator`, e `Scanner`. Para se inspirar, veja +https://fpy.li/13-36["Go (Golang) Standard Library Interfaces (Selected)"] +de Asuka Kenji. + +Uma boa razão para se criar protocolos minimalistas é que eles servem de base +para protocolos mais complexos, quando necessário. Veremos a seguir que não é +difícil criar um protocolo derivado com um método adicional. + +==== Estendendo um protocolo Como((("static protocols", "extending"))) mencionei na seção anterior, os -desenvolvedores Go defendem que, quando em dúvida, melhor escolher o minimalismo ao definir interfaces—o nome usado para protocolos estáticos naquela linguagem. +desenvolvedores Go defendem que, na dúvida, melhor escolher o minimalismo +ao definir interfaces—o nome usado para protocolos estáticos naquela linguagem. Muitas das interfaces Go mais usadas têm um único método. -Quando a prática revela que um protocolo com mais métodos seria útil, -em vezz de adicionar métodos ao protocolo original, -é melhor derivar dali um novo protocolo. -Estender um protocolo estático em Python tem algumas ressalvas, como mostra o <> shows. +Quando a prática revela que um protocolo com mais métodos seria útil, em vez de +adicionar métodos ao protocolo original, é melhor derivar dali um novo +protocolo. Estender um protocolo estático em Python tem algumas ressalvas, como +mostra o <>. [[ex_randompickload_protocol]] ._randompickload.py_: estendendo `RandomPicker` @@ -2024,41 +2270,70 @@ Estender um protocolo estático em Python tem algumas ressalvas, como mostra o < include::../code/13-protocol-abc/typing/randompickload.py[] ---- ==== -<1> Se você quer que o protocolo derivado possa ser checado durante a execução, você precisa aplicar o decorador novamente—seu comportamento não é herdado.footnote:[Para detalhes e justificativa, veja por favor a seção sobre https://fpy.li/13-37[`@runtime_checkable`] (EN) -na PEP 544—Protocols: Structural subtyping (static duck typing).] -<2> Todo protocolo deve nomear explicitamente `typing.Protocol` como uma de suas classes base, além do protocolo que estamos estendendo. Isso é diferente da forma como herança funciona em Python.footnote:[Novamente, leia por favor https://fpy.li/13-38["Merging and extending protocols"] (EN) na PEP 544 para os detalhes e [.keep-together]#justificativas#.] -<3> De volta à programação orientada a objetos "normal": só precisamos declarar o método novo no protocolo derivado. A declaração do método `pick` é herdada de `RandomPicker`. -Isso conclui o último exemplo sobre definir e usar um protocolo estático neste capítulo. +<1> Se você quer que o protocolo derivado possa ser checado durante a execução, +precisa aplicar o decorador `@runtime_checkable` novamente—pois os +comportamentos definidos em decoradores de classes não são +herdados.footnote:[Para detalhes e justificativa, veja a seção sobre +https://fpy.li/13-37[`@runtime_checkable`] na PEP 544—Protocols: Structural +subtyping (static duck typing).] -Para encerrar o capítulo, vamos olhar as ABCs numéricas e sua possível substituição por protocolos numéricos. +<2> Todo protocolo deve nomear explicitamente `typing.Protocol` como uma de suas +classes base, além do protocolo que estamos estendendo. Isto é diferente da +forma como herança funciona de modo geral.footnote:[Novamente, leia +https://fpy.li/13-38[_Merging and extending protocols_] na PEP 544 para os +detalhes e justificativa.] + +<3> De volta à programação orientada a objetos "normal": só precisamos declarar +o método novo no protocolo derivado. A declaração do método `pick` é herdada de +`RandomPicker`. + +Isto conclui o último exemplo sobre definir e usar um protocolo estático neste +capítulo. Para encerrar, vamos olhar as ABCs numéricas e sua possível +substituição por protocolos numéricos. [[numbers_abc_proto_sec]] ==== As ABCs em numbers e os novos protocolos numéricos -Como((("static protocols", "numbers ABCS and numeric protocols", id="SPnumbers13")))((("numbers ABCs", id="number13")))((("numeric protocols", id="numpro13")))((("protocols", "numeric", id="Pnum13"))) vimos em <>, as ABCs no pacote `numbers` da biblioteca padrão funcionam bem para checagem de tipos durante a execução. +Como((("static protocols", "numbers ABCS and numeric protocols", +id="SPnumbers13")))((("numbers ABCs", id="number13")))((("numeric protocols", +id="numpro13")))((("protocols", "numeric", id="Pnum13"))) vimos na +<>, as ABCs no pacote `numbers` da biblioteca padrão +funcionam bem para checagem de tipos durante a execução. -Se você precisa checar um inteiro, pode usar `isinstance(x, numbers.Integral)` para aceitar `int`, `bool` (que é subclasse de `int`) ou outros tipos inteiros oferecidos por bibliotecas externas que registram seus tipos como subclasses virtuais das ABCs de `numbers`. -Por exemplo, a NumPy tem https://fpy.li/13-39[21 tipos inteiros] — bem como várias variações de tipos de ponto flutuante registrados como `numbers.Real`, e números complexos com várias amplitudes de bits, registrados como `numbers.Complex`. +Se você precisa checar um inteiro, pode usar `isinstance(x, numbers.Integral)` +para aceitar `int`, `bool` (que é subclasse de `int`) ou outros tipos inteiros +oferecidos por bibliotecas externas que registram seus tipos como subclasses +virtuais das ABCs de `numbers`. Por exemplo, a NumPy tem +https://fpy.li/13-39[21 tipos inteiros]—bem como diversos tipos de ponto flutuante +registrados como `numbers.Real`, e números complexos com várias amplitudes de +bits, registrados como `numbers.Complex`. [TIP] ==== -De forma algo surpreendente, `decimal.Decimal` não é registrado como uma subclasse virtual de `numbers.Real`. -A razão para isso é que, se você precisa da precisão de `Decimal` no seu programa, -então você quer estar protegido da mistura acidental de números decimais e de números de ponto flutuante (que são menos precisos). + +De forma algo surpreendente, `decimal.Decimal` não é registrado como uma +subclasse virtual de `numbers.Real`. A razão para isso é que, se você precisa da +precisão de `Decimal` no seu programa, então você quer estar protegido da +mistura acidental de números decimais e de números de ponto flutuante (que são +menos precisos). + ==== Infelizmente, a torre numérica não foi projetada para checagem de tipo estática. -A ABC raiz—`numbers.Number`—não tem métodos, -então se você declarar `x: Number`, o Mypy não vai deixar você fazer operações aritméticas ou chamar qualquer método com `X`. +A ABC raiz—`numbers.Number`—não tem métodos, então se você declarar `x: Number`, +o Mypy não vai deixar você fazer operações aritméticas ou chamar qualquer método +com `X`. -Se as ABCs de `numbers` não tem suporte, quais as opções? +Quando as ABCs de `numbers` não servem, quais as opções? -Um bom lugar para procurar soluções de tipagem é no projeto _typeshed_. -Como parte da biblioteca padrão de Python, o módulo `statistics` tem um arquivo stub correspondente no _typeshed_ com dicas de tipo, o https://fpy.li/13-40[_statistics.pyi_], +Um bom lugar para procurar soluções de tipagem é no projeto _typeshed_. Como +parte da biblioteca padrão de Python, o módulo `statistics` tem um arquivo stub +correspondente no _typeshed_ com dicas de tipo, o +https://fpy.li/13-40[_statistics.pyi_]. -Lá você encontrará as seguintes definições, que são usadas para anotar várias funções: +Lá você encontrará as seguintes definições, que são usadas para anotar diversas funções: [source, python] ---- @@ -2068,23 +2343,26 @@ _NumberT = TypeVar('_NumberT', float, Decimal, Fraction) Essa abordagem está correta, mas é limitada. Ela não suporta((("numeric types", "support for"))) tipos numéricos -fora da biblioteca padrão, que as ABCs de `numbers` suportam durante a execução - -quando tipos numéricos são registrados como subclasses virtuais. +fora da biblioteca padrão, que as ABCs de `numbers` suportam durante a +execução—quando tipos numéricos são registrados como subclasses virtuais. A tendência atual é recomendar os protocolos numéricos fornecidos pelo módulo `typing`, -que discutimos na <>. - -Infelizmente, durante a execução os protocolos numéricos podem deixar você na mão. -Como mencionado em <>, -o tipo `complex` no Python 3.9 implementa `+__float__+`, -mas o método existe apenas para lançar uma `TypeError` com uma mensagem explícita: -"can't convert complex to float." ("não é possível converter complex para float") -Por alguma razão, ele também implementa `+__int__+`. -A presença desses métodos faz `isinstance` produzir resultados enganosos no Python 3.9. No Python 3.10, os métodos de `complex` que geravam `TypeError` incondicionalmente foram removidos.footnote:[ver -https://fpy.li/13-41[Issue #41974—Remove `+complex.__float__+`, `+complex.__floordiv__+`, etc].] - -Por outro lado, os tipos complexos da NumPy implementam métodos `+__float__+` e `+__int__+` que funcionam, -emitindo apenas um aviso quando cada um deles é usado pela primeira vez: +como `SupportsFloat`, que discutimos na <>. + +Infelizmente, durante a execução os protocolos numéricos podem deixar você na +mão. Como mencionado na <>, o tipo `complex` no +Python 3.9 implementa `+__float__+`, mas o método existe apenas para lançar uma +`TypeError` com uma mensagem explícita: "can't convert complex to float" (não é +possível converter complex para float). Por alguma razão, ele também implementa +`+__int__+`. A presença destes métodos faz `isinstance` produzir resultados +enganosos no Python 3.9. No Python 3.10, os métodos de `complex` que geravam +`TypeError` incondicionalmente foram removidos.footnote:[ver +https://fpy.li/13-41[Issue #41974—Remove `+complex.__float__+`, +`+complex.__floordiv__+`, etc].] + +Por outro lado, os tipos complexos da NumPy implementam métodos `+__float__+` e +`+__int__+` que funcionam, emitindo apenas um aviso quando cada um deles é usado +pela primeira vez: [source, python] ---- @@ -2099,11 +2377,15 @@ discards the imaginary part ---- O problema oposto também acontece: -Os tipos embutidos `complex`, `float`, e `int`, além `numpy.float16` e `numpy.uint8`, não -tem um método `+__complex__+`, então `isinstance(x, SupportsComplex)` retorna `False` para eles.footnote:[Eu não testei todas as outras variantes de _float_ e _integer_ que a NumPy oferece.] -Os tipo complexos da NumPy, tal como `np.complex64`, implementam `+__complex__+` para conversão em um `complex` embutido. +os tipos embutidos `complex`, `float`, e `int`, bem como `numpy.float16` e +`numpy.uint8`, não têm um método `+__complex__+`, então `isinstance(x, +SupportsComplex)` retorna `False` para eles.footnote:[Eu não testei todas as +outras variantes de _float_ e _integer_ que a NumPy oferece.] Os tipos complexos +da NumPy, tal como `np.complex64`, implementam `+__complex__+` para conversão em +um `complex` embutido. -Entretanto, na prática, o construtor embutido `complex()` trabalha com instâncias de todos esses tipos sem erros ou avisos. +Entretanto, na prática, o construtor embutido `complex()` trabalha com +instâncias de todos esses tipos sem erros ou avisos. [source, python] ---- @@ -2116,12 +2398,13 @@ Entretanto, na prática, o construtor embutido `complex()` trabalha com instânc [(1+0j), (1+0j), (1+0j), (1+0j), (1+0j), (1+0j)] ---- -Isso mostra que checagens de `SupportsComplex` com `isinstance` sugerem que todas aquelas conversões para `complex` falhariam, mas ela são bem sucedidas. -Na mailing list typing-sig, -Guido van Rossum indicou que o `complex` embutido aceita um único argumento, -e essa é a razão daquelas conversões funcionarem. +Isso mostra que checagens de `SupportsComplex` com `isinstance` sugerem que +todas aquelas conversões para `complex` falhariam, mas elas funcionam. Na +lista de discussão _typing-sig_, Guido van Rossum indicou que o `complex` embutido +aceita um único argumento, e por isso as conversões funcionam. -Por outro lado, o Mypy aceita argumentos de todos esses seis tipos em uma chamada à função [.keep-together]#`to_complex()`#, definida assim: +Por outro lado, o Mypy aceita argumentos de todos esses seis tipos em uma +chamada à função `to_complex()`, definida assim: [source, python] ---- @@ -2129,155 +2412,188 @@ def to_complex(n: SupportsComplex) -> complex: return complex(n) ---- -No momento em que escrevo isso, a NumPy não tem dicas de tipo, então seus tipos numéricos são todos `Any`.footnote:[Os tipos numéricos da NumPy são todos registrados com as ABCs apropriadas de `numbers`, que o Mypy ignora.] -Por outro lado, o Mypy de alguma maneira "sabe" que o `int` e o `float` embutidos podem ser convertidos para `complex`, -apesar de, no _typeshed_, apenas a classe embutida `complex` ter o método `+__complex__+`.footnote:[Isso é uma mentira bem intencionada da parte do typeshed: a partir de Python 3.9, o tipo embutido `complex` na verdade não tem mais um método `+__complex__+`.] - -Concluindo, apesar((("numeric types", "checking for convertibility"))) da impressão que a checagem de tipos para tipos numéricos não deveria ser difícil, -a situação atual é a seguinte: -as dicas de tipo da PEP 484 -https://fpy.li/cardxvi[evitam] (EN) a torre numérica -e recomendam implicitamente que os checadores de tipos codifiquem explicitamente as relações de tipo entre os `complex`, `float`, e `int` embutidos. -O Mypy faz isso, e também, [.keep-together]#pragmaticamente#, aceita que `int` e `float` -são _consistente-com_ `SupportsComplex`, apesar deles não implementarem `+__complex__+`. +No momento em que escrevo isso, a NumPy não tem dicas de tipo, então seus tipos +numéricos são todos `Any`.footnote:[Os tipos numéricos da NumPy são todos +registrados com as ABCs apropriadas de `numbers`, que o Mypy ignora.] Por outro +lado, o Mypy de alguma maneira "sabe" que o `int` e o `float` embutidos podem +ser convertidos para `complex`, apesar de, no _typeshed_, apenas a classe +embutida `complex` ter o método `+__complex__+`.footnote:[Isso é uma mentira bem +intencionada da parte do typeshed: a partir de Python 3.9, o tipo embutido +`complex` na verdade não tem mais um método `+__complex__+`.] + +Concluindo, apesar((("numeric types", "checking for convertibility"))) da +expectativa de que a checagem de tipos numéricos não seria difícil, a situação +atual é a seguinte: as dicas de tipo da PEP 484 +https://fpy.li/cardxvi[desprezam] a torre numérica e recomendam implicitamente +que os checadores de tipos tratem como casos especiais as relações de tipo entre +os `complex`, `float`, e `int` embutidos. O Mypy faz isso, e também, +pragmaticamente, aceita que `int` e `float` são _consistente-com_ +`SupportsComplex`, apesar deles não implementarem `+__complex__+`. [TIP] ==== -Só encontrei resultados inesperados usando checagens com `isinstance` em conjunto com os protocolos numéricos `Supports*` quando fiz experiências de conversão de ou para `complex`. -Se você não usa números complexos, pode confiar naqueles protocolos em vez das ABCs de `numbers`. +Só encontrei resultados inesperados usando checagens com `isinstance` em +conjunto com os protocolos numéricos `Supports*` quando fiz experiências de +conversão de ou para `complex`. Se você não usa números complexos, pode confiar +naqueles protocolos em vez das ABCs de `numbers`. ==== As principais lições dessa seção são: -* As ABCs de `numbers` são boas para checagem de tipos durante a execução, mas inadequadas para tipagem estática. -* Os protocolos numéricos estáticos `SupportsComplex`, `SupportsFloat`, etc. funcionam bem para tipagem estática, mas são pouco confiáveis para checagem de tipos durante a execução se números complexos estiverem envolvidos. +* As ABCs de `numbers` são boas para checagem de tipos durante a execução, mas +não servem para tipagem estática. + +* Os protocolos numéricos estáticos `SupportsComplex`, `SupportsFloat`, etc. +funcionam bem para tipagem estática, mas são pouco confiáveis para checagem de +tipos durante a execução se números complexos estiverem envolvidos. -Estamos agora prontos para uma rápida revisão do que vimos nesse capítulo.((("", startref="Pstatic13")))((("", startref="Pnum13")))((("", startref="numpro13")))((("", startref="number13")))((("", startref="SPnumbers13"))) +Estamos agora prontos para a revisão dos temas deste capítulo.((("", +startref="Pstatic13")))((("", startref="Pnum13")))((("", +startref="numpro13")))((("", startref="number13")))((("", +startref="SPnumbers13"))) === Resumo do capítulo -O((("interfaces", "overview of")))((("protocols", "overview of")))((("ABCs (abstract base classes)", "overview of"))) Mapa de Tipagem (<>) é a chave para entender esse capítulo. -Após uma breve introdução às quatro abordagens da tipagem, -comparamos protocolos dinâmicos e estáticos, -os quais suportam tipagem pato e tipagem pato estática, respectivamente. -Os dois tipos de protocolo compartilham uma característica essencial, nunca é exigido de uma classe que ela declare explicitamente o suporte a qualquer protocolo específico. -Uma classe suporta um protocolo simplesmente implementando os métodos necessários. - -A próxima grande seção foi a <>, -onde exploramos os esforços que interpretador Python faz para que os protocolos dinâmicos de sequência e iterável funcionem, incluindo a implementação parcial de ambos. -Então vimos como fazer uma classe implementar um protocolo durante a execução, -através da adição de métodos extra via _monkey patching_. -A seção sobre tipagem pato terminou com sugestões de programação defensiva, -incluindo a detecção de tipos estruturais sem checagens explícitas com `isinstance` ou `hasattr`, usando `try/except` e falhando rápido. - -Após Alex Martelli introduzir a tipagem ganso em <>, -vimos como criar subclasses de ABCs existentes, -examinamos algumas ABCs importantes da biblioteca padrão, -e criamos uma ABC do zero, -que então implementamos da forma tradicional, criando subclasses, e por registro. -Finalizamos aquela seção vendo como o método especial `+__subclasshook__+` permite às ABCs suportarem a tipagem estrutural, pelo reconhecimento de classes não-relacionadas, mas que fornecem os métodos que preenchem os requisitos da interface definida na ABC. - -A última grande seção foi a <>, -onde retomamos o estudo da tipagem pato estática, que havia começado no <>, em <>. -Vimos como o decorador `@runtime_checkable` também aproveita o `+__subclasshook__+` para suportar tipagem estrutural durante a execução—mesmo que o melhor uso dos protocolos estáticos seja com checadores de tipos estáticos, -que podem levar em consideração as dicas de tipo, tornando a tipagem estrutural mais confiável. -Então falamos sobre o projeto e a codificação de um protocolo estático e como estendê-lo. -O capítulo terminou com <>, -que conta a triste história do abandono da torre numérica e das limitações da alternativa proposta: -os protocolos numéricos estáticos tal como `SupportsFloat` e outros, -adicionados ao módulo `typing` no Python 3.8. - -A mensagem principal desse capítulo é que temos quatro maneiras complementares de programar com interfaces no Python moderno, -cada uma com diferentes vantagens e deficiências. -Você possivelmente encontrará casos de uso adequados para cada esquema de tipagem em qualquer base de código de Python moderno de tamanho significativo. -Rejeitar qualquer dessas abordagens tornará seu trabalho como programador Python mais difícil que o necessário. - -Dito isso, Python ganhou sua enorme popularidade enquanto suportava apenas tipagem pato. -Outras linguagens populares, como Javascript, PHP e Ruby, bem como Lisp, Smalltalk, Erlang e Clojure—essas últimas não muito populares mas extremamente influentes—são todas linguagens que tinham e ainda tem um impacto tremendo aproveitando o poder e a simplicidade da tipagem pato. +O((("interfaces", "overview of")))((("protocols", "overview of")))((("ABCs (abstract base classes)", +"overview of"))) Mapa de Tipagem (<>) +é a chave para entender este capítulo. Após uma breve introdução às quatro +abordagens da tipagem, comparamos protocolos dinâmicos e estáticos, que +suportam tipagem pato e tipagem pato estática, respectivamente. Os dois tipos de +protocolo compartilham uma característica essencial: nunca é exigido de uma +classe que ela declare explicitamente o suporte a qualquer protocolo. +Uma classe suporta um protocolo apenas implementando os métodos necessários. + +A próxima parada foi a <>, onde exploramos os esforços que o +interpretador Python faz para que os protocolos dinâmicos de sequência e +iterável funcionem, incluindo a implementação parcial de ambos. Então vimos como +fazer uma classe implementar um protocolo durante a execução, através da adição +de métodos via _monkey patching_. A seção sobre tipagem pato terminou com +sugestões de programação defensiva, incluindo a detecção de tipos estruturais +sem checagens explícitas com `isinstance` ou `hasattr`, usando `try/except` e +falhando logo. + +Após Alex Martelli introduzir a tipagem ganso em _<>_, vimos +como criar subclasses de ABCs existentes, examinamos algumas ABCs importantes da +biblioteca padrão, e criamos uma ABC do zero, que então implementamos por +herança e por registro. Finalizamos aquela seção vendo como o método especial +`+__subclasshook__+` permite que ABCs suportem a tipagem estrutural, pelo +reconhecimento de classes não-relacionadas, mas que fornecem os métodos +exigidos pela interface declarada na ABC. + +Retomamos o estudo da tipagem pato estática na <>, que +iniciamos na <>. Vimos como +o decorador `@runtime_checkable` também aproveita o `+__subclasshook__+` para +suportar tipagem estrutural durante a execução—mesmo que o melhor uso dos +protocolos estáticos seja com checadores de tipos estáticos, que podem levar em +consideração as dicas de tipo, tornando a tipagem estrutural mais confiável. +Então falamos sobre o projeto e a codificação de um protocolo estático e como +estendê-lo. O capítulo terminou com a triste história do abandono da torre +numérica e das limitações da alternativa proposta: os protocolos numéricos +estáticos, tal como `SupportsFloat` e outros adicionados ao módulo `typing` no +Python 3.8. + +A mensagem principal deste capítulo é que temos quatro maneiras complementares +de programar com interfaces no Python moderno, cada uma com diferentes vantagens +e deficiências. Você encontrará casos de uso adequados para cada esquema de +tipagem em qualquer base de código moderna de tamanho significativo. Rejeitar +qualquer destas abordagens tornará seu trabalho como programador Python mais +difícil e limitado. + +Dito isso, Python ganhou sua enorme popularidade enquanto suportava apenas +tipagem pato. Outras linguagens populares que aproveitam o poder e a +simplicidade da tipagem pato são Javascript, PHP e Ruby, e outras, +menos populares mas muito influentes, como +Lisp, Smalltalk, Erlang, Elixir e Clojure. [[interfaces_further_reading]] === Para saber mais -Para((("interfaces", "further reading on")))((("protocols", "further reading on")))((("ABCs (abstract base classes)", "further reading on"))) uma rápida revisão do prós e contras da tipagem, bem como da importância de `typing.Protocol` -para a saúde de bases de código checadas estaticamente, eu recomendo fortemente o post de Glyph Lefkowitz -https://fpy.li/13-42["I Want A New Duck: `typing.Protocol` and the future of duck typing"] ("Quero Um Novo Pato: `typing.Protocol` e o futuro da tipagem pato"). +Para((("interfaces", "further reading on")))((("protocols", +"further reading on")))((("ABCs (abstract base classes)", "further reading on"))) +uma rápida revisão dos prós e contras da tipagem, bem como da importância de +`typing.Protocol` para a saúde de bases de código checadas estaticamente, +recomendo fortemente o post de Glyph Lefkowitz +https://fpy.li/13-42[_I Want A New Duck: `typing.Protocol` and the future of duck typing_] +(Quero um novo pato: `typing.Protocol` e o futuro da tipagem pato"). Também aprendi bastante em seu post -https://fpy.li/13-43["Interfaces and Protocols"] (EN) ("Interfaces e Protocolos"), -comparando `typing.Protocol` com `zope.interface` — um mecanismo mais antigo para definir interfaces em sistemas plug-in fracamente acoplados, usado no -https://fpy.li/13-44[Plone CMS], -na https://fpy.li/13-45[Pyramid web framework], e no framework de programação assíncrona +https://fpy.li/13-43[_Interfaces and Protocols_], +comparando `typing.Protocol` com `zope.interface`—um mecanismo mais antigo +para definir interfaces em sistemas plug-in fracamente acoplados, usado no +https://fpy.li/13-44[Plone CMS], no framework Web +na https://fpy.li/13-45[Pyramid], e no framework de programação assíncrona https://fpy.li/13-46[Twisted], -um projeto fundado por Glyph.footnote:[Agradeço ao revisor técnico Jürgen Gmach por ter recomentado o post "Interfaces and Protocols".] - -Bons livros sobre Python tem—quase que por definição—uma ótima cobertura de tipagem pato. -Dois de meus livros favoritos de Python tiveram atualizações lançadas após a primeira edição de _Python Fluente_: -_The Quick Python Book_, 3rd ed., (Manning), de Naomi Ceder; e -pass:[Python in a Nutshell, 3rd ed.,] de Alex Martelli, Anna Ravenscroft, e Steve Holden (O'Reilly). - -Para uma discussão sobre os prós e contras da tipagem dinâmica, veja a entrevista de Guido van Rossum com Bill Venners em -https://fpy.li/13-47["Contracts in Python: A Conversation with Guido van Rossum, Part IV"] (EN) ("Contratos em Python: Uma Conversa com Guido van Rossum, Parte IV"). -O post -https://fpy.li/13-48["Dynamic Typing"] (EN) ("Tipagem Dinâmica"), de Martin Fowler, traz uma avaliação perspicaz e equilibrada deste debate. +um projeto fundado por Glyph.footnote:[Agradeço ao revisor técnico Jürgen Gmach +por ter recomentado o post "Interfaces and Protocols".] + +Bons livros sobre Python têm—quase que por definição—uma ótima cobertura de +tipagem pato. Dois de meus livros favoritos de Python tiveram atualizações +lançadas após a primeira edição de _Python Fluente_: _The Quick Python Book_, +3rd ed., (Manning), de Naomi Ceder; e _Python in a Nutshell_, 3rd ed., de +Alex Martelli, Anna Ravenscroft, e Steve Holden (O'Reilly). + +Para uma discussão sobre os prós e contras da tipagem dinâmica, veja a +entrevista de Guido van Rossum com Bill Venners em +https://fpy.li/13-47[_Contracts in Python: A Conversation with Guido van Rossum, Part IV_] +(Contratos em Python: uma conversa com Guido van Rossum"). +O post https://fpy.li/13-48[_Dynamic Typing_], de Martin Fowler, +traz uma avaliação perspicaz e equilibrada deste debate. Ele também escreveu -https://fpy.li/13-33["Role Interface"] (EN) ("Interface Papel"), -que mencionei na <>. -Apesar de não ser sobre tipagem pato, -aquele post é altamente relevante para o projeto de protocolos em Python, -pois ele contrasta as interfaces papel estreitas com as interfaces públicas bem mais abrangentes de classes em geral. +https://fpy.li/13-33[_Role Interface_] (interface papel), que +mencionei na <>. Apesar de não ser sobre tipagem pato, +aquele post é altamente relevante para o projeto de protocolos em Python, pois +ele contrasta as interfaces papel estreitas com as interfaces públicas bem mais +abrangentes de classes em geral. A documentação do Mypy é, muitas vezes, a melhor fonte de informação sobre qualquer tema relacionado a tipagem estática em Python, -incluindo a tipagem pato estática, tratada no capítulo -https://fpy.li/13-50[_Protocols and structural subtyping_] (Protocolos e subtipagem estrutural). +incluindo à tipagem pato estática, tratada em +https://fpy.li/13-50[_Protocols and structural subtyping_]. As demais referências são sobre tipagem ganso. -O pass:[Python Cookbook], 3rd ed. (O'Reilly) -tem uma seção sobre como definir uma ABC (Recipe 8.12). -O livro foi escrito antes de Python 3.4, -então eles não usam a atual sintaxe preferida para declarar ABCs, criar uma subclasse de `abc.ABC` -(em vez disso, eles usam a palavra-chave `metaclass`, da qual só vamos precisar mesmo em<>). -Tirando esse pequeno detalhe, a receita cobre os principais recursos das ABCs muito bem. - -_The Python Standard Library by Example_ by Doug Hellmann (Addison-Wesley), -tem um capítulo sobre o módulo `abc`. -Ele também esta disponível na web, em -https://fpy.li/13-51[_PyMOTW—Python Module of the Week_]. -Hellmann usa a declaração de ABC no estilo -antigo:`PluginBase(metaclass=abc.ABCMeta)` -em vez de `PluginBase(abc.ABC)`, suportada desde o Python 3.4. - -Quando usamos ABCs, herança múltipla não é apenas comum mas praticamente inevitável, -porque cada uma das ABCs fundamentais de coleções (`Sequence`, `Mapping`, `Set`) -estendem `Collection`, que por sua vez estende múltiplas ABCs -(veja <>). Assim, o <> é um complemento importante ao presente capítulo. - -A https://fpy.li/13-52[PEP 3119--Introducing Abstract Base Classes] (EN) +O _Python Cookbook_, 3rd ed. de Beazley & Jones (O'Reilly) tem uma seção sobre +como definir uma ABC (Recipe 8.12). O livro foi escrito antes de Python 3.4, +então eles não usam a atual sintaxe preferida para declarar ABCs, criar uma +subclasse de `abc.ABC` (em vez disso, eles usam a palavra-chave `metaclass`, da +qual só vamos precisar mesmo no <>). Tirando esse pequeno +detalhe, a receita cobre os principais recursos das ABCs muito bem. + +_The Python Standard Library by Example_ de Doug Hellmann (Addison-Wesley), tem +um capítulo sobre o módulo `abc`. Ele também está disponível na web, em +https://fpy.li/13-51[_PyMOTW—Python Module of the Week_]. Hellmann usa a +declaração de ABC no estilo antigo: `++PluginBase(metaclass=abc.ABCMeta)++` em vez de +`PluginBase(abc.ABC)`, suportada desde o Python 3.4. + +Quando usamos ABCs, herança múltipla não é apenas comum, mas praticamente +inevitável, pois cada uma das ABCs fundamentais de coleções (`Sequence`, +`Mapping`, `Set`) estende `Collection`, que por sua vez estende múltiplas ABCs +(veja <>). Assim, o <> é um complemento +importante ao presente capítulo. + +A https://fpy.li/13-52[_PEP 3119–Introducing Abstract Base Classes_] apresenta a justificativa para as ABCs. -A https://fpy.li/13-53[PEP 3141--A Type Hierarchy for Numbers] (EN) +A https://fpy.li/13-53[_PEP 3141–A Type Hierarchy for Numbers_] apresenta as ABCs do https://fpy.li/13-54[módulo `numbers`], -mas a discussão no Mypy issue https://fpy.li/13-55[#3186 "int is not a Number?"] -inclui alguns argumentos sobre a razão da torre numérica ser inadequada para checagem estática de tipo. +mas a discussão no Mypy issue https://fpy.li/13-55[#3186] intitulado +"int is not a Number?" (int não é um número?) +inclui alguns argumentos sobre por que a torre numérica não serve +para checagem estática de tipo. Alex Waygood escreveu uma https://fpy.li/13-56[resposta abrangente no StackOverflow], discutindo formas de anotar tipos numéricos. Vou continuar monitorando o Mypy issue https://fpy.li/13-55[#3186] -para os próximos capítulos dessa saga, na esperança de um final feliz que torne a tipagem estática e a tipagem ganso compatíveis, como eles deveriam ser. +para os próximos capítulos dessa saga, +na esperança de um final feliz que torne a tipagem estática +e a tipagem ganso compatíveis, como deveriam ser. -//// -PROD: Please check for orphans within the Soapbox. -I could not use section titles, so I faked them with sentences in bold type, but sometimes a page break may appear between a "title" and the next paragraph. -//// - [role="pagebreak-before less_space"] [[interfaces_soapbox]] .Ponto de vista **** -[role="soapbox-title"] **A Jornada MVP da tipagem estática em Python** Trabalhei((("interfaces", "Soapbox discussion", id="Isoap13")))((("protocols", @@ -2287,48 +2603,48 @@ typing")))((("static protocols", "Soapbox discussion"))) na Thoughtworks, empresa pioneira em metodologias ágeis de engenharia de software. A Thoughtworks muitas vezes ajuda os clientes a criar e implantar um MVP: _Minimum Viable Product_ (Produto Mínimo Viável), -"uma versão simples de um produto, que é oferecida para -os usuários com o objetivo de validar hipóteses centrais do negócio," +"uma versão simples de um produto, oferecida para os usuários com o +objetivo de validar hipóteses centrais do negócio," conforme a definição de Paulo Caroli em -https://fpy.li/13-58[Lean Inception], +https://fpy.li/13-58[_Lean Inception_], um artigo no https://fpy.li/13-59[blog coletivo] editado por Martin Fowler. Guido van Rossum e os outros mantenedores que projetaram e implementaram a -tipagem estática tem seguido a estratégia do MVP desde 2006. Primeiro, a -https://fpy.li/pep3107[PEP 3107—Function Annotations] foi implementada no Python -3.0 com uma semântica bastante limitada: apenas sintaxe para anexar anotações a -parâmetros e retornos de funções, armazenadas no objeto função. +tipagem estática têm seguido a estratégia do MVP desde 2006. Primeiro, a +https://fpy.li/pep3107[_PEP 3107—Function Annotations_] foi implementada no Python +3.0 com uma semântica bastante limitada: apenas uma sintaxe para anexar +anotações a parâmetros e resultados de funções, armazenadas no objeto função. Isso foi feito para explicitamente permitir experimentação e receber feedback—os principais benefícios de um MVP. -Oito anos depois, a https://fpy.li/pep484[PEP 484—Type Hints] foi proposta e +Oito anos depois, a https://fpy.li/pep484[_PEP 484—Type Hints_] foi proposta e aprovada. Sua implementação, no Python 3.5, não exigiu mudanças na linguagem ou na biblioteca padrão—exceto a adição do módulo `typing`, do qual nenhuma outra parte da biblioteca padrão dependia. A PEP 484 suportava apenas tipos nominais com genéricos—similar ao Java—mas com -a checagem estática efetiva sendo executada por ferramentas externas. +a checagem estática sendo executada por ferramentas externas. Recursos importantes não existiam, como anotações de variáveis, tipos embutidos genéricos, e protocolos. -Apesar dessas limitações, esse MVP de tipagem foi bem sucedida o suficiente para +Apesar destas limitações, este MVP de tipagem foi bem sucedido o suficiente para atrair investimento e adoção por parte de empresas com enormes bases de código em Python, como a Dropbox, o Google e o Facebook, bem como apoio de IDEs profissionais como o https://fpy.li/13-60[PyCharm], o https://fpy.li/13-61[Wing], e o https://fpy.li/13-62[VS Code]. -A https://fpy.li/pep526[PEP 526—Syntax for Variable Annotations] foi o primeiro +A https://fpy.li/pep526[_PEP 526—Syntax for Variable Annotations_] foi o primeiro passo evolutivo que exigiu mudanças no interpretador, no Python 3.6. Mais -mudanças no interpretador de Python 3.7 foram feitas para suportar a -https://fpy.li/pep563[PEP 563—Postponed Evaluation of Annotations] e a -https://fpy.li/pep560[PEP 560—Core support for typing module and generic types], +mudanças no interpretador foram feitas na versão 3.7 para suportar a +https://fpy.li/pep563[_PEP 563—Postponed Evaluation of Annotations_] e a +https://fpy.li/pep560[_PEP 560—Core support for typing module and generic types_], que permitiram que coleções embutidas e da biblioteca padrão aceitem dicas de tipo genéricas "de fábrica" no Python 3.9, graças à -https://fpy.li/pep585[PEP 585—Type Hinting Generics In Standard Collections]. +https://fpy.li/pep585[_PEP 585—Type Hinting Generics In Standard Collections_]. Durante todos esses anos, alguns usuários de Python—incluindo eu—ficamos -desapontados com os tipos estáticos. Após que aprendi Go, a falta de +desapontados com os tipos estáticos. Após aprender Go, a falta de tipagem pato estática em Python era incompreensível, pois a tipagem pato sempre foi uma característica marcante desta linguagem. @@ -2338,30 +2654,28 @@ posterior com o feedback do uso em situações reais. Se há uma coisa que todos aprendemos com Python 3, é que progresso incremental é mais seguro que lançamentos estrondosos. Estou contente que não tivemos que -esperar pelo Python 4—se é que existirá—para tornar Python mais atrativo par +esperar pelo Python 4—se é que existirá—para tornar Python mais atrativo para grandes empresas, onde os benefícios da tipagem estática superam a complexidade adicional. -[role="soapbox-title"] **Abordagens à tipagem em linguagens populares** - A <> é((("Soapbox sidebars", "typing map")))((("typing -map"))) uma variação do Mapa de Tipagem(<>) com -algumas linguagem populares que suportam cada um dos modos de tipagem. +map"))) uma variação do Mapa de Tipagem (<>) com +algumas linguagens conhecidas que suportam cada um dos modos de tipagem. [[type_systems_languages]] .Quatro abordagens para checagem de tipos e algumas linguagens que as usam. image::../images/mapa-da-tipagem-linguagens.png[align="center",pdfwidth=12cm] -TypeScript e Python ≥ 3.8 são as únicas linguagem em minha pequena e arbitrária -amostra que suportam todas as quatro abordagens. +TypeScript e Python ≥ 3.8 são as únicas linguagens em minha pequena amostra que +suportam todas as quatro abordagens. -Go é claramente uma linguagem de tipo estáticos na tradição do Pascal, mas ela +Go é claramente uma linguagem de tipos estáticos na tradição do Pascal, mas ela foi a pioneira da tipagem pato estática—pelo menos entre as linguagens mais usadas hoje. Também coloquei Go no quadrante da tipagem ganso por causa de sua sintaxe especial para checagem de tipo (_type assertion_), -que permitem tratar tipos dinamicamente durante a execução. +que permite tratar tipos nominais dinamicamente durante a execução. No ano 2000, só existiam linguagens populares nos quadrantes diametralmente opostos da tipagem pato e da tipagem estática. @@ -2371,47 +2685,65 @@ O fato de cada um dos quatro quadrantes ter pelo menos três linguagens populares sugere que muita gente vê benefícios em cada uma das quatro abordagens à tipagem. - -[role="soapbox-title pagebreak-before less_space"] **Monkey patching** -Monkey patching((("Soapbox sidebars", "monkey-patching")))((("monkey-patching"))) tem uma reputação ruim. -Se usado com exagero, pode gerar sistemas difíceis de entender e manter. -A correção está normalmente intimamente ligada a seu alvo, tornando-se frágil. -Outro problema é que duas bibliotecas que aplicam correções deste tipo durante a execução podem pisar nos pés uma da outra, com a segunda biblioteca a rodar destruindo as correções da primeira. - -Mas o monkey patching pode também ser útil, por exemplo, para fazer uma classe implementar um protocolo durante a execução. -O design pattern Adaptador resolve o mesmo problema através da implementação de uma nova classe inteira. - -É fácil usar monkey patching em código Python, mas há limitações. -Ao contrário de Ruby e Javascript, Python não permite mudar o comportamento dos tipos embutidos durante a execução. -Na verdade, considero isto uma vantagem, -pois dá a certeza que um objeto `str` terá sempre os mesmos métodos. -Esta limitação reduz a chance de bibliotecas aplicarem -correções conflitantes quando importadas em seu projeto. - -[role="soapbox-title"] -Metáforas e idiomas em interfaces - -Uma((("Soapbox sidebars", "interfaces"))) metáfora promove o entendimento tornando restrições e acessos visíveis. -Esse é o valor das palavras "stack" (_pilha_) e "queue" (_fila_) para descrever estruturas de dados fundamentais: elas tornam claras aa operações permitidas, isto é, como os itens podem ser adicionados ou removidos. -Por outro lado, Alan Cooper et al. escrevem em _About Face, the Essentials of Interaction Design_, 4th ed. (Wiley): +Monkey patching((("Soapbox sidebars", +"monkey-patching")))((("monkey-patching"))) tem uma reputação ruim. Se usado com +exagero, pode gerar sistemas difíceis de entender e manter. O remendo (_patch_) +dinâmico está +normalmente fortemente acoplado ao seu alvo, tornando-se quebradiço quando o código +evolui. Outro problema é +que duas bibliotecas que aplicam remendos deste tipo durante a execução podem +pisar nos pés uma da outra, com a segunda biblioteca a rodar destruindo os +remendos da primeira. + +Mas o monkey patching pode também ser útil, por exemplo, para fazer uma classe +implementar um protocolo durante a execução. O design pattern _Adapter_ resolve +o mesmo problema de modo mais verboso implementando toda uma nova classe. + +É fácil usar monkey patching em código Python, mas há limitações. Ao contrário +de Ruby e Javascript, Python não permite mudar o comportamento dos tipos +embutidos durante a execução. Na verdade, considero isto uma vantagem, pois dá a +certeza de que um objeto `str` terá sempre os mesmos métodos. Esta limitação reduz +a chance de bibliotecas aplicarem correções conflitantes quando importadas em +seu projeto. + +**Metáforas e idiomas em interfaces** + +Uma((("Soapbox sidebars", "interfaces"))) metáfora promove o entendimento +tornando restrições e acessos visíveis. Esse é o valor das palavras _stack_ +(pilha) e _queue_ (fila) para descrever estruturas de dados fundamentais: +elas tornam claras as operações permitidas, isto é, como os itens podem ser +adicionados ou removidos. Por outro lado, Alan Cooper et al. escrevem em _About +Face, the Essentials of Interaction Design_, 4th ed. (Wiley): [quote] ____ -Fidelidade estrita a metáforas liga interfaces de forma desnecessariamente firme aos mecanismos do mundo físico. -____ -Eles está falando de interface de usuário, mas a advertência se aplica também a APIs. -Mas Cooper admite que quando uma metáfora "verdadeiramente apropriada" "cai no nosso colo," podemos usá-la (ele escreve "cai no nosso colo" porque é tão difícil encontrar metáforas adequadas que ninguém deveria perder tempo tentando encontrá-las ativamente). -Acredito que a imagem da máquina de bingo que usei nesse capítulo é apropriada e eu a defenderei. +Fidelidade estrita a metáforas liga interfaces de forma desnecessariamente firme +aos mecanismos do mundo físico. -_About Face_ é, de longe, o melhor livro sobre design de UI que já li—e eu li uns tantos. -Abandonar as metáforas como paradigmas de design, as substituindo por "interfaces idiomáticas", foi a lição mais valiosa que aprendi com o trabalho de Cooper. +____ -Em _About Face_, Cooper não lida com APIs, mas quanto mais penso em suas ideias, mais vejo como se aplicam ao Python. -Os protocolos fundamentais da linguagem são o que Cooper chama de "idiomas." -Uma vez que aprendemos o que é uma "sequência", podemos aplicar esse conhecimento em diferentes contextos. -Esse é o tema principal de _Python Fluente_: ressaltar os idiomas fundamentais da linguagem, para que o seu código seja conciso, efetivo e legível—para um Pythonista fluente.((("", startref="ABCsoap13")))((("", startref="Psoap13")))((("", startref="Isoap13"))) +Os autores estão falando de interface de usuário, mas a advertência se aplica também a +APIs. Eles admitem que quando "cai no nosso colo" uma metáfora "verdadeiramente +apropriada", podemos usá-la (escreveram "cai no nosso colo" porque é tão +difícil encontrar metáforas adequadas que ninguém deveria perder tempo tentando +encontrá-las). Acredito que a imagem da máquina de bingo que usei +nesse capítulo é apropriada. + +_About Face_ é, disparado, o melhor livro sobre design de UI que já li—e eu li +uns tantos. Abandonar as metáforas como paradigmas de design, adotando em seu +lugar "interfaces idiomáticas", foi a lição mais valiosa que aprendi com o +trabalho de Cooper. + +Em _About Face_, Cooper não lida com APIs, mas quanto mais penso em suas ideias, +mais vejo como se aplicam ao Python. Os protocolos fundamentais da linguagem são +o que Cooper chama de "idiomas." Uma vez que aprendemos o que é uma "sequência", +podemos aplicar esse conhecimento em diferentes contextos. Esse é o tema +principal de _Python Fluente_: ressaltar os idiomas fundamentais da linguagem, +para que o seu código seja conciso, efetivo e legível para um pythonista +fluente.((("", startref="ABCsoap13")))((("", startref="Psoap13")))((("", +startref="Isoap13"))) **** diff --git a/online/cap14.adoc b/online/cap14.adoc index 6cbc30c..cd9e7c5 100644 --- a/online/cap14.adoc +++ b/online/cap14.adoc @@ -5,53 +5,95 @@ [quote, Alan Kay, Os Primórdios de Smalltalk] ____ -[...] precisávamos de toda uma teoria melhor sobre herança (e ainda precisamos). Por exemplo, herança e instanciação (que é um tipo de herança) embaralham tanto a pragmática (tal como fatorar o código para economizar espaço) quanto a semântica (usada para um excesso de tarefas como: especialização, generalização, especiação, etc.).footnote:[Alan Kay, "The Early History of Smalltalk" (_Os Primórdios de Smalltalk_), na SIGPLAN Not. 28, 3 (março de 1993), 69–95. Também disponível https://fpy.li/14-1[online] (EN). -Agradeço a meu amigo Christiano Anderson, por compartilhar essa referência quando eu estava escrevendo este capítulo.] + +[...] precisávamos de toda uma teoria melhor sobre herança (e ainda precisamos). +Por exemplo, herança e instanciação (que é um tipo de herança) confundem +a pragmática (fatorar o código para economizar espaço) quanto a +semântica (usada para tarefas demais, como: especialização, generalização, +especiação, etc.).footnote:[Alan Kay, "The Early History of Smalltalk" (_Os +Primórdios de Smalltalk_), na SIGPLAN Not. 28, 3 (março de 1993), 69–95. +Também disponível https://fpy.li/14-1[online]. Agradeço a meu amigo +Christiano Anderson, por compartilhar essa referência quando eu estava +escrevendo este capítulo.] + ____ -Esse((("inheritance and subclassing", "topics covered"))) capítulo é sobre herança e criação de subclasses. -Vou presumir um entendimento básico desses conceitos, que você pode ter aprendido lendo https://docs.python.org/pt-br/3/tutorial/classes.html[_O Tutorial de Python_], ou por experiências com outra linguagem orientada a objetos popular, tal como Java, C# ou {cpp}. -Aqui vamos nos concentrar em quatro características de Python: +Este((("inheritance and subclassing", "topics covered"))) capítulo é sobre +herança e criação de subclasses. Vou presumir um entendimento básico destes +conceitos, que você pode ter aprendido lendo +https://fpy.li/6w[O Tutorial de Python], +ou trabalhando com outra linguagem orientada a objetos, tal como +Java, C# ou {cpp}. Vamos nos concentrar em quatro características de +Python: * A função `super()` -* As armadilhas na criação de subclasses de tipos embutidos +* Armadilhas na criação de subclasses de tipos embutidos * Herança múltipla e a ordem de resolução de métodos * Classes mixin -Herança múltipla acontece quando uma classe tem mais de uma classe base. O {cpp} a suporta; Java e C# não. -Muitos consideram que a herança múltipla não vale a quantidade de problemas que causa. -Ela foi deliberadamente deixada de fora de Java, após seu aparente abuso nas primeiras bases de código {cpp}. +Herança múltipla acontece quando uma classe tem mais de uma classe base. +Ela existe em {cpp}, mas não em Java e C#. +Muitos consideram que a herança múltipla não vale +os problemas que causa. Ela foi deliberadamente deixada de fora de +Java, após supostamente ser usada em excesso nos primeiras bases de código em {cpp}. -Esse capítulo introduz a herança múltipla para aqueles que nunca a usaram, -e oferece orientações sobre como lidar com herança simples ou múltipla, se você precisar usá-la. +Este capítulo apresenta a herança múltipla para aqueles que nunca a usaram, +e oferece orientações sobre como lidar com herança simples ou múltipla, +quando necessário. -Em 2021, quando escrevo essas linhas, há uma forte reação contra o uso excessivo de herança em geral—não apenas herança múltipla—porque superclasses e subclasses são fortemente acopladas, ou seja, interdependentes. -Esse acoplamento forte significa que modificações em uma classe pode ter efeitos inesperados -e de longo alcance em suas subclasses, tornando os sistemas frágeis e difíceis de entender. +Em 2021, quando escrevo essas linhas, há uma forte reação contra o uso excessivo +de herança em geral—não apenas herança múltipla—porque superclasses e subclasses +são fortemente acopladas, ou seja, interdependentes. Esse acoplamento forte +significa que modificações em uma classe podem ter efeitos inesperados e de longo +alcance em suas subclasses, tornando os sistemas frágeis e difíceis de entender. -Entretanto, ainda temos que manter os sistemas existentes, que podem ter complexas hierarquias de classe, ou trabalhar com frameworks que nos obrigam a usar herança—algumas vezes até herança múltipla. +Entretanto, ainda temos que dar manutenção a sistemas existentes, que podem ter +hierarquias de classe complexas, ou trabalhar com frameworks que nos obrigam a +usar herança—algumas vezes até herança múltipla. -Vou ilustrar as aplicações práticas da herança múltipla com a biblioteca padrão, o framework para programação web Django e o toolkit para programação de interface gráfica Tkinter. +Vou ilustrar as aplicações práticas da herança múltipla com a biblioteca padrão, +o framework Django e o toolkit para programação de +interface gráfica Tkinter. === Novidades neste capítulo -Não((("inheritance and subclassing", "significant changes to"))) há nenhum recurso novo no Python no que diz respeito ao assunto desse capítulo, mas fiz inúmeras modificações baseadas nos comentários dos revisores técnicos da segunda edição, especialmente Leonardo Rochael e Caleb Hattingh. +Não((("inheritance and subclassing", "significant changes to"))) há nenhum +recurso novo no Python relacionado ao tema deste capítulo, mas fiz +inúmeras modificações baseadas nos comentários dos revisores técnicos da segunda +edição, especialmente Leonardo Rochael e Caleb Hattingh. -Escrevi uma nova seção de abertura, tratando especificamente da função embutida `super()`, e mudei os exemplos na <>, para explorar mais profundamente a forma como `super()` suporta a herança múltipla _cooperativa_. +Escrevi uma nova seção de abertura, tratando especificamente da função embutida +`super()`, e mudei os exemplos na <>, para explorar mais +profundamente a forma como `super()` suporta a herança múltipla cooperativa. -A <> também é nova. A <> foi reorganizada, e apresenta exemplos mais simples de _mixin_ vindos da bilbioteca padrão, antes de apresentar o exemplos com o framework Django e as complicadas hierarquias do Tkinter. +A <> também é nova. Reorganizei a <>, +apresentando exemplos mais simples de _mixin_ na bilbioteca +padrão, antes de apresentar o exemplos com o Django e a +hierarquia complicada do Tkinter. -Como o próprio título sugere, as ressalvas à herança sempre foram um dos temas principais desse capítulo. Mas como cada vez mais desenvolvedores consideram essa técnica problemática, acrescentei alguns parágrafos sobre como evitar a herança no final da <> e da <>. +Como o próprio título sugere, as ressalvas à herança sempre foram um dos temas +principais desse capítulo. Mas como cada vez mais desenvolvedores consideram +essa técnica problemática, acrescentei alguns parágrafos sobre como evitar a +herança no final da <> e da +<>. -Vamos começar com uma revisão da enigmática função `super()`. +Vamos começar com uma revisão da mal compreendida função `super()`. === A função super() -O((("inheritance and subclassing", "super() function", id="IACsuper14")))((("super() function", id="super14")))((("functions", "super() function"))) uso consistente da função embutida `super()` é essencial na criação de programas Python orientados a objetos fáceis de manter. +O((("inheritance and subclassing", "super() function", +id="IACsuper14")))((("super() function", id="super14")))((("functions", "super() function"))) +uso consistente da função embutida `super()` é essencial na criação +de programas orientados a objetos fáceis de manter em Python. -Quando uma subclasse sobrescreve um método de uma superclasse, esse novo método normalmente precisa invocar o método correspondente na superclasse. -Aqui está o modo recomendado de fazer isso, tirado de um exemplo da documentação do módulo _collections_, na seção https://docs.python.org/pt-br/3/library/collections.html#ordereddict-examples-and-recipes["OrderedDict Examples and Recipes" (_OrderedDict: Exemplos e Receitas_)] (EN).:footnote:[Modifiquei apenas a docstring do exemplo, porque a original está errada. Ela diz: "Armazena itens na ordem das chaves adicionadas por último" (_"Store items in the order the keys were last added"_), mas não é isso o que faz a classe claramente batizada `LastUpdatedOrderedDict`.] +Quando uma subclasse sobrescreve um método de uma superclasse, o novo método +normalmente precisa invocar o método correspondente na superclasse. Aqui está o +modo recomendado de fazer isso, tirado de um exemplo da documentação do módulo +_collections_, na seção +https://fpy.li/6x[OrderedDict: Exemplos e Receitas].:footnote:[A docstring +original estava errada, reportei no https://fpy.li/7e[_issue #141721_], +enviei PR, e traduzi aqui.] [source, python] ---- @@ -65,14 +107,22 @@ class LastUpdatedOrderedDict(OrderedDict): Para executar sua tarefa, `LastUpdatedOrderedDict` sobrescreve `+__setitem__+` para: -. Usar `+super().__setitem__+`, invocando aquele método na superclasse e permitindo que ele insira ou atualize o par chave/valor. -. Invocar `self.move_to_end`, para garantir que a `key` atualizada esteja na última posição. +. Usar `+super().__setitem__+`, invocando aquele método na superclasse e +permitindo que ele insira ou atualize o par chave/valor. -Invocar um `+__init__+` sobreposto é particulamente importante, para permitir que a superclasse execute sua parte na inicialização da instância. +. Invocar `self.move_to_end`, para garantir que a `key` atualizada esteja na +última posição. + +Invocar um `+__init__+` herdado é particulamente importante, para permitir que a +superclasse execute sua parte na inicialização da instância. [TIP] ==== -Se você aprendeu programação orientada a objetos com Java, com certeza se lembra que, naquela linguagem, um método construtor invoca automaticamente o construtor sem argumentos da superclasse. Python não faz isso. Se acostume a escrever o seguinte código padrão: + +Se você aprendeu programação orientada a objetos com Java, deve se lembrar de +que, naquela linguagem, um método construtor invoca automaticamente o construtor +sem argumentos da superclasse. Python não faz isso. Acostume-se a escrever o +seguinte código padrão: [source, python] ---- @@ -82,7 +132,8 @@ Se você aprendeu programação orientada a objetos com Java, com certeza se lem ---- ==== -Você pode já ter visto código que não usa `super()`, e em vez disso chama o método na superclasse diretamente, assim: +Você pode já ter visto código que não usa `super()`, e em vez disso invoca o +método na superclasse diretamente, assim: [source, python] ---- @@ -94,14 +145,19 @@ class NotRecommended(OrderedDict): self.move_to_end(key) ---- -Essa alternativa até funciona nesse caso em particular, mas não é recomendado por duas razões. +Esta alternativa até funciona nesse caso em particular, mas não é recomendada por duas razões. Primeiro, codifica a superclasse explicitamente. -O nome `OrderedDict` aparece na declaração `class` e também dentro de `+__setitem__+`. -Se, no futuro, alguém modificar a declaração `class` para mudar a classe base ou adicionar outra, pode se esquecer de atualizar o corpo de `+__setitem__+`, introduzindo um bug. - -A segunda razão é que `super` implementa lógica para tratar hierarquias de classe com herança múltipla. -Voltaremos a isso na <>. -Para concluir essa recapitulação de `super`, é útil rever como essa função era invocada no Python 2, porque a assinatura antiga, com dois argumentos, é reveladora: +O nome `OrderedDict` aparece na declaração `class` e também dentro de +`+__setitem__+`. Se, no futuro, alguém modificar a declaração `class` para mudar +a classe base ou adicionar outra, pode se esquecer de atualizar o corpo de +`+__setitem__+`, introduzindo um bug. + +A segunda razão é que `super` implementa lógica para tratar hierarquias de +classe com herança múltipla. +Voltaremos a isso na <>. +Para concluir essa recapitulação de `super`, é bom rever como essa função era +invocada no Python 2. Sem os parâmetros default, a assinatura de `super` é mais +reveladora: [source, python] ---- @@ -113,45 +169,72 @@ class LastUpdatedOrderedDict(OrderedDict): self.move_to_end(key) ---- -Os dois argumento de `super` são agora opcionais. -O compilador de bytecode de Python 3 obtém e fornece ambos examinando o contexto circundante, quando `super()` é invocado dentro de um método. -Os argumentos são: +Os dois parâmetros de `super` agora são opcionais. +O compilador de bytecode de Python 3 fornece os argumentos examinando o contexto +quando `super()` é invocado dentro de um método. +Os parâmetros são: `type`:: O início do caminho para a superclasse que implementa o método desejado. - Por default, é a classe que possui o método onde a chamada a `super()` aparece. + Por default, é a classe onde está o método que invoca `super()`. `object_or_type`:: - O objeto (para chamadas a métodos de instância) ou classe (para chamadas a métodos de classe) que será o receptor da chamada ao método.footnote:[Adotamos o termo "receptor" como tradução para _receiver_, que é o objeto `o` vinculado um método `m` no momento da chamada `o.m()`.] - Por default, é `self` se a chamada `super()` acontece no corpo de um método de instância. - -Independente desses argumentos serem fornecidos por você ou pelo compilador, a chamada a `super()` devolve um objeto proxy dinâmico que encontra um método (tal como `+__setitem__+` no exemplo) -em uma superclasse do parâmetro `type` e a vincula ao `object_or_type`, -de modo que não precisamos passar explicitamente o receptor (`self`) quando invocamos o método. - -No Python 3, ainda é permitido passar explicitamente o primeiro e o segundo argumentos a `super()`.footnote:[Também é possível passar apenas o primeiro argumento, mas isso não é útil e pode logo ser descontinuado, com as bênçãos de Guido van Rossum, o próprio criador de `super()`. Veja a discussão em https://fpy.li/14-4["Is it time to deprecate unbound super methods?" (_É hora de descontinuar métodos "super" não vinculados?_)].] Mas eles são necessários apenas em casos especiais, tal como pular parte do MRO (sigla de Method Resolution Order—_Ordem de Resolução de Métodos_), para testes ou depuração, ou para contornar algum comportamento indesejado em uma superclasse. - -Vamos agora discutir as ressalvas à criação de subclasses de tipos embutidos.((("", startref="super14")))((("", startref="IACsuper14"))) + O objeto (ao invocar métodos de instância) ou classe (ao invocar + métodos de classe) que será o receptor da chamada ao + método.footnote:[Adotamos o termo "receptor" como tradução para _receiver_, + que é o objeto `x` vinculado um método `m` no momento da chamada `x.m()`.] + Por default, é `self` se a chamada `super()` acontece no corpo de um método + de instância. + +A chamada a `super()` devolve um objeto proxy dinâmico que encontra um método (tal +como `+__setitem__+` no exemplo) em uma superclasse do argumento `type` e o +vincula a `object_or_type`, de modo que não precisamos passar explicitamente o +receptor (`self`) quando invocamos o método. + +No Python 3, ainda é permitido passar explicitamente o primeiro e o segundo +argumentos a `super()`.footnote:[Também é possível passar apenas o primeiro +argumento, mas isso não é útil e pode logo ser descontinuado, com as bênçãos de +Guido van Rossum, o próprio criador de `super()`. Veja a discussão em +https://fpy.li/14-4[_Is it time to deprecate unbound super methods?_] +(Está na hora de descontinuar métodos "super" não vinculados?).] +Mas eles são necessários apenas em casos especiais, para testes, ou depuração, +ou para contornar algum comportamento indesejado em uma superclasse. + +Vamos agora discutir as ressalvas à criação de subclasses de tipos +embutidos.((("", startref="super14")))((("", startref="IACsuper14"))) [[subclass_builtin_woes_sec]] === Problemas com subclasses de tipos embutidos -Nas((("inheritance and subclassing", "subclassing built-in types", id="IASsubbuilt14"))) primeiras versões de Python não era possível criar subclasses de tipos embutidos como `list` ou `dict`. -Desde Python 2.2 isso é possível, mas há restrição importante: -o código (escrito em C) dos tipos embutidos normalmente não chama os métodos sobrepostos por classes definidas pelo usuário. -Há uma boa descrição curta do problema na documentação do PyPy, na seção "Differences between PyPy and CPython" (_"Diferenças entre o PyPy e o CPython"_), -https://fpy.li/pypydif["Subclasses of built-in types" (_Subclasses de tipos embutidos_)]: +Nas((("inheritance and subclassing", "subclassing built-in types", +id="IASsubbuilt14"))) primeiras versões do Python não era possível criar +subclasses de tipos embutidos como `list` ou `dict`. Desde o Python 2.2 isso é +possível, mas há uma limitação importante: o código em C dos tipos +embutidos normalmente não invoca os métodos sobrescritos por classes definidas +pelo usuário. Há uma boa descrição curta do problema na documentação do PyPy, na +seção _Differences between PyPy and CPython_ (Diferenças entre o PyPy e o +CPython), em https://fpy.li/pypydif[_Subclasses of built-in types_] +(Subclasses de tipos embutidos)]: [quote] ____ -Oficialmente, o CPython não tem qualquer regra sobre exatamente quando um método sobreposto de subclasses de tipos embutidos é ou não invocado implicitamente. Como uma aproximação, esses métodos nunca são chamados por outros métodos embutidos do mesmo objeto. Por exemplo, um `+__getitem__+` sobreposto em uma subclasse de `dict` nunca será invocado pelo método `get()` do tipo embutido. +Oficialmente, o CPython não tem nenhuma regra sobre exatamente quando um método +sobrescrito de subclasses de tipos embutidos é ou não invocado implicitamente. +Como uma aproximação, esses métodos nunca são chamados por outros métodos +embutidos do mesmo objeto. Por exemplo, um `+__getitem__+` sobrescrito em uma +subclasse de `dict` nunca será invocado pelo método `get()` do tipo embutido. ____ +Concretamente, isto significa que `meu_dict['x']` e `meu_dict.get('x')` +podem produzir resultados diferentes, mesmo no caso mais simples quando +a chave `'x'` existe, supondo que `meu_dict` é uma instância de uma subclasse +de `dict` criada por você. + O <> ilustra o problema. [[ex_doppeldict]] -.Nossa sobreposição de `+__setitem__+` é ignorado pelos métodos `+__init__+` e `+__update__+` to tipo embutido `dict` +.Nosso `+__setitem__+` sobrescrito é ignorado pelos métodos `+__init__+` e `+__update__+` to tipo embutido `dict` ==== [source, python] ---- @@ -170,21 +253,41 @@ O <> ilustra o problema. {'three': 3, 'one': 1, 'two': [2, 2]} ---- ==== -<1> `+DoppelDict.__setitem__+` duplica os valores ao armazená-los (por nenhuma razão em especial, apenas para termos um efeito visível). Ele funciona delegando para a superclasse. -<2> O método `+__init__+`, herdado de `dict`, claramente ignora que `+__setitem__+` foi sobreposto: o valor de `'one'` não foi duplicado. -<3> O operador `[]` chama nosso `+__setitem__+` e funciona como esperado: `'two'` está mapeado para o valor duplicado `[2, 2]`. -<4> O método `update` de `dict` também não usa nossa versão de `+__setitem__+`: o valor de `'three'` não foi duplicado. - -Esse comportamento dos tipos embutidos é uma violação de uma regra básica da programação orientada a objetos: -a busca por métodos deveria sempre começar pela classe do receptor (`self`), mesmo quando a chamada ocorre dentro de um método implementado na superclasse. -Isso é o que se chama "vinculação tardia" ("_late binding_"), que Alan Kay—um dos criadores de Smalltalk—considera ser uma característica básica da programação orientada a objetos: -em qualquer chamada na forma `x.method()`, o método exato a ser chamado deve ser determinado durante a execução, baseado na classe do receptor `x`.footnote:[É interessante observar que o -{cpp} diferencia métodos virtuais e não-virtuais. Métodos virtuais têm vinculação tardia, enquanto os métodos não-virtuais são vinculados na compilação. Apesar de todos os métodos que podemos escrever em Python serem de vinculação tardia, como um método virtual, objetos embutidos escritos em C parecem ter métodos não-virtuais por default, pelo menos no CPython.] -Este triste estado de coisas contribui para os problemas que vimos na <>. -O problema não está limitado a chamadas dentro de uma instância—saber se `self.get()` chama -`self.__getitem__()`—mas também acontece com métodos sobrepostos de outras classes que deveriam ser chamados por métodos embutidos. -O <> foi adaptado da https://fpy.li/14-5[documentação do PyPy] (EN). +<1> `+DoppelDict.__setitem__+` duplica os valores ao armazená-los (por nenhuma +razão, apenas para termos um efeito visível). Ele funciona delegando +para a superclasse. + +<2> O método `+__init__+`, herdado de `dict`, claramente ignora que +`+__setitem__+` foi sobrescrito: o valor de `'one'` não foi duplicado. + +<3> O operador `[]` invoca nosso `+__setitem__+` e funciona como esperado: +`'two'` está mapeado para o valor duplicado `[2, 2]`. + +<4> O método `update` de `dict` também não usa nossa versão de `+__setitem__+`: +o valor de `'three'` não foi duplicado. + +Este comportamento dos tipos embutidos viola uma regra básica da +programação orientada a objetos: a busca por métodos deveria sempre começar pela +classe do receptor (`self`), mesmo quando a invocação ocorre dentro de um método +implementado na superclasse. Isso é o que se chama _late binding_ (vinculação tardia), +que Alan Kay—um dos criadores de Smalltalk—considera ser uma +característica essencial da programação orientada a objetos: em qualquer chamada na +forma `x.method()`, o método exato a ser chamado deve ser determinado durante a +execução, baseado na classe do receptor `x`.footnote:[É interessante observar +que o {cpp} diferencia métodos virtuais e não-virtuais. Métodos virtuais têm +vinculação tardia, enquanto os métodos não-virtuais são vinculados na +compilação. Apesar de todos os métodos que podemos escrever em Python serem de +vinculação tardia, como um método virtual, objetos embutidos escritos em C +parecem ter métodos não-virtuais por default, pelo menos no CPython.] Este +triste estado de coisas contribui para os problemas que vimos na +<>. + +O problema não está limitado a chamadas dentro de uma instância—saber se +`self.get()` invoca `+self.__getitem__()+`. Também acontece com métodos +sobrescritos de outras classes que deveriam ser chamados por métodos embutidos. +O <> foi adaptado da https://fpy.li/14-5[documentação do +PyPy]. [[ex_other_subclass]] .O `+__getitem__+` de `AnswerDict` é ignorado por `dict.update` @@ -214,10 +317,20 @@ O <> foi adaptado da https://fpy.li/14-5[documentação do Py [WARNING] ==== -Criar subclasses diretamente de tipos embutidos como `dict` ou `list` ou `str` é um processo propenso ao erro, pois os métodos embutidos quase sempre ignoram as sobreposições definidas pelo usuário. Em vez de criar subclasses de tipos embutidos, derive suas classes do módulo https://docs.python.org/pt-br/3/library/collections.html[`collections`], usando as classes `UserDict`, `UserList`, e `UserString`, que foram projetadas para serem fáceis de estender. + +Criar subclasses diretamente de tipos embutidos, como `dict`, `list` ou `str`, é +um processo propenso ao erro, pois os métodos embutidos quase sempre ignoram +métodos sobrescritos pelo usuário. Em vez de criar subclasses de tipos +embutidos, derive suas classes do módulo +https://fpy.li/2w[`collections`], usando +as classes `UserDict`, `UserList`, e `UserString`, que foram projetadas para +serem fáceis de estender. + ==== -Se você criar uma subclasse de `collections.UserDict` em vez de `dict`, os problemas expostos no <> e no <> desaparecem. Veja o <>. +Herdando de `collections.UserDict` em vez de `dict`, os problemas expostos no +<> e no <> desaparecem. Veja o +<>. [[ex_userdict_ok]] .`DoppelDict2` and `AnswerDict2` funcionam como esperado, porque estendem `UserDict` e não `dict` @@ -256,27 +369,51 @@ Se você criar uma subclasse de `collections.UserDict` em vez de `dict`, os prob ---- ==== -Como um experimento, para medir o trabalho extra necessário para criar uma subclasse de um tipo embutido, reescrevi a classe `StrKeyDict` do <>, para torná-la uma subclasse de `dict` em vez de `UserDict`. Para fazê-la passar pelo mesmo banco de testes, tive que implementar -`+__init__+`, `get`, e `update`, pois as versões herdadas de `dict` se recusaram a cooperar com os métodos sobrepostos `+__missing__+`, `+__contains__+` e `+__setitem__+`. A subclasse de `UserDict` no <> tem 16 linhas, enquanto a subclasse experimental de `dict` acabou com 33 linhas.footnote:[Se você tiver curiosidade, o experimento está no arquivo +Como um experimento, para medir o trabalho extra necessário para criar uma +subclasse de um tipo embutido, reescrevi a classe `StrKeyDict` +(<> do <>), +para torná-la uma subclasse de `dict` em vez de `UserDict`. +Para fazê-la passar pelo mesmo banco de testes, tive que implementar +`+__init__+`, `get`, e `update`, pois as versões herdadas de `dict` se recusaram +a cooperar com os métodos sobrescritos `+__missing__+`, `+__contains__+` e +`+__setitem__+`. A subclasse de `UserDict` no <> do <> +tem 16 linhas, enquanto a subclasse experimental de `dict` acabou com 33 linhas.footnote:[Se +você tiver curiosidade, o experimento está no arquivo https://fpy.li/14-7[_14-inheritance/strkeydict_dictsub.py_] do repositório https://fpy.li/code[_fluentpython/example-code-2e_].] -Para deixar claro: essa seção tratou de um problema que se aplica apenas à delegação a métodos dentro do código em C dos tipos embutidos, e afeta apenas classes derivadas diretamente daqueles tipos. -Se você criar uma subclasse de uma classe escrita em Python, tal como `UserDict` ou `MutableMapping`, não vai encontrar esse problema.footnote:[Aliás, nesse mesmo tópico, o PyPy se comporta de forma mais "correta" que o CPython, às custas de introduzir uma pequena incompatibilidade. Veja os detalhes em https://fpy.li/14-5["Differences between PyPy and CPython" (_Diferenças entre o PyPy e o CPython_)] (EN).] +Para deixar claro: esta seção tratou de um problema que se aplica apenas à +delegação a métodos dentro do código em C dos tipos embutidos, e afeta apenas +classes derivadas diretamente daqueles tipos. Se você criar uma subclasse de uma +classe escrita em Python, tal como `UserDict` ou `MutableMapping`, não vai +encontrar este problema.footnote:[Aliás, nesse mesmo tópico, o PyPy se comporta +mais "corretamente" que o CPython, às custas de introduzir uma pequena +incompatibilidade. Veja os detalhes em https://fpy.li/14-5["Differences between +PyPy and CPython" (_Diferenças entre o PyPy e o CPython_)].] -Vamos agora examinar uma questão que aparece na herança múltipla: -se uma classe tem duas superclasses, como Python decide qual atributo usar quando invocamos `super().attr`, mas ambas as superclasses têm um atributo com esse nome?((("", startref="IASsubbuilt14"))) +Vamos agora examinar uma questão que aparece na herança múltipla: se uma classe +tem duas superclasses, como Python decide qual atributo usar quando invocamos +`super().attr`, mas ambas as superclasses têm um atributo com este +nome?((("",startref="IASsubbuilt14"))) -[[mro_section]] +[[mult_inherit_mro_sec]] === Herança múltipla e a Ordem de Resolução de Métodos -Qualquer((("inheritance and subclassing", "multiple inheritance and method resolution order", id="IASmultiple14")))((("multiple inheritance", "method resolution order and", id="mulinh14")))((("method resolution order (MRO)", id="methres14"))) linguagem que implemente herança múltipla precisa lidar com o potencial conflito de nomes, quando superclasses contêm métodos com nomes iguais. -Isso é chamado "o problema do diamante", ilustrado na <> e no <>. +Qualquer((("inheritance and subclassing", "multiple inheritance and method resolution order", +id="IASmultiple14")))((("multiple inheritance", "method resolution order and", +id="mulinh14")))((("method resolution order (MRO)", id="methres14"))) +linguagem que implemente herança múltipla precisa lidar com o +potencial conflito de nomes, quando superclasses contêm métodos com nomes +iguais. Este é o chamado "problema do losango" (_diamond problem_), +ilustrado na <> e no <>, onde da hiearquia +começa na classe base `Root` (raiz) e termina na classe `Leaf` +(folha).footnote:[Adotamos a convenção dos computólogos +e desenhamos árvores de cabeça para baixo: a raiz no topo, as folhas na base.] [[diamond_uml]] .Esquerda: Sequência de ativação para a chamada `leaf1.ping()`. Direita: Sequência de ativação para a chamada `leaf1.pong()`. -image::../images/flpy_1401.png[UML do problema do diamante] +image::../images/flpy_1401.png[align="center",pdfwidth=11cm] [[ex_diamond]] .diamond.py: classes `Leaf`, `A`, `B`, `Root` formam o grafo na <> @@ -288,10 +425,11 @@ include::../code/14-inheritance/diamond.py[tags=DIAMOND_CLASSES] ==== <1> `Root` fornece `ping`, `pong`, e `+__repr__+` (para facilitar a leitura da saída). <2> Os métodos `ping` e `pong` na classe `A` chamam `super()`. -<3> Apenas o método `ping` na classe `B` chama `super()`. -<4> A classe `Leaf` implementa apenas `ping`, e chama `super()`. +<3> Apenas o método `ping` na classe `B` invoca `super()`. +<4> A classe `Leaf` implementa apenas `ping`, e invoca `super()`. -Vejamos agora o efeito da invocação dos métodos `ping` e `pong` em uma instância de `Leaf` (<>). +Vejamos agora o efeito da invocação dos métodos `ping` e `pong` em uma instância +de `Leaf` (<>). [[ex_diamond_demo]] .Doctests para chamadas a `ping` e `pong` em um objeto `Leaf` @@ -302,17 +440,27 @@ include::../code/14-inheritance/diamond.py[tags=DIAMOND_CALLS] ---- ==== <1> `leaf1` é uma instância de `Leaf`. -<2> Chamar `leaf1.ping()` ativa os métodos `ping` em `Leaf`, `A`, `B`, e `Root`, porque os métodos `ping` nas três primeiras classes chamam `super().ping()`. -<3> Chamar `leaf1.pong()` ativa `pong` em `A` através da herança, que por sua vez chama -`super.pong()`, ativando `B.pong`. -As sequências de ativação que aparecem no <> e na <> são determinadas por dois fatores: +<2> Chamar `leaf1.ping()` ativa os métodos `ping` em `Leaf`, `A`, `B`, e `Root`, +porque os métodos `ping` nas três primeiras classes chamam `super().ping()`. + +<3> Chamar `leaf1.pong()` ativa `pong` em `A` através da herança, que por sua +vez invoca `super.pong()`, ativando `B.pong`. + +As sequências de ativação que aparecem no <> e na +<> são determinadas por dois fatores: * A ordem de resolução de métodos da classe `Leaf`. * O uso de `super()` em cada método. -Todas as classes possuem um atributo chamado `+__mro__+`, que mantém uma tupla de referências a superclasses, na ordem de resolução dos métodos, indo desde a classe corrente até a classe `object`.footnote:[Classes também têm um método `.mro()`, mas este é um recurso avançado de programaçõa de metaclasses, mencionado no <>. Durante o uso normal de uma classe, apenas o conteúdo do atributo `+__mro__+` importa.] -Para a classe `Leaf` class, o `+__mro__+` é o seguinte: +A ordem de resolução de métodos é conhecida pela sigla MRO (_Method Resolution +Order_). Em Python, todas as classes têm um atributo chamado `+__mro__+`, que +armazena uma tupla de referências a superclasses, na ordem de resolução dos +métodos, indo desde a classe corrente até a classe `object`.footnote:[Classes +também têm um método `.mro()`, mas este é um recurso avançado de programaçõa de +metaclasses, mencionado na <>. Durante o uso normal de uma +classe, apenas o conteúdo do atributo `+__mro__+` importa.] Para a classe `Leaf`, +o `+__mro__+` é o seguinte: [source, python] ---- @@ -321,46 +469,65 @@ include::../code/14-inheritance/diamond.py[tags=LEAF_MRO] [NOTE] ==== -Olhando para a <>, pode parecer que a MRO descreve uma -https://pt.wikipedia.org/wiki/Busca_em_largura[busca em largura (ou amplitude)], -mas isso é apenas uma coincidência para essa hierarquia de classes em particular. -A MRO é computada por um algoritmo conhecido, chamado C3. -Seu uso no Python está detalhado no artigo -https://fpy.li/14-10["The Python 2.3 Method Resolution Order" (_A Ordem de Resolução de Métodos no Python 2.3_)], de Michele Simionato. -É um texto difícil, mas Simionato escreve: -"...a menos que você faça amplo uso de herança múltipla e mantenha hierarquias não-triviais, não é necessário entender o algoritmo C3, e você pode facilmente ignorar este artigo." -==== - -A MRO determina apenas a ordem de ativação, mas se um método específico será ou não ativado em cada uma das classes vai depender de cada implementação chamar ou não `super()`. -Considere o experimento com o método `pong`. -A classe `Leaf` não sobrescreve aquele método, então a chamada `leaf1.pong()` ativa a implementação na próxima classe listada em `+Leaf.__mro__+`: a classe `A`. -O método `A.pong` chama `super().pong()`. -A classe `B` class é e próxima na MRO, portanto `B.pong` é ativado. -Mas aquele método não chama `super().pong()`, então a sequência de ativação termina ali. +Na <>, pode parecer que a MRO descreve uma +https://fpy.li/6y[busca em largura], mas isso é apenas uma +coincidência para esta hierarquia de classes simples. A MRO é computada +por um algoritmo da literatura de computação, chamado C3. +Seu uso no Python está detalhado no artigo +https://fpy.li/14-10[_The Python 2.3 Method Resolution Order_] (A Ordem +de Resolução de Métodos no Python 2.3), de Michele Simionato. É um texto +difícil, mas Simionato escreve: "...a menos que você use herança +múltipla intensivamente, e mantenha hierarquias não-triviais, +não é necessário entender o algoritmo C3, +e você pode facilmente ignorar este artigo." -Além do grafo de herança, a MRO também leva em consideração a ordem na qual as superclasses aparecem na declaração da uma subclasse. -Em outras palavras, se em _diamond.py_ (no <>) a classe `Leaf` fosse declarada como -`Leaf(B, A)`, daí a classe `B` apareceria antes de `A` em `+Leaf.__mro__+`. -Isso afetaria a ordem de ativação dos métodos `ping`, e também faria `leaf1.pong()` ativar `B.pong` através da herança, mas `A.pong` e `Root.pong` nunca seriam executados, porque `B.pong` não chama `super()`. +==== -Quando um método invoca `super()`, ele é um _método cooperativo_. -Métodos cooperativos permitem((("cooperative multiple inheritance"))) a _herança múltipla cooperativa_. -Esses termos são intencionais: para funcionar, a herança múltipla no Python exige a cooperação ativa dos métodos envolvidos. Na classe `B`, `ping` coopera, mas `pong` não. +A MRO determina apenas a ordem de ativação, mas se um método específico será ou +não ativado em cada uma das classes vai depender de cada implementação chamar ou +não `super()`. + +Considere o experimento com o método `pong`. A classe `Leaf` não sobrescreve +aquele método, então a chamada `leaf1.pong()` ativa a implementação na próxima +classe listada em `+Leaf.__mro__+`: a classe `A`. O método `A.pong` invoca +`super().pong()`. A classe `B` class é e próxima na MRO, portanto `B.pong` é +ativado. Mas aquele método não invoca `super().pong()`, então a sequência de +ativação termina ali. + +Além do grafo de herança, a MRO também considera a ordem na qual as +superclasses aparecem na declaração da uma subclasse. Considerando o programa +_diamond.py_ (no <>), se a classe `Leaf` fosse declarada como `Leaf(B, +A)`, daí a classe `B` apareceria antes de `A` em `+Leaf.__mro__+`. Isso afetaria +a ordem de ativação dos métodos `ping`, e também faria `leaf1.pong()` ativar +`B.pong` através da herança, mas `A.pong` e `Root.pong` não seriam invocados, +porque `B.pong` não invoca `super()`. + +Quando um método invoca `super()`, ele é um método cooperativo. Métodos +cooperativos permitem((("cooperative multiple inheritance"))) a herança +múltipla cooperativa. Esses termos são intencionais: para funcionar, a herança +múltipla no Python exige a cooperação ativa dos métodos envolvidos invocando +`super()`. Na classe `B`, `ping` coopera, mas `pong` não. [WARNING] ==== -Um método não-cooperativo pode ser a causa de bugs sutis. -Muitos programadores, lendo o <>, poderiam esperar que, quando o método `A.pong` invoca `super.pong()`, isso acabaria por ativar `Root.pong`. -Mas se `B.pong` for ativado antes, ele deixa a bola cair. -Por isso, é recomendado que todo método `m` de uma classe não-base chame `super().m()`. + +Um método não-cooperativo pode ser a causa de bugs sutis. Muitos programadores, +lendo o <>, poderiam esperar que, quando o método `A.pong` invoca +`super.pong()`, isso acabaria por ativar `Root.pong`. Mas se `B.pong` for +ativado antes, ele deixa a bola cair. Por isso, recomenda-se que um método +subrescrito `m` de uma classe não-base invoque `super().m()`. + ==== -Métodos cooperativos devem ter assinaturas compatíveis, porque nunca se sabe se `A.ping` será chamado antes ou depois de `B.ping`. -A sequência de ativação depende da ordem de `A` e `B` na declaração de cada subclasse que herda de ambos. +Métodos cooperativos devem ter assinaturas compatíveis, porque nunca se sabe se +`A.ping` será chamado antes ou depois de `B.ping`. A sequência de ativação +depende da ordem de `A` e `B` na declaração de cada subclasse que herda de +ambos. -Python é uma linguagem dinâmica, então a interação de `super()` com a MRO também é dinâmica. -O <> mostra um resultado surpreendente desse comportamento dinâmico. +Python é uma linguagem dinâmica, então a interação de `super()` com a MRO também +é dinâmica. O <> mostra um resultado surpreendente desse +comportamento dinâmico. [[ex_diamond2]] .diamond2.py: classes para demonstrar a natureza dinâmica de `super()` @@ -371,7 +538,7 @@ include::../code/14-inheritance/diamond2.py[tags=DIAMOND_CLASSES] ---- ==== <1> A classe `A` vem de _diamond.py_ (no <>). -<2> A classe `U` não tem relação com `A` ou`Root` do módulo `diamond`. +<2> A classe `U` não tem relação com `A` ou `Root` do módulo `diamond`. <3> O que `super().ping()` faz? Resposta: depende. Continue lendo. <4> `LeafUA` é subclasse de `U` e `A`, nessa ordem. @@ -382,15 +549,11 @@ Se você criar uma instância de `U` e tentar chamar `ping`, ocorre um erro: include::../code/14-inheritance/diamond2.py[tags=UNRELATED_DEMO_1] ---- -//// -PROD: I don't understand why the snippet above and the next snippet -below are not colorized in the PDF output. -//// - -O objeto `'super'` devolvido por `super()` não tem um atributo `'ping'`, porque o MRO de `U` tem duas classes: -`U` e `object`, e este último não tem um atributo chamado `'ping'`. +O objeto `'super'` devolvido por `super()` não tem um atributo `'ping'`, porque +a MRO de `U` tem duas classes: `U` e `object`, e esta última não tem um atributo +chamado `'ping'`. -Entretanto, o método `U.ping` não é inteiramente sem solução. Veja isso: +Entretanto, o método `U.ping` não é completamente inútil. Veja isso: [source, python] ---- @@ -401,25 +564,32 @@ A chamada `super().ping()` em `LeafUA` ativa `U.ping`, que também coopera chamando `super().ping()`, ativando `A.ping` e, por fim, `Root.ping`. -Observe que as clsses base de `LeafUA` são `(U, A)`, nessa ordem. -Se em vez disso as bases fossem `(A, U)`, daí `leaf2.ping()` nunca chegaria a `U.ping`, -porque o `super().ping()` em `A.ping` ativaria `Root.ping`, e esse último não chama `super()`. +Observe que as clsses base de `LeafUA` são `(U, A)`, nesta ordem. Se em vez +disso as bases fossem `(A, U)`, daí `leaf2.ping()` nunca chegaria a `U.ping`, +porque o `super().ping()` em `A.ping` ativaria `Root.ping`, e esse último não +invoca `super()`. -Em um programa real, uma classe como `U` poderia ser uma _classe mixin_: -uma classe projetada para ser usada junto com outras classes em herança múltipla, fornecendo funcionalidade adicional. -Vamos estudar isso em breve, na <>. +Em um programa real, uma classe como `U` poderia ser uma classe _mixin_: uma +classe projetada para ser usada ao lado outras classes em herança múltipla, +fornecendo funcionalidade adicional. Vamos estudar isso em breve, na +<>. -Para((("UML class diagrams", "Tkinter Text widget class and superclasses"))) concluir essa discussão sobre a MRO, a <> ilustra parte do complexo grafo de herança múltipla do toolkit de interface gráfica Tkinter, da biblioteca padrão de Python. +Para((("UML class diagrams", "Tkinter Text widget class and superclasses"))) +concluir essa discussão sobre a MRO, a <> ilustra parte do +complexo grafo de herança múltipla do toolkit de interface gráfica Tkinter, da +biblioteca padrão de Python. [[tkwidgets_mro_uml]] .Esquerda: diagrama UML da classe e das superclasses do componente `Text` do Tkinter. Direita: O longo e sinuoso caminho de `+Text.__mro__+`, desenhado com as setas pontilhadas. image::../images/flpy_1402.png[UML do componente Text do Tkinter] -Para estudar a figura, comece pela classe `Text`, na parte inferior. -A classe `Text` implementa um componente de texto completo, editável e com múltiplas linhas. -Ele sozinho fornece muita funcionalidade, mas também herda muitos métodos de outras classes. -A imagem à esquerda mostra um diagrama de classe UML simples. -À direita, a mesma imagem é decorada com setas mostrando a MRO, como listada no <> com a ajuda de uma função de conveniência `print_mro`. +Para estudar a figura, comece pela classe `Text`, na parte inferior. A classe +`Text` implementa um componente de texto completo, editável e com múltiplas +linhas. Ele sozinho fornece muita funcionalidade, mas também herda muitos +métodos de outras classes. A imagem à esquerda mostra um diagrama de classe UML +simples. À direita, a mesma imagem é decorada com setas mostrando a MRO, como +listada no <> com a ajuda de uma função de conveniência +`print_mro`. [[ex_tkinter_text_mro]] .MRO de `tkinter.Text` @@ -441,22 +611,33 @@ Vamos agora falar sobre mixins.((("", startref="methres14")))((("", startref="mu [[mixin_classes_sec]] === Classes mixin -Uma((("inheritance and subclassing", "mixin classes", id="IASmixin14")))((("mixin classes", id="mixin14"))) classe mixin é projetada para ser herdada em conjunto com pelo menos uma outra classe, em um arranjo de herança múltipla. -Uma mixin não é feita para ser a única classe base de uma classe concreta, pois não fornece toda a funcionalidade para um objeto concreto, apenas adicionando ou customizando o comportamento de classes filhas ou irmãs. +Uma((("inheritance and subclassing", "mixin classes", +id="IASmixin14")))((("mixin classes", id="mixin14"))) classe mixin é feita +para ser herdada com pelo menos uma outra classe, em um arranjo de +herança múltipla. Uma mixin não é feita para ser a única classe base de uma +classe concreta, pois não fornece toda a funcionalidade para um objeto concreto, +apenas adicionando ou customizando o comportamento de classes filhas ou irmãs. [NOTE] ==== -Classes mixin são uma convenção sem qualquer suporte explícito no Python e no {cpp}. -Ruby permite a definição explícita e o uso de módulos que funcionam como mixins—coleções de métodos que podem ser incluídas para adicionar funcionalidade a uma classe. -C#, PHP, e Rust implementam traits (_características_ ou _traços_ ou _aspectos_), que são também uma forma explícita de mixin. + +Classes mixin são uma convenção sem qualquer suporte explícito no Python e no +{cpp}. Ruby permite a definição explícita e o uso de módulos que funcionam como +mixins—coleções de métodos que podem ser incluídas para adicionar funcionalidade +a uma classe. C#, PHP, e Rust implementam traits (_traços_ +ou _aspectos_), que são também uma forma explícita de mixin. + ==== -Vamos ver um exemplo simples mas conveniente de uma classe mixin. +Vamos ver um exemplo simples e útil de uma classe mixin. ==== Mapeamentos maiúsculos -O <> mostra a `UpperCaseMixin`, -uma((("mappings", "case-insensitive", id="Mcase14"))) classe criada para fornecer acesso indiferente a maiúsculas/minúsculas para mapeamentos com chaves do tipo string, convertendo todas as chaves para maiúsculas quando elas são adicionadas ou consultadas. +O <> mostra a `UpperCaseMixin`, uma((("mappings", +"case-insensitive", id="Mcase14"))) classe criada para fornecer acesso +indiferente a maiúsculas/minúsculas para mapeamentos com chaves do tipo string, +convertendo todas as chaves para maiúsculas quando elas são adicionadas ou +consultadas. [[ex_uppermixin]] .uppermixin.py: `UpperCaseMixin` suporta mapeamentos indiferentes a maiúsculas/minúsculas @@ -466,14 +647,18 @@ uma((("mappings", "case-insensitive", id="Mcase14"))) classe criada para fornece include::../code/14-inheritance/uppermixin.py[tags=UPPERCASE_MIXIN] ---- ==== -[role="pagebreak-before less_space"] -<1> Essa função auxiliar recebe uma `key` de qualquer tipo e tenta devolver `key.upper()`; se isso falha, devolve a `key` inalterada. -<2> A mixin implementa quatro métodos essenciais de mapeamentos, sempre chamando `super()`com a chave em maiúsculas, se possível. -Como todos os métodos de `UpperCaseMixin` chamam `super()`, -esta mixin depende de uma classe irmã que implemente ou herde métodos com a mesma assinatura. -Para dar sua contribuição, uma mixin normalmente precisa aparecer antes de outras classes na MRO de uma subclasse que a use. -Na prática, isso significa que mixins devem aparecer primeiro na tupla de classes base em uma declaração de classe. +<1> Esta função auxiliar recebe uma `key` de qualquer tipo e tenta devolver +`key.upper()`; se isto falha, devolve a `key` inalterada. + +<2> A mixin implementa quatro métodos essenciais de mapeamentos, sempre chamando +`super()` após tentar converter a chave em maiúsculas. + +Como todos os métodos de `UpperCaseMixin` chamam `super()`, esta mixin depende +de uma classe irmã que implemente ou herde métodos com a mesma assinatura. Para +dar sua contribuição, uma mixin normalmente precisa aparecer antes de outras +classes na MRO de uma subclasse. Na prática, isto significa que mixins +devem aparecer primeiro na tupla de classes base em uma declaração de classe. O <> apresenta dois exemplos. [[ex_upperdict]] @@ -484,17 +669,19 @@ O <> apresenta dois exemplos. include::../code/14-inheritance/uppermixin.py[tags=UPPERDICT] ---- ==== -<1> `UpperDict` não precisa de qualquer implementação própria, mas `UpperCaseMixin` deve ser a primeira classe base, caso contrário os métodos chamados seriam os de `UserDict`. + +<1> `UpperDict` não precisa implementar nenhum método, mas +`UpperCaseMixin` tem ser a primeira classe base, +caso contrário os métodos chamados seriam os de `UserDict`. + <2> `UpperCaseMixin` também funciona com `Counter`. -<3> Em vez de `pass`, é melhor fornecer uma docstring para satisfazer a necessidade sintática de um corpo não-vazio na declaração `class`. -//// -PROD: I don't understand why the next two snippets -below are not colorized in the PDF output. -//// +<3> Em vez de `pass`, é melhor fornecer uma docstring para satisfazer +a sintaxe da instrução `class`, que precisa ter um corpo. + -Aqui estão alguns doctests de -https://fpy.li/14-11[_uppermixin.py_], para `UpperDict`: +Aqui estão alguns doctests de `UpperDict`, do módulo +https://fpy.li/14-11[_uppermixin.py_]: [source, python] ---- @@ -508,46 +695,77 @@ E uma rápida demonstração de `UpperCounter`: include::../code/14-inheritance/uppermixin.py[tags=UPPERCOUNTER_DEMO] ---- -`UpperDict` e `UpperCounter` parecem quase mágica, mas tive que estudar cuidadosamente o código de `UserDict` e `Counter` para fazer `UpperCaseMixin` trabalhar com eles. - +`UpperDict` e `UpperCounter` parecem quase mágicas, mas tive que estudar +cuidadosamente o código de `UserDict` e `Counter` para fazer `UpperCaseMixin` +trabalhar com eles. Por exemplo, minha primeira versão de `UpperCaseMixin` não incluía o método `get`. Aquela versão funcionava com `UserDict`, mas não com `Counter`. -A classe `UserDict` herda `get` de `collections.abc.Mapping`, e aquele `get` chama `+__getitem__+`, que implementei. -Mas as chaves não eram transformadas em maiúsculas quando uma `UpperCounter` era carregada no -`+__init__+`. -Isso acontecia porque `+Counter.__init__+` usa `Counter.update`, que por sua vez recorre ao método `get` herdado de `dict`. Entretanto, o método `get` na classe `dict` não chama `+__getitem__+`. -Esse é o núcleo do problema discutido na <>. -É também uma dura advertência sobre a natureza frágil e intrincada de programas que se apoiam na herança, mesmo nessa pequena escala. - -A próxima seção apresenta vários exemplos de herança múltipla, muitas vezes usando classes mixin.((("", startref="Mcase14")))((("", startref="mixin14")))((("", startref="IASmixin14"))) +A classe `UserDict` herda `get` de `collections.abc.Mapping`, e aquele `get` +invoca `+__getitem__+`, que implementei. +Mas as chaves não eram transformadas em maiúsculas quando uma `UpperCounter` era +carregada no `+__init__+`. +Isso acontecia porque `+Counter.__init__+` usa `Counter.update`, que por sua vez +recorre ao método `get` herdado de `dict`. Entretanto, o método `get` na classe +`dict` não invoca `+__getitem__+`. + +Esta é a essência do problema discutido na <>. É também +uma clara demonstração da natureza frágil e quebradiça de programas que se +apoiam no acoplamento forte da herança, mesmo nessa pequena escala. + +A próxima seção apresenta vários exemplos de herança múltipla, muitas vezes +usando classes mixin.((("", startref="Mcase14")))((("", +startref="mixin14")))((("", startref="IASmixin14"))) [[multi_real_world_sec]] === Herança múltipla no mundo real -No((("inheritance and subclassing", "real-world examples of", id="IASreal14")))((("multiple inheritance", "real-world examples of", id="MIreal14"))) livro _Design Patterns_ ("Padrões de Projetos"),footnote:[Erich Gamma, Richard Helm, Ralph Johnson, e John Vlissides, _Padrões de Projetos: Soluções Reutilizáveis de Software Orientados a Objetos_ (Bookman).] quase todo o código está em {cpp}, mas o único exemplo de herança múltipla é o padrão _Adapter_ ("Adaptador"). -Em Python a herança múltipla também não é regra, mas há exemplos importantes, que comentarei nessa seção. +No((("inheritance and subclassing", "real-world examples of", +id="IASreal14")))((("multiple inheritance", "real-world examples of", +id="MIreal14"))) livro _Design Patterns_ (Padrões de Projetos),footnote:[Erich +Gamma, Richard Helm, Ralph Johnson, e John Vlissides, _Padrões de Projetos: +Soluções Reutilizáveis de Software Orientados a Objetos_ (Bookman).] quase todo +o código está em {cpp}. O único exemplo de herança múltipla é o padrão +_Adapter_ (Adaptador). Em Python a herança múltipla também não é regra, mas +há exemplos importantes, que comentarei nessa seção. ==== ABCs também são mixins -Na((("collections.abc module", "multiple inheritance in")))((("mixin methods"))) biblioteca padrão de Python, o uso mais visível de herança múltipla é o pacote `collections.abc`. -Nenhuma controvérsia aqui: afinal, até o Java suporta herança múltipla de interfaces, e ABCs são declarações de interface que podem, opcionalmente, fornecer implementações concretas de métodos.footnote:[Como já mencionado, o Java 8 permite que interfaces também forneçam implementações de métodos. Esse novo recurso é chamado https://fpy.li/14-12["Default Methods" (_Métodos Default_)] (EN) no Tutorial oficial de Java.] - -A documentação oficial de Python para -https://docs.python.org/pt-br/3/library/collections.abc.html[`collections.abc`] (EN) -usa o termo _mixin method_ ("método mixin") para os métodos concretos implementados em muitas das coleções nas ABCs. -As ABCs que oferecem métodos mixin cumprem dois papéis: elas são definições de interfaces e também classes mixin. -Por exemplo, a https://fpy.li/14-14[implementação de `collections.UserDict`] (EN) -recorre a vários dos métodos mixim fornecidos por `collections.abc.MutableMapping`. +Na((("collections.abc module", "multiple inheritance in")))((("mixin methods"))) +biblioteca padrão de Python, o uso mais visível de herança múltipla é o pacote +`collections.abc`. Nenhuma controvérsia aqui: afinal, até o Java suporta +herança múltipla de interfaces, e ABCs são declarações de interface que podem, +opcionalmente, fornecer implementações concretas de métodos.footnote:[Como já +mencionado, o Java 8 permite que interfaces também forneçam implementações de +métodos. Esse novo recurso é chamado https://fpy.li/14-12[_Default Methods_] +(Métodos Default) no Tutorial oficial de Java.] + +A documentação oficial do pacote +https://fpy.li/6z[`collections.abc`] +chama de _mixin methods_ (métodos mixin) os métodos concretos +implementados nas ABCs de coleções. As ABCs que oferecem métodos +mixin cumprem dois papéis: elas são definições de interfaces e também classes +mixin. Por exemplo, a +https://fpy.li/14-14[«implementação»] de `collections.UserDict` aproveita +vários dos métodos mixin fornecidos por `collections.abc.MutableMapping`. ==== ThreadingMixIn e ForkingMixIn -O pacote https://docs.python.org/pt-br/3/library/http.server.html[_http.server_] inclui((("ThreadingMixIn class")))((("ForkingMixIn")))((("HTTPServer class")))((("ThreadingHTTPServer class")))((("servers", "ThreadingHTTPServer class")))((("servers", "HTTPServer class"))) as classes `HTTPServer` e `ThreadingHTTPServer`. Essa última foi adicionada ao Python 3.7. Sua documentação diz: +O pacote https://fpy.li/72[_http.server_] +inclui((("ThreadingMixIn class")))((("ForkingMixIn")))((("HTTPServer class")))((("ThreadingHTTPServer class")))((("servers", +"ThreadingHTTPServer class")))((("servers", "HTTPServer class"))) +as classes `HTTPServer` e `ThreadingHTTPServer`. +Esta última foi adicionada ao Python 3.7. +A documentação de `ThreadingHTTPServer` diz (nossa tradução): -classe `http.server.ThreadingHTTPServer`(server_address, RequestHandlerClass):: -Essa classe é idêntica a `HTTPServer`, mas trata requisições com threads, usando a `ThreadingMixIn`. Isso é útil para lidar com navegadores web que abrem sockets prematuramente, situação na qual o `HTTPServer` esperaria indefinidamente. +____ +Esta classe é idêntica a `HTTPServer`, mas trata requisições com threads, +usando a `ThreadingMixIn`. Isso é útil para lidar com navegadores web que abrem +sockets prematuramente, situação na qual o `HTTPServer` esperaria indefinidamente. +____ -Este é o -https://fpy.li/14-16[código-fonte completo] da classe `ThreadingHTTPServer` no Python 3.10: +As duas linhas abaixo são o +https://fpy.li/14-16[código-fonte completo] +da classe `ThreadingHTTPServer` no Python 3.10: [source, python] ---- @@ -555,7 +773,8 @@ class ThreadingHTTPServer(socketserver.ThreadingMixIn, HTTPServer): daemon_threads = True ---- -O https://fpy.li/14-17[código-fonte] de `socketserver.ThreadingMixIn` tem 38 linhas, incluindo os comentários e as docstrings. +O https://fpy.li/14-17[código-fonte] de `socketserver.ThreadingMixIn` tem 38 +linhas, incluindo os comentários e as docstrings. O <> apresenta um resumo de sua implementação. [[ex_threadmixin]] @@ -566,26 +785,40 @@ O <> apresenta um resumo de sua implementação. class ThreadingMixIn: """Mixin class to handle each request in a new thread.""" - # 8 lines omitted in book listing + # 8 linhas omitidas aqui def process_request_thread(self, request, client_address): # <1> - ... # 6 lines omitted in book listing + ... # 6 linhas omitidas aqui def process_request(self, request, client_address): # <2> - ... # 8 lines omitted in book listing + ... # 8 linhas omitidas aqui def server_close(self): # <3> super().server_close() self._threads.join() ---- ==== -<1> `process_request_thread` não chama `super()` porque é um método novo, não uma sobreposição. Sua implementação chama três métodos de instância que `HTTPServer` oferece ou herda. -<2> Isso sobrescreve o método `process_request`, que `HTTPServer` herda de `socketserver.BaseServer`, iniciando uma thread e delegando o trabalho efetivo para a `process_request_thread` que roda naquela thread. O método não chama `super()`. -<3> `server_close` chama `super().server_close()` para parar de receber requisições, e então espera que as threads iniciadas por `process_request` terminem sua execução. -A `ThreadingMixIn` aparece junto com `ForkingMixIn` na documentação do módulo https://docs.python.org/pt-br/3/library/socketserver.html#socketserver.ForkingMixIn[`socketserver`]. -Essa última classe foi projetada para suportar servidores concorrentes baseados na -https://docs.python.org/pt-br/3/library/os.html#os.fork[`os.fork()`], uma API para iniciar processos filhos, disponível em sistemas Unix (ou similares) compatíveis com a https://pt.wikipedia.org/wiki/POSIX[POSIX]. +<1> `process_request_thread` não invoca `super()` porque é um método novo, +não sobrescreve um método herdado. Sua implementação invoca três métodos de +instância que `HTTPServer` implementa ou herda. + +<2> Isto sobrescreve o método `process_request`, que `HTTPServer` herda de +`socketserver.BaseServer`, iniciando uma thread e delegando o trabalho real +para a `process_request_thread` que roda naquela thread. O método não invoca +`super()`. + +<3> `server_close` invoca `super().server_close()` para parar de receber +requisições, e então espera que as threads iniciadas por `process_request` +terminem sua execução. + +A documentação do módulo https://fpy.li/73[`socketserver`] +apresenta a `ThreadingMixIn` e a `ForkingMixIn`. +Esta última classe foi projetada para +suportar servidores concorrentes baseados em +https://fpy.li/74[`os.fork()`], +uma API para iniciar processos filhos, disponível em sistemas derivados do +Unix, compatíveis com a norma https://fpy.li/7c[POSIX]. @@ -594,76 +827,169 @@ https://docs.python.org/pt-br/3/library/os.html#os.fork[`os.fork()`], uma API pa [NOTE] ==== -Não é necessário entender de Django para acompanhar essa seção. -Uso uma pequena parte do framework como um exemplo prático de herança múltipla, e tentarei fornecer todo o pano de fundo necessário (supondo que você tenha alguma experiência com desenvolvimento web no lado servidor, com qualquer linguagem ou framework). -==== -No((("Django generic views mixins", id="Django14"))) Django, uma view é um objeto invocável que recebe um argumento `request`—um objeto representando uma requisição HTTP—e devolve um objeto representando uma resposta HTTP. -Nosso interesse aqui são as diferentes respostas. -Elas podem ser tão simples quanto um redirecionamento, sem nenhum conteúdo em seu corpo, ou tão complexas quando uma página de catálogo de uma loja online, renderizada a partir de uma template HTML e listando múltiplas mercadorias, com botões de compra e links para páginas com detalhes. +Não é necessário conhecer Django para acompanhar essa seção. Uso uma pequena +parte do framework como um exemplo prático de herança múltipla, e tentarei +fornecer todo o pano de fundo necessário (supondo que você tenha alguma +experiência com desenvolvimento Web no lado servidor, com qualquer linguagem ou +framework). -Originalmente, o Django oferecia uma série de funções, chamadas views genéricas, que implementavam alguns casos de uso comuns. Por exemplo, muitos sites precisam exibir resultados de busca que incluem dados de inúmeros itens, com listagens ocupando múltiplas páginas, cada resultado contendo também um link para uma página de informações detalhadas sobre aquele item. No Django, uma view de lista e uma view de detalhes são feitas para funcionarem juntas, resolvendo esse problema: uma view de lista renderiza resultados de busca , e uma view de detalhes produz uma página para cada item individual. - -Entretanto, as views genéricas originais eram funções, então não eram extensíveis. Se quiséssemos algo algo similar mas não exatamente igual a uma view de lista genérica, era preciso começar do zero. +==== -O conceito de views baseadas em classes foi introduzido no Django 1.3, juntamente com um conjunto de classes de views genéricas divididas em classes base, mixins e classes concretas prontas para o uso. No Django 3.2, as classes base e as mixins estão no módulo `base` do pacote `django.views.generic`, ilustrado((("UML class diagrams", "django.views.generic.base module"))) na <>. No topo do diagrama vemos duas classes que se encarregam de responsabilidades muito diferentes: `View` e [.keep-together]#`TemplateResponseMixin`.# +No((("Django generic views mixins", id="Django14"))) Django, uma view é um +objeto invocável que recebe um argumento `request`—um objeto representando uma +requisição HTTP—e devolve um objeto representando uma resposta HTTP. Nosso +interesse aqui são as diferentes respostas. Elas podem ser tão simples quanto +um redirecionamento, sem nenhum conteúdo em seu corpo, ou tão complexas quanto +uma página de catálogo de uma loja online, renderizada a partir de um template +HTML que exibe múltiplas mercadorias, com botões de compra e links para páginas +com detalhes. + +Originalmente, o Django oferecia uma série de funções, chamadas _generic views_ (views genéricas), +que implementavam alguns casos de uso comuns. Por exemplo, muitos sites precisam +exibir resultados de busca que incluem dados de vários itens, com listagens +ocupando múltiplas páginas, cada resultado contendo também um link para uma +página de informações detalhadas sobre aquele item. No Django, uma view de lista +e uma view de detalhes são feitas para funcionarem juntas, resolvendo esse +problema: uma view de lista renderiza resultados de busca, e uma view de +detalhes produz uma página para cada item individual. + +Entretanto, as views genéricas originais eram funções, então não eram +extensíveis. Se quiséssemos algo similar mas não exatamente igual a uma +view de lista genérica, era preciso começar do zero. + +O conceito de views baseadas em classes foi introduzido no Django 1.3, +juntamente com um conjunto de classes de views genéricas divididas em classes +base, mixins e classes concretas prontas para o uso. No Django 3.2, as classes +base e as mixins estão no módulo `base` do pacote `django.views.generic`, +ilustrado((("UML class diagrams", "django.views.generic.base module"))) na +<>. No topo do diagrama vemos duas classes que se +encarregam de responsabilidades muito diferentes: `View` e +`TemplateResponseMixin`. [role="width-80"] [[django_view_base_uml]] .Diagrama de classes UML do módulo `django.views.generic.base`. -image::../images/flpy_1403.png[Diagrama de classes UML do módulo `django.views.generic.base`.] +image::../images/flpy_1403.png[align="center",pdfwidth=8cm] [TIP] ==== -Um recurso fantástico para estudar essas classes é o site https://fpy.li/14-21[_Classy Class-Based Views_] (EN), onde se pode navegar por elas facilmente, ver todos os métodos em cada classe (métodos herdados, sobrepostos e adicionados), os diagramas de classes, consultar sua documentação e estudar seu https://fpy.li/14-22[código-fonte no GitHub]. -==== - -`View` é a classe base de todas as views (ela poderia ser uma ABC), e oferece funcionalidade essencial como o método `dispatch`, que delega para métodos de "tratamento" como `get`, `head`, `post`, etc., implementados por subclasses concretas para tratar os diversos verbos HTTP.footnote:[Os programadores Django sabem que o método de classe `as_view` é a parte mais visível da interface `View`, mas isso não é relevante para nós aqui.] A classe `RedirectView` herda apenas de `View`, e podemos ver que ela implementa `get`, `head`, `post`, etc. - -Se é esperado que as subclasses concretas de `View` implementem os métodos de tratamento, por que aqueles métodos não são parte da interface de `View`? A razão: subclasses são livres para implementar apenas os métodos de tratamento que querem suportar. Uma `TemplateView` é usada apenas para exibir conteúdo, então ela implementa apenas `get`. Se uma requisição HTTP `POST` é enviada para uma `TemplateView`, o método herdado `View.dispatch` verifica que não há um método de tratamento para `post`, e produz uma resposta HTTP `405 Method Not Allowed`.footnote:[Se você gosta de padrões de projetos, note que o mecanismo de despacho do Django é uma variação dinâmica do padrão -https://pt.wikipedia.org/wiki/Template_Method[Template Method (_Método Template_]. Ele é dinâmico porque a classe `View` não obriga subclasses a implementarem todos os métodos de tratamento, mas `dispatch` verifica, durante a execução, se um método de tratamento concreto está disponível para cada requisição específica.] - -A `TemplateResponseMixin` fornece funcionalidade que interessa apenas a views que precisam usar uma template. Uma `RedirectView`, por exemplo, não tem qualquer conteúdo em seu corpo, então não precisa de uma template e não herda dessa mixin. `TemplateResponseMixin` fornece comportamentos para `TemplateView` e outras views que renderizam templates, tal como `ListView`, `DetailView`, etc., definidas nos sub-pacotes de `django.views.generic`. A <> mostra o módulo `django.views.generic.list`((("UML class diagrams", "django.views.generic.list module"))) e parte do módulo `base`. -Para usuários do Django, a classe mais importante na <> é `ListView`, uma classe agregada sem qualquer código (seu corpo é apenas uma docstring). Quando instanciada, uma `ListView` tem um atributo de instância `object_list`, através do qual a template pode interagir para mostrar o conteúdo da página, normalmente o resultado de uma consulta a um banco de dados, composto de múltiplos objetos. Toda a funcionalidade relacionada com a geração desse iterável de objetos vem da `MultipleObjectMixin`. Essa mixin também oferece uma lógica complexa de paginação—para exibir parte dos resultados em uma página e links para mais páginas. +Um ótimo recurso para estudar essas classes é o site +https://fpy.li/14-21[_Classy Class-Based Views_], onde você pode navegar +facilmente pelo diagrama das classes, ver todos os métodos em cada classe +(métodos herdados, sobrescritos e adicionados), consultar sua documentação e +estudar seu +https://fpy.li/14-22[código-fonte no GitHub]. -Suponha que você queira criar uma view que não irá renderizar uma template, mas sim produzir uma lista de objetos em formato JSON. Para isso existe `BaseListView`. Ela oferece um ponto inicial de extensão fácil de usar, unindo a funcionalidade de `View` e de `MultipleObjectMixin`, mas sem a sobrecarga do mecanismo de templates. +==== -A API de views baseadas em classes do Django é um exemplo melhor de herança múltipla que o Tkinter. Em especial, é fácil entender suas classes mixin: cada uma tem um propósito bem definido, e todos os seus nomes contêm o sufixo `…Mixin`. +`View` é a classe base de todas as views (ela poderia ser uma ABC), e oferece +funcionalidade essencial como o método `dispatch`, que delega para métodos de +tratamento de requisições (_request handling_) como `get`, `head`, `post`, etc., +implementados por subclasses concretas para tratar os diversos verbos +HTTP.footnote:[Os programadores Django sabem que o método de classe `as_view` é +a parte mais visível da interface `View`, mas isso não é relevante para nós +aqui.] A classe `RedirectView` herda apenas de `View`. Note que ela +implementa `get`, `head`, `post`, etc. + +Espera-se que as subclasses concretas de `View` implementem os métodos de +tratamento, então por que aqueles métodos não são parte da interface de `View`? +A razão: subclasses são livres para implementar apenas os métodos de tratamento +que querem suportar. Uma `TemplateView` é usada apenas para exibir conteúdo, +então ela implementa apenas `get`. Se uma requisição HTTP `POST` é enviada para +uma `TemplateView`, o método herdado `View.dispatch` verifica que não há um +método de tratamento para `post`, e produz uma resposta HTTP `405 Method Not +Allowed`.footnote:[Se você gosta de padrões de projetos, note que o mecanismo de +despacho do Django é uma variação dinâmica do padrão +https://fpy.li/75[_Template Method_] (Método Template). +Ele é dinâmico porque a classe `View` não obriga +subclasses a implementarem todos os métodos de tratamento, mas `dispatch` +verifica, durante a execução, se um método de tratamento concreto está +disponível para cada requisição específica.] + +A `TemplateResponseMixin` fornece funcionalidade que interessa apenas às views +que precisam usar um template. Uma `RedirectView`, por exemplo, não tem +conteúdo, então não precisa de um template e não herda dessa mixin. +`TemplateResponseMixin` fornece comportamentos para `TemplateView` +e outras views que renderizam templates, tal como `ListView`, `DetailView`, +etc., definidas nos subpacotes de `django.views.generic`. A +<> mostra o módulo `django.views.generic.list`((("UML +class diagrams", "django.views.generic.list module"))) e parte do módulo `base`. [[django_view_list_uml]] -.Diagrama de classe UML para o módulo `django.views.generic.list`. Aqui as três classes do módulo base aparecem recolhidas (veja a <>). A classe `ListView` não tem métodos ou atributos: é uma classe agregada. -image::../images/flpy_1404.png[Diagram de classe para `django.views.generic.list`] - -Views baseadas em classes não são universalmente aceitas por usuários do Django. Muitos as usam de forma limitada, como caixas opacas. Mas quando é necessário criar algo novo, muitos programadores Django continuam criando funções monolíticas de views, para abarcar todas aquelas responsabilidades, ao invés de tentar reutilizar as views base e as mixins. - -Demora um certo tempo para aprender a usar as views baseadas em classes e a forma de estendê-las para suprir necessidades específicas de uma aplicação, mas considero que vale a pena estudá-las. Elas eliminam muito código repetitivo, tornam mais fácil reutilizar soluções, e melhoram até a comunicação das equipes—por exemplo, pela definição de nomes padronizados para as templates e para as variáveis passadas para contextos de templates. Views baseadas em classes são views do Django "on rails"footnote:[NT: Literalmente "nos trilhos", mas obviamente uma referência à popular framework web baseada na linguagem Ruby, a _Ruby on Rails_].((("", startref="Django14"))) +.Diagrama de classe UML do o módulo `django.views.generic.list`. Aqui as três classes do módulo `base` aparecem recolhidas (veja a <>). A classe `ListView` não tem métodos ou atributos: é uma classe agregada. +image::../images/flpy_1404.png[align="center",pdfwidth=12cm] + +Para usuários do Django, a classe mais importante na <> é +`ListView`, uma classe agregada sem qualquer código (seu corpo é apenas uma +docstring). Quando instanciada, uma `ListView` tem um atributo de instância +`object_list`, através do qual o código do template pode iterar para montar o +conteúdo da página, normalmente o resultado de uma consulta a um banco de dados, +composto de múltiplos objetos. Toda a funcionalidade relacionada com a geração +deste iterável de objetos vem da `MultipleObjectMixin`. Esta mixin também +oferece uma lógica complexa de paginação—para exibir parte dos resultados em uma +página e links para mais páginas. + +Suponha que você queira criar uma view que não vai renderizar um template, mas +sim produzir uma lista de objetos em formato JSON. Para isso existe +`BaseListView`. Ela oferece um ponto inicial de extensão fácil de usar, unindo a +funcionalidade de `View` e de `MultipleObjectMixin`, mas sem a complexidade do +mecanismo de templates. + +A API de views baseadas em classes do Django é um exemplo melhor de herança +múltipla que o Tkinter. Em especial, é fácil entender suas classes mixin: cada +uma tem um propósito bem definido, e tseus nomes terminam com o sufixo +`…Mixin`. + +Views baseadas em classes não são universalmente aceitas por usuários do Django. +Muitos as usam de forma limitada, como caixas opacas. Mas quando é necessário +criar algo novo, muitos programadores Django continuam criando funções +monolíticas de views, para abarcar todas aquelas responsabilidades, ao invés de +tentar reutilizar as views base e as mixins. + +Demora um certo tempo para aprender a usar as views baseadas em classes e a +forma de estendê-las para suprir as necessidades específicas de uma aplicação, +mas considero que vale a pena estudá-las. Elas eliminam muito código repetitivo, +facilitam o reuso de soluções, e melhoram até a comunicação das +equipes—por exemplo, pela definição de nomes padronizados para os templates e +para as variáveis passadas para contextos de templates. Views baseadas em +classes são views do Django "on rails"footnote:[NT: Literalmente "nos trilhos", +mas claramente uma referência ao popular framework _Ruby on Rails_].((("", startref="Django14"))) ==== Herança múltipla no Tkinter -Um((("Tkinter GUI toolkit", "multiple inheritance in", id="tinkter14"))) exemplo extremo de herança múltipla na biblioteca padrão de Python é o -https://docs.python.org/pt-br/3/library/tkinter.html[toolkit de interface gráfica Tkinter]. -Usei parte da hierarquia de componentes do Tkinter para ilustrar a MRO na <>. A -<> mostra todos as classes de componentes no pacote base `tkinter` (há mais componentes gráficos no subpacote https://docs.python.org/pt-br/3/library/tkinter.ttk.html[`tkinter.ttk`]). +Um((("Tkinter GUI toolkit", "multiple inheritance in", id="tinkter14"))) exemplo +extremo de herança múltipla na biblioteca padrão de Python é o +https://fpy.li/76[toolkit de interface gráfica Tkinter]. +No momento em que escrevo essa seção, o Tkinter já tem 25 anos de idade. Não +é um exemplo das melhores práticas atuais. Mas mostra como a herança múltipla +era usada quando os programadores ainda não conheciam suas desvantagens. E vai +nos servir de contra-exemplo, quando tratarmos de algumas boas práticas, na +próxima seção. + +Usei parte da +hierarquia de componentes do Tkinter para ilustrar a MRO na +<>. A <> mostra todas as classes de componentes +no pacote base `tkinter` (há mais componentes gráficos no subpacote +https://fpy.li/77[`tkinter.ttk`]). [[tkinter_uml]] -.Diagrama de classes resumido da hierarquia de classes de interface gráfica do Tkinter; classes etiquetadas com «mixin» são projetadas para oferecer metodos concretos a outras classes, através de herança múltipla. +.Diagrama de classes resumido da hierarquia de classes de interface gráfica do Tkinter; classes marcadas com «mixin» existem para oferecer metodos concretos a outras classes, por herança múltipla. image::../images/flpy_1405.png[Diagrama de classes UML dos componentes do Tkinter] -No momento em que escrevo essa seção, o Tkinter já tem 25 anos de idade. Ele não é um exemplo das melhores práticas atuais. Mas mostra como a herança múltipla era usada quando os programadores ainda não conheciam suas desvantagens. E vai nos servir de contra-exemplo, quando tratarmos de algumas boas práticas, na próxima seção. - Considere as seguintes classes na <>: -➊ `Toplevel`: A classe de uma janela principal em um aplicação Tkinter. +`① Toplevel`: A classe de uma janela principal em um aplicação Tkinter. -➋ `Widget`: A superclasse de todos os objetos visíveis que podem ser colocados em uma janela. +`② Widget`: A superclasse de todos os objetos visíveis que podem ser colocados em uma janela. -➌ `Button`: Um componente de botão simples. +`③ Button`: Um componente de botão simples. -➍ `Entry`: Um campo de texto editável de uma única linha. +`④ Entry`: Um campo de texto editável de uma única linha. -➎ `Text`: Um campo de texto editável de múltiplas linhas. +`⑤ Text`: Um campo de texto editável de múltiplas linhas. Aqui estão as MROs dessas classes, como exibidas pela função `print_mro` do <>: @@ -684,76 +1010,141 @@ Text, Widget, BaseWidget, Misc, Pack, Place, Grid, XView, YView, object [NOTE] ==== -Pelos padrões atuais, a hierarquia de classes do Tkinter é muito profunda. Poucas partes da bilbioteca padrão de Python tem mais que três ou quatro níveis de classes concretas, e o mesmo pode ser dito da biblioteca de classes de Java. -Entretanto, é interessante observar que algumas das hierarquias mais profundas da biblioteca de classes de Java são precisamente os pacotes relacionados à programação de interfaces gráficas: + +Pelos padrões atuais, a hierarquia de classes do Tkinter é profunda demais. +Poucas partes da bilbioteca padrão de Python tem mais que três ou quatro níveis +de classes concretas, e o mesmo pode ser dito da biblioteca de classes de Java. +Entretanto, é interessante observar que algumas das hierarquias mais profundas +da biblioteca de classes de Java são precisamente os pacotes relacionados à +programação de interfaces gráficas: https://fpy.li/14-26[`java.awt`] e https://fpy.li/14-27[`javax.swing`]. -O https://fpy.li/14-28[Squeak], uma versão moderna e aberta de Smalltalk, -inclui o poderoso e inovador toolkit de interface gráfica Morphic, também com uma hierarquia de classes profunda. -Na minha experiência, é nos toolkits de interface gráfica que a herança é mais útil. +O https://fpy.li/14-28[Squeak], +uma versão moderna e aberta de Smalltalk, inclui o poderoso e inovador toolkit +de interface gráfica Morphic, também com uma hierarquia de classes profunda. Na +minha experiência, é nos toolkits de interface gráfica que a herança é mais +útil. + ==== Observe como essas classes se relacionam com outras: -* `Toplevel` é a única classe gráfica que não herda de `Widget`, porque ela é a janela primária e não se comporta como um componente; por exemplo, ela não pode ser anexada a uma janela ou moldura (_frame_). `Toplevel` herda de `Wm`, que fornece funções de acesso direto ao gerenciador de janelas do ambiente, para tarefas como definir o título da janela e configurar suas bordas. -* `Widget` herda diretamente de `BaseWidget` e de `Pack`, `Place`, e `Grid`. As últimas três classes são gerenciadores de geometria: são responsáveis por organizar componentes dentro de uma janela ou moldura. Cada uma delas encapsula uma estratégia de layout e uma API de colocação de componentes diferente. -* `Button`, como a maioria dos componentes, descende diretamente apenas de `Widget`, mas indiretamente de `Misc`, que fornece dezenas de métodos para todos os componentes. +* `Toplevel` é a única classe gráfica que não herda de `Widget`, porque ela é a +janela primária e não se comporta como um componente; por exemplo, ela não pode +ser fixada a uma janela ou moldura (_frame_). `Toplevel` herda de `Wm`, que +fornece funções de acesso direto ao gerenciador de janelas do ambiente gráfico +do sistema operacional, para tarefas como definir o título da janela e +configurar suas bordas. + +* `Widget` herda diretamente de `BaseWidget` e de `Pack`, `Place`, e `Grid`. As +últimas três classes são gerenciadores de geometria: são responsáveis por +organizar componentes dentro de uma janela ou moldura. Cada uma delas encapsula +uma estratégia de layout e uma API de colocação de componentes diferente. + +* `Button`, como a maioria dos componentes, descende diretamente apenas de +`Widget`, mas indiretamente de `Misc`, que fornece dezenas de métodos para todos +os componentes. + * `Entry` é subclasse de `Widget` e `XView`, que suporta rolagem horizontal. + * `Text` é subclasse de `Widget`, `XView` e `YView` (para rolagem vertical). -Vamos agora discutir algumas boas práticas de herança múltipla e examinar se o Tkinter as segue.((("", startref="tinkter14")))((("", startref="IASreal14")))((("", startref="MIreal14"))) +Vamos agora discutir algumas boas práticas de herança múltipla e examinar +como o Tkinter se comporta.((("", startref="tinkter14")))((("", +startref="IASreal14")))((("", startref="MIreal14"))) [role="pagebreak-before less_space"] === Lidando com a herança -Aquilo((("inheritance and subclassing", "best practices", id="IAScop14"))) que Alan Kay escreveu na epígrafe continua sendo verdade: -ainda não existe um teoria geral sobre herança que possa guiar os programadores. O que temos são regras gerais, padrões de projetos, "melhores práticas", acrônimos perspicazes, tabus, etc. Alguns desses nos dão orientações úteis, mas nenhum deles é universalmente aceito ou sempre aplicável. +Aquilo((("inheritance and subclassing", "best practices", id="IAScop14"))) que +Alan Kay escreveu na epígrafe continua sendo verdade: ainda não existe um teoria +geral sobre herança que guie os programadores. O que temos são regras +gerais, padrões de projetos, "melhores práticas", acrônimos perspicazes, tabus, +etc. Alguns desses nos dão orientações úteis, mas nenhum deles é universalmente +aceito ou sempre aplicável. -É fácil criar designs frágeis e incompreensíveis usando herança, mesmo sem herança múltipla. Como não temos uma teoria abrangente, aqui estão algumas dicas para evitar grafos de classes parecidos com espaguete. +É fácil criar projetos frágeis e incompreensíveis usando herança, mesmo sem +herança múltipla. Como não temos uma teoria abrangente, aqui estão algumas dicas +para evitar diagramas de classes parecidos com um prato de espaguete. [[favor_composition_sec]] ==== Prefira a composição de objetos à herança de classes -O título dessa subseção é o segundo princípio do design orientado a objetos, do livro _Padrões de Projetos_,footnote:[Esse princípio aparece na página 20 da introdução, na edição em inglês do livro.] e é o melhor conselho que posso oferecer aqui. Uma vez que você se sinta confortável com a herança, é fácil usá-la em excesso. Colocar objetos em uma hierarquia elegante apela para nosso senso de ordem; programadores fazem isso por pura diversão. - -Preferir a composição leva a designs mais flexíveis. Por exemplo, no caso da classe -`tkinter.Widget`, em vez de herdar os métodos de todos os gerenciadores de geometria, instâncias do componente poderiam manter uma referência para um gerenciador de geometria, e invocar seus métodos. Afinal, um `Widget` não deveria "ser" um gerenciador de geometria, mas poderia usar os serviços de um deles por delegação. E daí você poderia adicionar um novo gerenciador de geometria sem afetar a hierarquia de classes do componente e sem se preocupar com colisões de nomes. Mesmo com herança simples, este princípio aumenta a flexibilidade, porque a subclasses são uma forma de acoplamento forte, e árvores de herança muito altas tendem a ser frágeis. - -A composição e a delegação podem substituir o uso de mixins para tornar comportamentos disponíveis para diferentes classes, mas não podem substituir o uso de herança de interfaces para definir uma hierarquia de tipos. - -==== Em cada caso, entenda o motivo do uso da herança - -Ao lidarmos com herança múltipla, é útil ter claras as razões pelas quais subclasses são criadas em cada caso específico. -As principais razões são: - -* Herança de interface cria um subtipo, implicando em uma relação "é-um". A melhor forma de fazer isso é usando ABCs. -* Herança de implementação evita duplicação de código pela reutilização. Mixins podem ajudar nisso. - -Na prática, frequentemente ambos os usos são simultâneos, mas sempre que você puder tornar a intenção clara, vá em frente. -Herança para reutilização de código é um detalhe de implementação, e muitas vezes pode ser substituída por composição e delegação. -Por outro lado, herança de interfaces é o fundamento de qualquer framework. -Se possível, a herança de interfaces deveria usar apenas ABCs como classes base. +O título desta seção é o segundo princípio do design orientado a objetos, do +livro _Padrões de Projetos_,footnote:[Esse princípio aparece na página 20 da +introdução, na edição em inglês.] e é o melhor conselho que posso oferecer aqui. +Uma vez que você se sinta confortável com a herança, é fácil usá-la em excesso. +Colocar objetos em uma hierarquia elegante apela para nosso senso de ordem; +programadores fazem isso por pura diversão. + +Preferir a composição leva a designs mais flexíveis. Por exemplo, no caso da +classe `tkinter.Widget`, em vez de herdar os métodos de todos os gerenciadores +de geometria, instâncias do componente poderiam manter uma referência para um +gerenciador de geometria, e invocar seus métodos. Afinal, um `Widget` não +deveria "ser" um gerenciador de geometria, mas poderia usar os serviços de um +deles por delegação. E daí você poderia adicionar um novo gerenciador de +geometria sem afetar a hierarquia de classes do componente e sem se preocupar +com colisões de nomes. Mesmo com herança simples, este princípio aumenta a +flexibilidade, porque a subclasses são uma forma de acoplamento forte, e árvores +de herança muito altas tendem a ser quebradiças. + +A composição e a delegação podem substituir o uso de mixins para tornar +comportamentos disponíveis para diferentes classes, mas não podem substituir o +uso de herança de interfaces para definir uma hierarquia de tipos. + +==== Entenda o motivo de usar herança em cada caso + +Ao lidarmos com herança múltipla, é útil ter claras as razões pelas quais +subclasses são criadas em cada caso específico. As principais razões são: + +* Herança de interface cria um subtipo, implicando em uma relação _é-um_. +A melhor forma de fazer isso é usando ABCs. + +* Herança de implementação evita duplicação de código pela reutilização. +Mixins podem ajudar nisso. + +Na prática, frequentemente as duas razões coexistem, mas quando você puder +tornar a intenção clara, faça isso. Herança para reutilização de código é um +detalhe de implementação, e muitas vezes pode ser substituída por composição e +delegação. Por outro lado, herança de interfaces é o fundamento de qualquer +framework. Idealmente, a herança de interfaces deveria usar apenas ABCs como +classes base. ==== Torne a interface explícita com ABCs -No Python moderno, se uma classe tem por objetivo definir uma interface, ela deveria ser explicitamente uma ABC ou uma subclasse de `typing.Protocol`. -Uma ABC deveria ser subclasse apenas de `abc.ABC` ou de outras ABCs. -A herança múltipla de ABCs não é problemática. +No Python moderno, se uma classe tem por objetivo definir uma interface, ela +deveria ser explicitamente uma ABC ou uma subclasse de `typing.Protocol`. Uma +ABC deveria ser subclasse apenas de `abc.ABC` ou de outras ABCs. A herança +múltipla de ABCs não é problemática. ==== Use mixins explícitas para reutilizar código -Se uma classe é projetada para fornecer implementações de métodos para reutilização por múltiplas subclasses não relacionadas, sem implicar em uma relação do tipo "é-uma", ele deveria ser uma _classe mixin_ explícita. Conceitualmente, uma mixin não define um novo tipo; ela simplesmente empacota métodos para reutilização. Uma mixin não deveria nunca ser instanciada, e classes concretas não devem herdar apenas de uma mixin. Cada mixin deveria fornecer um único comportamento específico, implementando poucos métodos intimamente relacionados. -Mixins devem evitar manter qualquer estado interno; isto é, uma classe mixin não deve ter atributos de instância. +Se uma classe é projetada para fornecer implementações de métodos para +reutilização por múltiplas subclasses não relacionadas, sem implicar em uma +relação do tipo _é-uma_, ele deveria ser uma classe mixin explícita. No Python, +não há uma maneira formal de declarar uma classe como mixin. Por isso, recomendo +que seus nomes incluam o sufixo `Mixin`. Conceitualmente, uma mixin não define +um novo tipo; ela simplesmente empacota métodos para reutilização. Uma mixin não +deveria nunca ser instanciada, e classes concretas não devem herdar apenas de +uma mixin. Cada mixin deveria fornecer um único comportamento específico, +implementando poucos métodos intimamente relacionados. Mixins devem evitar +manter qualquer estado interno; isto é, uma classe mixin não deve ter atributos +de instância. -No Python, não há uma maneira formal de declarar uma classe como mixin. Assim, é fortemente recomendado que seus nomes incluam o sufixo `Mixin`. [[aggregate_class_sec]] ==== Ofereça classes agregadas aos usuários [quote, Grady Booch et al., Object-Oriented Analysis and Design with Applications] ____ -Uma classe construída principalmente herdando de mixins, sem adicionar estrutura ou comportamento próprios, é chamada de _classe agregada_.footnote:[Grady Booch et al., "Object-Oriented Analysis and Design with Applications" (_Análise e Projeto Orientados a Objetos, com Aplicações_), 3ª ed. (Addison-Wesley), p. 109.] + +Uma classe construída principalmente herdando de mixins, sem adicionar estrutura +ou comportamento próprios, é chamada de _classe agregada_.footnote:[Grady Booch +et al., "Object-Oriented Analysis and Design with Applications" (_Análise e +Projeto Orientados a Objetos, com Aplicações_), 3ª ed. (Addison-Wesley), p. +109.] + ____ Se alguma combinação de ABCs ou mixins for especialmente útil para o código cliente, ofereça uma classe que una essas funcionalidades de uma forma sensata. @@ -766,71 +1157,146 @@ da classe `ListView` do Django, do canto inferior direito da <>, Alex Martelli cita _More Effective {cpp}_, de Scott Meyer, que diz: "toda classe não-final (não-folha) deveria ser abstrata". Em outras palavras, Meyer recomenda que subclasses deveriam ser criadas apenas a partir de classes abstratas. - -Se você precisar usar subclasses para reutilização de código, então o código a ser reutilizado deve estar em métodos mixin de ABCs, ou em classes mixin explicitamente nomeadas. - -Vamos agora analisar o Tkinter do ponto de vista dessas recomendações - -==== Tkinter: O bom, o mau e o feio - -Afootnote:[NT: O nome da seção é uma referência ao filme "The Good, the Bad and the Ugly", um clássico do _spaghetti western_ de 1966, lançado no Brasil com o título "Três Homens em Conflito".] maioria dos conselhos da seção anterior não são seguidos pelo Tkinter, com a notável excessão de "<>". E mesmo assim, esse não é um grande exemplo, pois a composição provavelmente funcionaria melhor para integrar os gerenciadores de geometria a `Widget`, como discutido na <>. - -Mas((("Tkinter GUI toolkit", "benefits and drawbacks of"))) lembre-se que o Tkinter é parte da biblioteca padrão desde Python 1.1, lançado em 1994. O Tkinter é uma camada sobreposta ao excelente toolkit Tk GUI, da linguagem Tcl. O combo Tcl/Tk não é, na origem, orientado a objetos, então a API Tk é basicamente um imenso catálogo de funções. Entretanto, o toolkit é orientado a objetos por projeto, apesar de não o ser em sua implementação Tcl original. - -A docstring de `tkinter.Widget` começa com as palavras "Internal class" (_Classe interna_). Isso sugere que `Widget` deveria provavelmente ser uma ABC. Apesar da classe `Widget` não ter métodos próprios, ela define uma interface. Sua mensagem é: "Você pode contar que todos os componentes do Tkinter vão oferecer os métodos básicos de componente (`+__init__+`, `destroy`, e dezenas de funções da API Tk), além dos métodos de todos os três gerenciadores de geometria". Vamos combinar que essa não é uma boa definição de interface (é abrangente demais), mas ainda assim é uma interface, e `Widget` a "define" como a união das interfaces de suas superclasses. - -A classe `Tk`, qie encapsula a lógica da aplicação gráfica, herda de `Wm` e `Misc`, nenhuma das quais é abstrata ou mixin (`Wm` não é uma mixin adequada, porque `TopLevel` é subclasse apenas dela). O nome da classe `Misc` é, por sí só, um mau sinal. `Misc` tem mais de 100 métodos, e todos os componentes herdam dela. Por que é necessário que cada um dos componentes tenham métodos para tratamento do clipboard, seleção de texto, gerenciamento de timer e coisas assim? Não é possível colar algo em um botão ou selecionar texto de uma barra de rolagem. `Misc` deveria ser dividida em várias classes mixin especializadas, e nem todos os componentes deveriam herdar de todas aquelas mixins. - -Para ser justo, como usuário do Tkinter você não precisa, de forma alguma, entender ou usar herança múltipla. Ela é um detalhe de implementação, oculto atrás das classes de componentes que serão instanciadas ou usadas como base para subclasses em seu código. Mas você sofrerá as consequências da herança múltipla excessiva quando digitar `dir(tkinter.Button)` e tentar encontrar um método específico em meio aos 214 atributos listados. -E terá que enfrentar a complexidade, caso decida implementar um novo componente Tk. +Criar subclasses de classes concretas é mais perigoso que criar subclasses de +ABCs e mixins, pois instâncias de classes concretas normalmente têm um estado +interno, que pode ser corrompido quando sobrescrevemos métodos que +interferem naquele estado. Mesmo se nossos métodos cooperarem chamando `super()`, +e o estado interno seja protegido através da sintaxe `__x`, restarão ainda +inúmeras formas pelas quais sobrescrever um método pode introduzir bugs. + +No texto <> (<>), Alex Martelli cita _More Effective {cpp}_, de Scott +Meyer, que diz: "toda classe não-final (não-folha) deveria ser abstrata". Em +outras palavras, Meyer recomenda que subclasses deveriam ser criadas apenas a +partir de classes abstratas. + +Se você precisar usar subclasses para reutilização de código, então o código a +ser reutilizado deve estar em métodos mixin de ABCs, ou em classes mixin +explicitamente nomeadas. + +Vamos agora analisar o Tkinter do ponto de vista destas recomendações. + +==== Tkinter: o bom, o mau e o feio + +A maioria dos conselhos da seção anterior não são seguidos pelo Tkinter, com a +notável exceção de oferecer classes agregadas (<>). E +mesmo assim, este não é um grande exemplo, pois a composição provavelmente +funcionaria melhor para integrar os gerenciadores de geometria a `Widget`, como +discutido na <>. + +Mas((("Tkinter GUI toolkit", "benefits and drawbacks of"))) lembre-se que o +Tkinter é parte da biblioteca padrão desde o Python 1.1, lançado em 1994. O +Tkinter é uma fachada em Python para o toolkit de GUI Tk, escrito na linguagem +Tcl. O combo Tcl/Tk não é, na origem, orientado a objetos, então a API Tk é +basicamente um imenso catálogo de funções. Entretanto, o toolkit é +conceitualmente orientado a objetos, apesar de não usar classes na implementação +original em Tcl. + +A docstring de `tkinter.Widget` começa com as palavras "Internal class" (Classe +interna). Isto sugere que `Widget` deveria provavelmente ser uma ABC. Apesar da +classe `Widget` não ter métodos próprios, ela define uma interface. Sua mensagem +é: "Você pode contar que todos os componentes do Tkinter vão oferecer os métodos +básicos de componente (`+__init__+`, `destroy`, e dezenas de funções da API Tk), +além dos métodos de todos os três gerenciadores de geometria". Vamos combinar +que essa não é uma boa definição de interface (é abrangente demais), mas ainda +assim é uma interface, e `Widget` a "define" como a união das interfaces de suas +superclasses. + +A classe `Tk`, que encapsula a lógica da aplicação gráfica, herda de `Wm` e +`Misc`, nenhuma das quais é abstrata ou mixin (`Wm` não é uma mixin típica, +porque `TopLevel` é subclasse apenas dela). O nome da classe `Misc` é, por sí +só, um mau sinal. `Misc` tem mais de 100 métodos, e todos os componentes herdam +dela. Por que é necessário que cada um dos componentes tenham métodos para +tratamento do clipboard, seleção de texto, gerenciamento de timer e coisas +assim? Não é possível colar algo em um botão ou selecionar texto de uma barra de +rolagem. `Misc` deveria ser dividida em várias classes mixin especializadas, e +nem todos os componentes deveriam herdar de todas aquelas mixins. + +Para ser justo, como usuário do Tkinter você não precisa, de forma alguma, +entender ou usar herança múltipla. Ela é um detalhe de implementação, oculto +atrás das classes de componentes que serão instanciadas ou usadas como base para +subclasses em seu código. Mas você sofrerá as consequências da herança múltipla +excessiva quando digitar `dir(tkinter.Button)` e tentar encontrar um método +específico em meio aos 214 atributos listados. E terá que enfrentar a +complexidade, caso decida implementar um novo componente Tk. [TIP] ==== -Apesar de ter problemas, o Tkinter é estável, flexível, e fornece um visual moderno se você usar o pacote `tkinter.ttk` e seus componentes tematizados. -Além disso, alguns dos componentes originais, como `Canvas` e `Text`, são incrivelmente poderosos. -Em poucas horas é possível transformar um objeto `Canvas` em uma aplicação de desenho razoavelmente completa. -Se você se interessa pela programação de interfaces gráficas, com certeza vale a pena considerar o Tkinter e o Tcl/Tk. + +Apesar de ter problemas, o Tkinter é estável, flexível, e fornece um visual +moderno se você usar o pacote `tkinter.ttk` e seus componentes tematizados. Além +disso, alguns dos componentes originais, como `Canvas` e `Text`, são +incrivelmente poderosos. Em poucas horas é possível transformar um objeto +`Canvas` em uma aplicação de desenho razoavelmente completa. Se você se +interessa pela programação de interfaces gráficas, com certeza vale a pena +estudar o Tkinter e o Tcl/Tk. + ==== Aqui termina nossa viagem através do labirinto da herança.((("", startref="IAScop14"))) @@ -838,140 +1304,275 @@ Aqui termina nossa viagem através do labirinto da herança.((("", startref="IAS [[inheritance_summary]] === Resumo do capítulo -Esse((("inheritance and subclassing", "overview of"))) capítulo começou com uma revisão da função `super()` no contexto de herança simples. -Daí discutimos o problema da criação de subclasses de tipos embutidos: -seus métodos nativos, implementados em C, não invocam os métodos sobrepostos em subclasses, exceto em uns poucos casos especiais. -É por isso que, quando precisamos de tipos `list`, `dict`, ou `str` customizados, é mais fácil criar subclasses de `UserList`, `UserDict`, ou `UserString`—todos definidos no módulo -https://docs.python.org/pt-br/3/library/collections.html[`collections`]—, que na verdade encapsulam os tipos embutidos correspondentes e delegam operações para aqueles—três exemplos a favor da composição sobre a herança na biblioteca padrão. Se o comportamento desejado for muito diferente daquilo que os tipos embutidos oferecem, pode ser mais fácil criar uma subclasse da ABC apropriada em https://docs.python.org/pt-br/3/library/collections.abc.html[`collections.abc`], e escrever sua própria implementação. - -O restante do capítulo foi dedicado à faca de dois gumes da herança múltipla. Primeiro vimos como a ordem de resolução de métodos, definida no atributo de classe `+__mro__+`, trata o problema de conflitos potenciais de nomes em métodos herdados. Também examinamos como a função embutida -`super()` se comporta em hierarquias com herança múltipla, e como ela algumas vezes se comporta de forma inesperada. O comportamento de `super()` foi projetado para suportar classes mixin, que estudamos usando o exemplo simples de `UpperCaseMixin` (para mapeamentos indiferentes a maiúsculas/minúsculas). - -Exploramos como a herança múltipla e os métodos mixin são usados nas ABCs de Python, bem como nos mixins de threading e forking de `socketserver`. -Usos mais complexos de herança múltipla foram exemplificados com as views baseadas em classes do Django e com o toolkit de interface gráfica Tkinter. -Apesar do Tkinter não ser um exemplo das melhores práticas modernas, é um exemplo de hierarquias de classe complexas que podemos encontrar em sistemas legados. - -Encerrando o capítulo, apresentamos sete recomendações para lidar com herança, e aplicamos alguns daqueles conselhos em um comentário sobre a hierarquia de classes do Tkinter. - -Rejeitar a herança—mesmo a herança simples—é uma tendência moderna. -Go é uma das mais bem sucedidas linguagens criadas no século 21. Ela não inclui um elemento chamado "classe", mas você pode construir tipos que são estruturas (_structs_) de campos encapsulados, e anexar métodos a essas estruturas. -Em Go é possível definir interfaces, que são checadas pelo compilador usando tipagem estrutural, também conhecida como((("static duck typing"))) _duck typing estática_—algo muito similar ao que temos com os tipos protocolo desde o Python 3.8. Essa linguagem também tem uma sintaxe especial para a criação de tipos e interfaces por composição, mas não há suporte a herança—nem entre interfaces. - -Então talvez o melhor conselho sobre herança seja: evite-a se puder. -Mas, frequentemente, não temos essa opção: os frameworks que usamos nos impõe suas escolhas de design. +Este((("inheritance and subclassing", "overview of"))) capítulo começou com uma +revisão da função `super()` no contexto de herança simples. Daí discutimos o +problema da criação de subclasses de tipos embutidos: seus métodos nativos, +implementados em C, não invocam os métodos sobrescritos em subclasses, exceto em +uns poucos casos especiais. É por isso que, quando precisamos de tipos `list`, +`dict`, ou `str` customizados, é mais fácil criar subclasses de `UserList`, +`UserDict`, ou `UserString (todos definidos no módulo +https://fpy.li/2w[`collections`]), que encapsulam os tipos embutidos +correspondentes e delegam operações para aqueles—três exemplos a favor da +composição sobre a herança na biblioteca padrão. Se o comportamento desejado for +muito diferente daquilo que os tipos embutidos oferecem, pode ser mais fácil +criar uma subclasse da ABC apropriada em +https://fpy.li/6z[`collections.abc`], +e escrever sua própria implementação. + +O restante do capítulo foi dedicado à faca de dois gumes da herança múltipla. +Primeiro vimos como a ordem de resolução de métodos, definida no atributo de +classe `+__mro__+`, trata o problema de conflitos potenciais de nomes em métodos +herdados. Também examinamos como a função embutida `super()` se comporta em +hierarquias com herança múltipla, e como ela às vezes tem um comportamento +surpreendente. O comportamento de `super()` foi projetado para suportar classes +mixin, que estudamos usando o exemplo simples de `UpperCaseMixin` (para +mapeamentos indiferentes a maiúsculas/minúsculas). + +Exploramos como a herança múltipla e os métodos mixin são usados nas ABCs de +Python, bem como na construção de servidores HTTP com mixins baseados em threads +e forks de `socketserver`. Usos mais complexos de herança múltipla foram +exemplificados com as views baseadas em classes do Django e com o toolkit de +interface gráfica Tkinter. Apesar do Tkinter não ser um exemplo das melhores +práticas modernas, é um exemplo de hierarquias de classe complexas que podemos +encontrar em sistemas legados. + +Encerrando o capítulo, apresentamos sete recomendações para lidar com herança, +e aplicamos alguns daqueles conselhos em um comentário sobre a hierarquia de +classes do Tkinter. + +Rejeitar a herança—mesmo a herança simples—é uma tendência moderna. Go é uma das +mais bem sucedidas linguagens criadas no século 21. Ela não inclui um elemento +chamado "classe", mas você pode construir tipos que são estruturas (_structs_) +de campos encapsulados, e associar métodos a essas estruturas. Em Go é possível +definir interfaces, que são checadas pelo compilador usando tipagem estrutural, +também conhecida como((("static duck typing"))) tipagem pato estática—algo +muito similar ao que temos com os tipos protocolo desde o Python 3.8. Essa +linguagem também tem uma sintaxe especial para a criação de tipos e interfaces +por composição, mas não há suporte a herança—nem entre interfaces. + +Então talvez o melhor conselho sobre herança seja: evite-a se puder. Mas, +frequentemente, não temos essa opção: os frameworks que usamos nos impõe suas +escolhas de design. [[inheritance_further_reading]] === Para saber mais [quote, Hynek Schlawack, "Subclassing in Python Redux"] ____ -No que diz respeito à legibilidade, composição feita de forma adequada é superior a herança. Como é mais frequente ler o código que escrevê-lo, como regra geral evite subclasses, mas em especial não misture os vários tipos de herança e não crie subclasses para compartilhar código. -____ - -Durante((("inheritance and subclassing", "further reading on"))) a revisão final desse livro, o revisor técnico Jürgen Gmach recomendou o post https://fpy.li/14-37["Subclassing in Python Redux" (_O ressurgimento das subclasses em Python_)], de Hynek Schlawack—a fonte da citação acima. -Schlawack é o autor do popular pacote _attrs_, e foi um dos principais contribuidores do framework de programação assíncrona Twisted, um projeto criado por Glyph Lefkowitz em 2002. -De acordo com Schlawack, após algum tempo os desenvolvedores perceberam que tinham usado subclasses em excesso no projeto. -O post é longo, e cita outros posts e palestras importantes. Muito recomendado. -Naquela mesma conclusão, Hynek Schlawack escreve: "Não esqueça que, na maioria dos casos, tudo o que você precisa é de uma função." -Concordo, e é precisamente por essa razão que _Python Fluente_ trata em detalhes das funções, antes de falar de classes e herança. -Meu objetivo foi mostrar o quanto você pode alcançar com funções se valendo das classes na biblioteca padrão, antes de criar suas próprias classes. +No que diz respeito à legibilidade, composição feita adequadamente é +superior a herança. Como é mais frequente ler o código que escrevê-lo, como +regra geral evite subclasses, mas em especial não misture os vários tipos de +herança e não crie subclasses para compartilhar código. -A criação de subclasses de tipos embutidos, a função `super`, e recursos avançados como descritores e metaclasses, foram todos introduzidos no artigo https://fpy.li/descr101["Unifying types and classes in Python 2.2" (_Unificando tipos e classes em Python 2.2_)] (EN), de Guido van Rossum. -Desde então, nada realmente importante mudou nesses recursos. -Python 2.2 foi uma proeza fantástica de evolução da linguagem, adicionando vários novos recursos poderosos em um todo coerente, sem quebrar a compatibilidade com versões anteriores. Os novo recursos eram 100% opcionais. Para usá-los, bastava programar explicitamente uma subclasse de `object`—direta ou indiretamente—, para criar uma assim chamada "classe no novo estilo". No Python 3, todas as classes são subclasses de `object`. - -O pass:[Python Cookbook, 3ª ed.], de David Beazley e Brian K. Jones (O'Reilly) inclui várias receitas mostrando o uso de `super()` e de classes mixin. Você pode começar pela esclarecedora seção https://fpy.li/14-38["8.7. Calling a Method on a Parent Class" (_Invocando um Método em uma Superclasse_)], e seguir as referências internas a partir dali. - -O post https://fpy.li/14-39["Python’s super() considered super!" (O _super() de Python é mesmo super!_)] (EN), de Raymond Hettinger, explica o funcionamento de `super` e a herança múltipla de uma perspectiva positiva. Ele foi escrito em resposta a https://fpy.li/14-40["Python's Super is nifty, but you can't use it (Previously: Python's Super Considered Harmful)" _O Super de Python é bacana, mas você não deve usá-lo (Antes: Super de Python Considerado Nocivo)_] (EN), de James Knight. -A resposta de Martijn Pieters a -https://fpy.li/14-41["How to use super() with one argument?" _(Como usar super() com um só argumento?)_] (EN) inclui uma explicação concisa e aprofundada de `super`, incluindo sua relação com descritores, um conceito que estudaremos apenas no <>. -Essa é a natureza de `super`. Ele é simples de usar em casos de uso básicos, mas é uma ferramenta poderosa e complexa, que alcança alguns dos recursos dinâmicos mais avançados de Python, raramente encontrados em outras linguagens. +____ -Apesar dos títulos daqueles posts, o problema não é exatamente com a função embutida `super`—que no Python 3 não é tão feia quanto era no Python 2. +Durante((("inheritance and subclassing", "further reading on"))) a revisão final +desse livro, o revisor técnico Jürgen Gmach recomendou o post +https://fpy.li/14-37[_Subclassing in Python Redux_] +(O ressurgimento das subclasses em Python), de Hynek Schlawack—a fonte da citação acima. +Schlawack +é o autor do popular pacote _attrs_, e foi um dos principais contribuidores do +framework de programação assíncrona Twisted, um projeto criado por Glyph +Lefkowitz em 2002. De acordo com Schlawack, após algum tempo os desenvolvedores +perceberam que haviam criado subclasses em excesso no projeto. O post é longo, e +cita outros posts e palestras importantes. Muito recomendado. + +Naquela mesma conclusão, Hynek Schlawack escreve: "Não esqueça que, na maioria +dos casos, tudo o que você precisa é de uma função." Concordo, e é precisamente +por essa razão que _Python Fluente_ trata em detalhes das funções, antes de +falar de classes e herança. Meu objetivo foi mostrar o quanto você pode alcançar +com funções se valendo das classes na biblioteca padrão, antes de criar suas +próprias classes. + +A criação de subclasses de tipos embutidos, a função `super`, e recursos +avançados como descritores e metaclasses, foram todos introduzidos no artigo +https://fpy.li/descr101[_Unifying types and classes in Python 2.2_] +(Unificando tipos e classes em Python 2.2), de Guido van Rossum. Desde então, nada +realmente importante mudou nesses recursos. Python 2.2 foi uma proeza fantástica +de evolução da linguagem, adicionando vários novos recursos poderosos em um todo +coerente, sem quebrar a compatibilidade com versões anteriores. Os novos recursos +eram 100% opcionais. Para usá-los, bastava programar explicitamente uma +subclasse de direta ou indirta de `object`, para criar uma assim chamada +_new-style class_ (classe no novo estilo) . No Python 3, +todas as classes são subclasses de `object`. + +O _Python Cookbook_, 3ª ed., de David Beazley e Brian K. Jones (O'Reilly) inclui +várias receitas mostrando o uso de `super()` e de classes mixin. Você pode +começar pela esclarecedora seção +https://fpy.li/14-38[_8.7. Calling a Method on a Parent Class_] +(Invocando um método em uma superclasse), e seguir as +referências internas a partir dali. + +O post https://fpy.li/14-39[_Python's super() considered super!_] +(O _super() de Python é mesmo super!)], de Raymond Hettinger, explica o funcionamento de +`super` e a herança múltipla de uma perspectiva positiva. Ele foi escrito em +resposta a +https://fpy.li/14-40[_Python's Super is nifty, but you can't use it (Previously: Python's Super Considered Harmful)_] +(O super de Python é bacana, mas você não deve usá-lo (Antes: super de Python considerado nocivo)), de James +Knight. A resposta de Martijn Pieters a +https://fpy.li/14-41[_How to use super() with one argument?_] +(Como usar super() com um só argumento?) +inclui uma explicação concisa e aprofundada de `super`, incluindo sua relação +com descritores, um conceito que estudaremos apenas no <>. +Assim é `super`: simples de usar nos casos básicos, mas também +uma ferramenta poderosa e complexa, que alcança alguns dos recursos dinâmicos +mais avançados de Python, raramente encontrados em outras linguagens. + +Apesar dos títulos daqueles posts, o problema não é exatamente a função +embutida `super`—que no Python 3 ficou mais fácil de usar do que era no Python 2. A questão real é a herança múltipla, algo inerentemente complicado e traiçoeiro. Michele Simionato vai além da crítica, e de fato oferece uma solução em seu -https://fpy.li/14-42["Setting Multiple Inheritance Straight" (_Colocando a Herança Múltipla em seu Lugar_)] (EN): +https://fpy.li/14-42[_Setting Multiple Inheritance Straight_] +(Colocando a herança múltipla em seu devido lugar): ele implementa _traits_ ("traços"), uma forma explícita de mixin originada na linguagem Self. Simionato escreveu, em seu blog, uma longa série de posts sobre herança múltipla em Python, incluindo -https://fpy.li/14-43["The wonders of cooperative inheritance, or using super in Python 3" (_As maravilhas da herança cooperativa, ou usando super em Python 3_)] (EN); -https://fpy.li/14-44["Mixins considered harmful," part 1 (_Mixins consideradas nocivas_)] (EN) e -https://fpy.li/14-45[part 2] (EN); -e https://fpy.li/14-46["Things to Know About Python Super," part 1 (_O que você precisa saber sobre o super de Python_)] (EN), -https://fpy.li/14-47[part 2] (EN), e https://fpy.li/14-48[part 3] (EN). +https://fpy.li/14-43[_The wonders of cooperative inheritance, or using super in Python 3_] +(As maravilhas da herança cooperativa, ou usando super em Python 3); +https://fpy.li/14-44[_Mixins considered harmful, part 1_] +(Mixins consideradas nocivas, parte 1) e +https://fpy.li/14-45[_part 2_]; +e https://fpy.li/14-46[_Things to Know About Python Super, part 1_] +(O que você precisa saber sobre o super de Python), +https://fpy.li/14-47[_part 2_], e +https://fpy.li/14-48[_part 3_]. Os posts mais antigos usam a sintaxe de `super` de Python 2, mas ainda são relevantes. -Eu li a primeira edição do _Object-Oriented Analysis and Design_, 3ª ed., de Grady Booch et al., e o recomendo fortemente como uma introdução geral ao pensamento orientado a objetos, independente da linguagem de programação. É um dos raros livros que trata da herança múltipla sem ideias pré-concebidas. - -Hoje, mais que nunca, é de bom tom evitar a herança, então cá estão duas referências sobre como fazer isso. -Brandon Rhodes escreveu https://fpy.li/14-49["The Composition Over Inheritance Principle" (_O Princípio da Composição Antes da Herança_)] (EN), parte de seu excelente guia https://fpy.li/14-50[_Python Design Patterns_ (_Padrões de Projetos no Python_)]. -Augie Fackler e Nathaniel Manista apresentaram https://fpy.li/14-51["The End Of Object Inheritance & The Beginning Of A New Modularity" (_O Fim da Herança de Objetos & O Início de Uma Nova Modularidade_)] na PyCon 2013. -Fackler e Manista falam sobre organizar sistemas em torno de interfaces e das funções que lidam com os objetos que implementam aquelas interfaces, evitando o acoplamento estreito e os pontos de falha de classes e da herança. -Isso me lembra muito a maneira de pensar do Go, mas aqui os autores a defendem para Python. +Li a primeira edição do _Object-Oriented Analysis and Design_, 3ª ed., de +Grady Booch et al., e o recomendo fortemente como uma introdução geral ao +pensamento orientado a objetos, independente da linguagem de programação. É um +dos raros livros que trata da herança múltipla sem preconceitos. + +Hoje, mais que nunca, aconselha-se evitar a herança. Então cá estão duas +referências sobre como fazer isso. Brandon Rhodes escreveu +https://fpy.li/14-49[_The Composition Over Inheritance Principle_] +(O princípio da composição antes da herança), parte de seu excelente guia +https://fpy.li/14-50[_Python Design Patterns_] +(Padrões de Projetos no Python). Augie Fackler e Nathaniel Manista apresentaram +https://fpy.li/14-51[_The End Of Object Inheritance & The Beginning Of A New Modularity_] +(O Fim da Herança de Objetos & O Início de Uma Nova Modularidade) +na PyCon 2013. Fackler e Manista falam sobre organizar sistemas em torno de +interfaces e das funções que lidam com os objetos que implementam aquelas +interfaces, evitando o acoplamento forte e os pontos de falha de classes e da +herança. É o modo de pensar da comunidade Go, aplicado ao Python. .Soapbox **** -[role="soapbox-title"] -Pense nas classes realmente necessárias +**Pense nas classes realmente necessárias** [quote, Alan Kay, The Early History of Smalltalk ("Os Primórdios de Smalltalk")] ____ -[Nós] começamos a defender a ideia de herança como uma maneira de permitir que iniciantes pudessem construir [algo] a partir de frameworks que só poderiam ser projetadas por especialistasfootnote:[Alan Kay, -"The Early History of Smalltalk" (_Os Promórdios de Smalltalk_), na SIGPLAN Not. 28, 3 (março de 1993), 69–95. Também disponível https://fpy.li/14-1[online] (EN). Agradeço a meu amigo Cristiano Anderson, que compartilhou essa referência quando eu estava escrevendo esse capítulo)]. -____ - -A((("inheritance and subclassing", "Soapbox discussion", id="IASsoap14")))((("Soapbox sidebars", "multilevel class hierarchies"))) imensa maioria dos programadores escreve aplicações, não frameworks. Mesmo aqueles que escrevem frameworks provavelmente passam muito (ou a maior parte) de seu tempo escrevendo aplicações. Quando escrevemos aplicações, normalmente não precisamos criar hierarquias de classes. No máximo escrevemos classes que são subclasses de ABCs ou de outras classes oferecidas pelo framework. Como desenvolvedores de aplicações, é muito raro precisarmos escrever uma classe que funcionará como superclasse de outra. As classes que escrevemos são, quase sempre, "classes folha" (isto é, folhas na árvore de herança). -Se, trabalhando como desenvolvedor de aplicações, você se pegar criando hierarquias de classe de múltiplos níveis, quase certamente uma ou mais das seguintes alternativas se aplica: +[...] começamos a defender a ideia de herança como uma maneira de permitir que +iniciantes pudessem construir [algo] a partir de frameworks que só poderiam ser +projetadas por especialistasfootnote:[Alan Kay, _The Early History of Smalltalk_ +(Os Promórdios de Smalltalk), na SIGPLAN Not. 28, 3 (março de 1993), +69–95. Também disponível https://fpy.li/14-1[online]. Agradeço a meu +amigo Cristiano Anderson, que compartilhou esta referência quando eu estava +escrevendo esse capítulo)]. -* Você está reinventando a roda. Procure um framework ou biblioteca que forneça componentes que possam ser reutilizados em sua aplicação. -* Você está usando um framework mal projetada. Procure uma alternativa. -* Você está complicando demais o processo. Lembre-se((("KISS principle"))) do _Princípio KISS_. -* Você ficou entediado programando aplicações e decidiu criar um novo framework. Parabéns e boa sorte! - -Também é possível que todas as alternativas acima se apliquem à sua situação: você ficou entediado e decidiu reinventar a roda, escrevendo seu próprio framework mal projetado e excessivamente complexo, e está sendo forçado a programar classe após classe para resolver problemas triviais. Espero que você esteja se divertindo, ou pelo menos que esteja sendo pago para fazer isso. - - -[role="soapbox-title"] -Tipos embutidos mal-comportados: bug ou _feature_? - -Os((("Soapbox sidebars", "trade-offs of built-ins"))) tipos embutidos `dict`, `list`, e `str` são blocos básicos essenciais do próprio Python, então precisam ser rápidos—qualquer problema de desempenho ali teria severos impactos em praticamente todo o resto. -É por isso que o CPython adotou atalhos que fazem com que métodos embutidos se comportem mal, ao não cooperarem com os métodos sobrepostos por subclasses. -Um caminho possível para sair desse dilema seria oferecer duas implementações para cada um desses tipos: um "interno", otimizado para uso pelo interpretador, e um externo, facilmente extensível. - -Mas isso nós já temos: `UserDict`, `UserList`, -e `UserString` não são tão rápidos quanto seus equivalentes embutidos, -mas são fáceis de estender. -A abordagem pragmática tomada pelo CPython significa que também podemos usar, -em nossas próprias aplicações, as implementações altamente otimizadas mas difíceis estender. -E isso faz sentido, considerando que não é tão frequente precisarmos de um mapeamento, -uma lista ou uma string customizados, mas usamos `dict`, `list`, e `str` diariamente. -Só precisamos estar cientes dos compromissos envolvidos. - - - -[role="soapbox-title"] -Herança através das linguagens - -Alan Kay((("Soapbox sidebars", "inheritance across languages"))) criou o termo "orientado a objetos", e Smalltalk tinha apenas herança simples, apesar de existirem versões com diferentes formas de suporte a herança múltipla, incluindo os dialetos modernos de Smalltalk, Squeak e Pharo, que suportam _traits_ ("traços")—um dispositivo de linguagem que pode substituir classes mixin, mas evita alguns dos problemas da herança múltipla. +____ -A primeira linguagem popular a implementar herança múltipla foi o {cpp}, e esse recurso foi abusado o suficiente para que o Java—criado para ser um substituto do {cpp}—fosse projetado sem suporte a herança múltipla de implementação (isto é, sem classes mixin). Quer dizer, isso até o Java 8 introduzir os métodos default, que tornam interfaces muito similares às classes abstratas usadas para definir interfaces em {cpp} e em Python. -Depois de Java, a linguagem da JVM mais usada é provavelmente o Scala, que implementa _traits_. +A((("inheritance and subclassing", "Soapbox discussion", +id="IASsoap14")))((("Soapbox sidebars", "multilevel class hierarchies"))) imensa +maioria dos programadores escreve aplicações, não frameworks. Mesmo aqueles que +escrevem frameworks provavelmente passam boa parte de seu tempo +escrevendo aplicações. Quando escrevemos aplicações, normalmente não precisamos +criar hierarquias de classes. No máximo escrevemos classes que são subclasses de +ABCs ou de outras classes oferecidas pelo framework. Como desenvolvedores de +aplicações, é muito raro precisarmos escrever uma classe que funcionará como +superclasse de outra. Quase sempre as classes que escrevemos são classes +folha: classes concretas sem subclasses. + +Se, trabalhando como desenvolvedor de aplicações, você se pegar criando +hierarquias de classe de múltiplos níveis, aposto que está vivendo uma destas +situações: + +* Você está reinventando a roda. Procure um framework ou biblioteca que forneça +componentes você possa reutilizar em sua aplicação. + +* Você está usando um framework mal projetado. Procure uma alternativa. + +* Você está complicando demais. Lembre-se((("KISS principle"))) do +_Princípio KISS_. + +* Você ficou entediado programando aplicações e decidiu criar um novo framework. +Parabéns e boa sorte! + +Também é possível que todas as alternativas acima descrevam situação: +você ficou entediado e decidiu reinventar a roda, escrevendo seu próprio +framework mal projetado e excessivamente complexo, e está sendo forçado a +programar classe após classe para resolver problemas triviais. Espero que você +esteja se divertindo, ou pelo menos que esteja sendo pago para fazer isso. + +**Tipos embutidos mal-comportados: bug ou _feature_?** + +Os((("Soapbox sidebars", "trade-offs of built-ins"))) tipos embutidos `dict`, +`list`, e `str` são blocos básicos essenciais do próprio Python, então precisam +ser rápidos—qualquer problema de desempenho ali teria severos impactos em +praticamente todo o resto. É por isso que o CPython adotou atalhos que fazem com +que muitos métodos embutidos escritos em C se comportem mal, ao não cooperarem +com os métodos sobrescritos por subclasses em Python. + +Uma solução para este dilema seria oferecer duas implementações para cada um +desses tipos: uma "interno", otimizada para uso pelo interpretador, e uma externa, +facilmente extensível. + +Mas veja só, isso nós já temos: `UserDict`, `UserList`, e `UserString` não são +tão rápidos quanto seus equivalentes embutidos, mas são fáceis de estender. A +abordagem pragmática tomada pelo CPython significa que também podemos usar, em +nossas próprias aplicações, as implementações altamente otimizadas mas difíceis +estender. E isso faz sentido, considerando que não é tão frequente precisarmos +de um mapeamento, uma lista ou uma string customizados, mas usamos `dict`, +`list`, e `str` diariamente. Só precisamos estar cientes dos compromissos +envolvidos. + + +**Herança através das linguagens** + +Alan Kay((("Soapbox sidebars", "inheritance across languages"))) criou o termo +"orientado a objetos", e Smalltalk tinha apenas herança simples, apesar de +existirem versões com diferentes formas de suporte a herança múltipla, incluindo +os dialetos modernos de Smalltalk, Squeak e Pharo, que suportam _traits_ +("traços")—um dispositivo de linguagem que pode substituir classes mixin, mas +evita alguns dos problemas da herança múltipla. + +A primeira linguagem popular a implementar herança múltipla foi o {cpp}, e esse +recurso foi abusado o suficiente para que o Java—criado para ser um substituto +do {cpp}—fosse projetado sem suporte a herança múltipla de implementação (isto +é, sem classes mixin). Quer dizer, isso até o Java 8 (e Kotlin) permitir métodos +default, que que aproximam suas interfaces do conceito de classes abstratas +que temps em Python e {cpp}. Outras linguagens que suportam _traits_ são versões recentes de PHP e Groovy, -bem como Rust e Raku—a linguagem antes conhecida como Perl 6.footnote:[Meu amigo e revisor técnico Leonardo Rochael explica isso melhor do que eu poderia: -"A existência continuada junto com o persistente adiamento da chegada do Perl 6 estava drenando a força de vontade da evolução do próprio Perl. Agora o Perl continua a ser desenvolvido como uma linguagem separada (está na versão 5.34), sem a ameaça de ser descontinuada pela linguagem antes conhecida como Perl 6."] +bem como Rus, Scala, t e Raku—a linguagem antes conhecida como Perl 6.footnote:[Meu amigo +e revisor técnico Leonardo Rochael explica isso melhor do que eu poderia: "A +existência continuada junto com o persistente adiamento da chegada do Perl 6 +estava drenando a força de vontade da evolução do próprio Perl. Agora o Perl +continua a ser desenvolvido como uma linguagem separada (está na versão 5.34), +sem a ameaça de ser descontinuada pela linguagem antes conhecida como Perl 6."] Então podemos dizer que _traits_ estão na moda em 2021. -Ruby traz uma perspectiva original para a herança múltipla: -não a suporta, mas introduz mixins como um recurso explícito da linguagem. Uma classe Ruby pode incluir um módulo em seu corpo, e aí os métodos definidos no módulo se tornam parte da implementação da classe. -Essa é uma forma "pura" de mixin, sem herança envolvida, e está claro que uma mixin Ruby não tem qualquer influência sobre o tipo da classe onde ela é usada. -Isso oferece os benefícios das mixins, evitando muitos de seus problemas mais comuns. - -Duas novas linguagens orientadas a objetos que estão recebendo muita atenção limitam severamente a herança: Go e Julia. -Ambas giram em torno de programar "objetos" implementando "métodos", e suportam https://pt.wikipedia.org/wiki/Polimorfismo_(ci%C3%AAncia_da_computa%C3%A7%C3%A3o)[polimorfismo], mas evitam o termo "classe". - -Go não tem qualquer tipo de herança, mas oferece uma sintaxe que facilita a composição. Julia tem uma hierarquia de tipos, mas subtipos não podem herdar estrutura, apenas comportamentos, e só é permitido criar subtipos de tipos abstratos. Além disso, os métodos de Julia são implementados com despacho múltiplo—uma forma mais avançada do mecanismo que vimos na <>.((("", startref="IASsoap14"))) +Ruby traz uma perspectiva original para a herança múltipla: não a suporta, mas +introduz mixins como um recurso explícito da linguagem. Uma classe Ruby pode +incluir um módulo em seu corpo, e aí os métodos definidos no módulo se tornam +parte da implementação da classe. Essa é uma forma "pura" de mixin, sem herança +envolvida, e está claro que uma mixin em Ruby não influencia o tipo da classe +onde que a utiliza. Isto oferece os benefícios das mixins, evitando muitos de +seus problemas mais comuns. + +Duas novas linguagens orientadas a objetos que estão recebendo muita atenção +limitam severamente a herança: Go e Julia. Ambas giram em torno de programar +"objetos" implementando "métodos", e suportam https://fpy.li/7b[polimorfismo], +mas evitam o termo "classe". + +Go não tem nenhum tipo de herança, mas oferece uma sintaxe que facilita a +composição de suas interfaces e structs. Julia tem uma hierarquia de tipos, mas +subtipos não podem herdar estrutura, só comportamentos, e só é permitido +criar subtipos de tipos abstratos. Além disso, os métodos de Julia são +implementados com despacho múltiplo—uma forma mais avançada do mecanismo de +despacho único que vimos na <>.((("", +startref="IASsoap14"))) **** diff --git a/online/cap15.adoc b/online/cap15.adoc index 8ca5f93..7bb60a0 100644 --- a/online/cap15.adoc +++ b/online/cap15.adoc @@ -5,18 +5,25 @@ [quote, Guido van Rossum, um fã do Monty Python] ____ -Aprendi uma dolorosa lição: para programas pequenos, a tipagem dinâmica é ótima. -Para programas grandes é necessária uma abordagem mais disciplinada. -E ajuda se a linguagem der a você aquela disciplina, ao invés de dizer "Bem, faça o que quiser".footnote:[De um vídeo no YouTube da -_A Language Creators' Conversation: Guido van Rossum, James Gosling, Larry Wall & Anders Hejlsberg_ ("Uma Conversa entre Criadores de Linguagens: Guido van Rossum, James Gosling, Larry Wall & Anders Hejlsberg), transmitido em 2 de abril de 2019. A citação (editada por brevidade) começa em https://fpy.li/15-1[1:32:05]. -A transcrição completa está disponível em https://github.com/fluentpython/language-creators (EN).] + +Aprendi uma dura lição: para programas pequenos, a tipagem dinâmica é ótima. +Para programas grandes precisamos de uma abordagem mais disciplinada. E ajuda se +a linguagem der a você aquela disciplina, ao invés de dizer "Bem, faça o que +quiser".footnote:[De um vídeo no YouTube da _A Language Creators' Conversation: +Guido van Rossum, James Gosling, Larry Wall & Anders Hejlsberg_ (Uma Conversa +entre Criadores de Linguagens: Guido van Rossum, James Gosling, Larry Wall & +Anders Hejlsberg), transmitido em 2 de abril de 2019. A citação (editada por +brevidade) começa em https://fpy.li/15-1[1:32:05]. Produzi e publiquei +a transcrição completa em https://https://fpy.li/9k.] + ____ -Esse((("gradual type system", "topics covered"))) capítulo é uma continuação do <>, e fala mais sobre o sistema de tipagem gradual de Python. +Este((("gradual type system", "topics covered"))) capítulo é uma continuação do +<>, e fala mais sobre o sistema de tipagem gradual de Python. Os tópicos principais são: -* Assinaturas de funções sobrepostas +* Assinaturas de funções sobrecarregadas * `typing.TypedDict`: dando dicas de tipos para `dicts` usados como registros * Coerção de tipo * Acesso a dicas de tipo durante a execução @@ -27,30 +34,38 @@ Os tópicos principais são: === Novidades neste capítulo -Esse((("gradual type system", "significant changes to"))) capítulo é inteiramente novo, escrito para essa segunda edição de _Python Fluente_. Vamos começar com sobreposições. +Esse((("gradual type system", "significant changes to"))) capítulo é +inteiramente novo, escrito para essa segunda edição de _Python Fluente_. +Vamos começar com sobrecargas. [[overload_sec]] -=== Assinaturas sobrepostas +=== Assinaturas sobrecarregadas -No Python, funções((("gradual type system", "overloaded signatures", id="GTSoverload15")))((("overloaded signatures", id="overlaodsig15")))((("@typing.overload decorator", id="attyping15"))) podem aceitar diferentes combinações de argumentos. +No Python, as funções((("gradual type system", "overloaded signatures", +id="GTSoverload15")))((("overloaded signatures", +id="overlaodsig15")))((("@typing.overload decorator", id="attyping15"))) +podem aceitar diferentes combinações de argumentos. -O decorador `@typing.overload` permite anotar tais combinações. Isso é particularmente importante quando o tipo devolvido pela função depende do tipo de dois ou mais parâmetros. +O decorador `@typing.overload` permite anotar tais combinações. Isto é +particularmente importante quando o tipo devolvido pela função depende do tipo +de dois ou mais parâmetros. -Considere a função embutida `sum`. Esse é o texto de `help(sum)`.footnote:[NT: Texto original em inglês: "Return the sum of a 'start' value (default: 0) plus an iterable of numbers -When the iterable is empty, return the start value. -This function is intended specifically for use with numeric values and may reject non-numeric types"]: +Considere a função embutida `sum`. Esse é o texto de `help(sum)`, traduzido: [source] ---- >>> help(sum) sum(iterable, /, start=0) - Devolve a soma de um valor 'start' (default: 0) mais a soma dos números de um iterável + Devolve a soma de um valor 'start' (default: 0) mais a soma dos + números de um iterável Quando o iterável é vazio, devolve o valor inicial ('start'). - Essa função é direcionada especificamente para uso com valores numéricos e pode rejeitar tipos não-numéricos. + Esta função é direcionada especificamente para uso com valores + numéricos e pode rejeitar tipos não-numéricos. ---- -A função embutida `sum` é escrita em C, mas o _typeshed_ tem dicas de tipos sobrepostas para ela, em https://fpy.li/15-2[_builtins.pyi_]: +A função embutida `sum` é escrita em C, mas o _typeshed_ tem dicas de tipos +sobrecarregadas para ela, em https://fpy.li/15-2[_builtins.pyi_]: [source, python] ---- @@ -60,23 +75,30 @@ def sum(__iterable: Iterable[_T]) -> Union[_T, int]: ... def sum(__iterable: Iterable[_T], start: _S) -> Union[_T, _S]: ... ---- -Primeiro, vamos olhar a sintaxe geral das sobreposições. +Primeiro, vamos olhar a sintaxe geral das sobrecargas. Esse acima é todo o código sobre `sum` que você encontrará no arquivo stub (_.pyi_). A implementação estará em um arquivo diferente. -As reticências (`\...`) não tem qualquer função além de cumprir a exigência sintática para um corpo de função, como no caso de `pass`. +As reticências (`\...`) não tem qualquer função além de cumprir a exigência +sintática para um corpo de função, em vez usar de `pass`. Assim os arquivos _.pyi_ são arquivos Python válidos. -Como mencionado na <>, os dois sublinhados prefixando `+__iterable+` são a convenção da PEP 484 para argumentos apenas posicionais, que é checada pelo Mypy. -Isso significa que você pode invocar `sum(my_list)`, mas não `sum(__iterable = my_list)`. +Como mencionado na <>, os dois sublinhados prefixando +`+__iterable+` são a convenção da PEP 484 para argumentos apenas posicionais, +que é checada pelo Mypy. Isso significa que você pode invocar `sum(my_list)`, +mas não `sum(__iterable = my_list)`. -O checador de tipos tenta fazer a correspondência entre os argumentos dados com cada assinatura sobreposta, em ordem. -A chamada `sum(range(100), 1000)` não casa com a primeira sobreposição, pois aquela assinatura tem apenas um parâmetro. Mas casa com a segunda. +O checador de tipos tenta fazer a correspondência entre os argumentos dados com +cada assinatura sobrecarregada, em ordem. A chamada `sum(range(100), 1000)` não +casa com a primeira sobrecarga, pois aquela assinatura tem apenas um parâmetro. +Mas casa com a segunda. -Você pode também usar `@overload` em um modulo Python regular, colocando as assinaturas sobrepostas logo antes da assinatura real da função e de sua implementação. -O <> mostra como `sum` apareceria anotada e implementada em um módulo Python. +Você pode também usar `@overload` em um modulo Python (_.py_) normal, colocando +as assinaturas sobrecarregadas logo antes da assinatura real da função e de sua +implementação. O <> mostra como `sum` apareceria anotada e +implementada em um módulo Python. [[sum_overload_ex]] -._mysum.py_: definição da função `sum` com assinaturaas sobrepostas +._mysum.py_: definição da função `sum` com assinaturaas sobrecarregadas ==== [source, python] ---- @@ -84,47 +106,63 @@ include::../code/15-more-types/mysum.py[] ---- ==== <1> Precisamos deste segundo `TypeVar` na segunda assinatura. -<2> Essa assinatura é para o caso simples: `sum(my_iterable)`. -O tipo do resultado pode ser `T`—o tipo dos elementos que `my_iterable` produz—ou pode ser `int`, se o iterável for vazio, pois o valor default do parâmetro `start` é `0`. -<3> Quando `start` é dado, ele pode ser de qualquer tipo `S`, então o tipo do resultado -é `Union[T, S]`. -É por isso que precisamos de `S`. Se `T` fosse reutilizado aqui, então o tipo de `start` teria que ser do mesmo tipo dos elementos de `Iterable[T]`. -<4> A assinatura da implementação efetiva da função não tem dicas de tipo. + +<2> Essa assinatura é para o caso simples: `sum(my_iterable)`. O tipo do +resultado pode ser `T`—o tipo dos elementos que `my_iterable` produz—ou pode ser +`int`, se o iterável for vazio, pois o valor default do parâmetro `start` é `0`. + +<3> Quando `start` é dado, ele pode ser de qualquer tipo `S`, então o tipo do +resultado é `Union[T, S]`. É por isso que precisamos de `S`. Se `T` fosse +reutilizado aqui, então o tipo de `start` teria que ser do mesmo tipo dos +elementos de `Iterable[T]`. + +<4> A assinatura da implementação real da função não tem dicas de tipo. São muitas linhas para anotar uma função de uma única linha. -Sim, eu sei, provavelmente isso é excessivo. -Mas pelo menos a função do exemplo não é `foo`. +Sinto muito, mas pelo menos a função do exemplo não é `foo`. -Se você quiser aprender sobre `@overload` lendo código, o _typeshed_ tem centenas de exemplos. -Quando escrevo esse capítulo, o https://fpy.li/15-3[arquivo stub] do _typeshed_ para as funções embutidas de Python tem 186 sobreposições—mais que qualquer outro na biblioteca padrão. +Se quiser aprender sobre `@overload` lendo código, o _typeshed_ tem centenas de +exemplos. Quando escrevo esse capítulo, o https://fpy.li/15-3[arquivo stub] do +_typeshed_ para as funções embutidas de Python tem 186 sobrecargas—mais que +qualquer outro na biblioteca padrão. .Aproveite a tipagem gradual [TIP] ==== -Tentar produzir código 100% anotado pode levar a dicas de tipo que acrescentam muito ruído e pouco valor agregado. Refatoração para simplificar as dicas de tipo pode levar a APIs pesadas. -Algumas vezes é melhor ser pragmático, e deixar parte do código sem dicas de tipo. + +Tentar produzir código 100% anotado pode levar a dicas de tipo que acrescentam +muito ruído e pouco valor agregado. Refatoração para simplificar as dicas de +tipo pode levar a APIs inconvenientes para quem vai usar. +Algumas vezes é melhor ser pragmático, e deixar +parte do código sem dicas de tipo. + ==== -As APIs convenientes e práticas que consideramos pythônicas são muitas vezes difíceis de anotar. -Na próxima seção veremos um exemplo: -são necessárias seis sobreposições para anotar adequadamente a flexível função embutida `max`. +As APIs convenientes e práticas que consideramos pythônicas são muitas vezes +difíceis de anotar. Na próxima seção veremos um exemplo: são necessárias seis +sobrecargas para anotar adequadamente a função embutida `max`, +que é muito flexível nos parâmetros que aceita. [[max_overload_sec]] -==== Sobreposição máxima +==== Sobrecarga máxima -É((("max() function", id="maxfunc15")))((("functions", "max() function"))) difícil acrescentar dicas de tipo a funções que usam os poderosos recursos dinâmicos de Python. +É((("max() function", id="maxfunc15")))((("functions", "max() function"))) +difícil acrescentar dicas de tipo a funções que usam os poderosos recursos +dinâmicos de Python. -Quando estudava o typeshed, enconterei o relatório de bug https://fpy.li/shed4051[#4051] (EN): -Mypy não avisou que é ilegal passar `None` como um dos argumentos para a função embutida `max()`, ou passar um iterável que em algum momento produz `None`. -Nos dois casos, você recebe uma exceção como a seguinte durante a execução: +Quando estudava o typeshed, encontrei o relatório de bug +https://fpy.li/shed4051[#4051]: Mypy não avisou que é proibido passar `None` como +um dos argumentos para a função embutida `max()`, ou passar um iterável que em +algum momento produz `None`. Nos dois casos, você recebe uma exceção como a +seguinte durante a execução: ---- TypeError: '>' not supported between instances of 'int' and 'NoneType' - -[NT: TypeError: '>' não é suportado entre instâncias de 'int' e 'NoneType'] ---- +Tradução: '>' não é suportado entre instâncias de 'int' e 'NoneType'. + A documentação de `max` começa com a seguinte sentença: [quote] @@ -134,12 +172,15 @@ ____ Para mim, essa é uma descrição bastante intuitiva. -Mas se eu for anotar uma função descrita nesses termos, tenho que perguntar: qual dos dois? Um iterável ou dois ou mais argumentos? +Mas se eu for anotar uma função descrita nesses termos, tenho que perguntar: +qual dos dois? Um iterável ou dois ou mais argumentos? -A realidade é mais complicada, porque `max` também pode receber dois argumentos opcionais: -`key` e `default`. +A realidade é mais complicada, porque `max` também pode receber dois argumentos +opcionais: `key` e `default`. -Escrevi `max` em Python para tornar mais fácil ver a relação entre o funcionamento da função e as anotações sobrepostas (a função embutida original é escrita em C); veja o <>. +Escrevi `max` em Python para evidenciar a relação entre o +funcionamento da função e as anotações sobrecarregadas (a função embutida +original é escrita em C); veja o <>. [[mymax_ex]] ._mymax.py_: Versão da função `max` em Python @@ -157,19 +198,28 @@ include::../code/15-more-types/protocol/mymax/mymax.py[tags=MYMAX] ---- ==== -O foco desse exemplo não é a lógica de `max`, então não vou perder tempo com a implementação, exceto para explicar `MISSING`. -A constante `MISSING` é uma instância única de `object`, usada como sentinela. -É o valor default para o argumento nomeado `default=`, de modo que `max` pode aceitar `default=None` e ainda assim distinguir entre duas situações. +O foco deste exemplo não é a lógica de `max`, então não vou explicar a +implementação, exceto para falar sobre `MISSING`. A constante `MISSING` é uma +instância única de `object`, usada como sentinela. É o valor default para o +argumento nomeado `default=`, de modo que `max` pode aceitar `default=None` e +ainda assim distinguir entre estas duas situações: + +. O usuário passou `None` como argumento `default`. +. O usuário não passou o argumento `default` +(neste caso seu valor fica sendo `MISSING`). Quando `first` é um iterável vazio... -. O usuário não forneceu um argumento para `default=`, então ele é `MISSING`, e `max` gera um `ValueError`. -. O usuário forneceu um valor para `default=`, incluindo `None`, e então `max` devolve o valor de `default`. +. Se o usuário não forneceu um argumento para `default=`, então ele é `MISSING`, e `max` gera um `ValueError`. +. Se usuário forneceu um valor para `default=`, incluindo `None`, e então `max` devolve o valor de `default`. -Para consertar o https://fpy.li/shed4051[issue #4051], escrevi o código no <>.footnote:[Agradeço a Jelle Zijlstra—um mantenedor do _typeshed_—que me ensinou várias coisas, incluindo como reduzir minhas nove sobreposições originais para seis.] +Para consertar o https://fpy.li/shed4051[issue #4051], escrevi o código no +<>.footnote:[Agradeço a Jelle Zijlstra—um mantenedor do +_typeshed_—que me ensinou várias coisas, incluindo como reduzir minhas nove +sobrecargas originais para "apenas" seis.] [[mymax_types_ex]] -._mymax.py_: início do módulo, com importações, definições e sobreposições +._mymax.py_: início do módulo, com importações, definições e sobrecargas ==== [source, python] ---- @@ -177,13 +227,17 @@ include::../code/15-more-types/protocol/mymax/mymax.py[tags=MYMAX_TYPES] ---- ==== -Minha implementação de `max` em Python tem mais ou menos o mesmo tamanho daquelas importações e declarações de tipo. -Graças ao _duck typing_, meu código não tem nenhuma checagem usando `isinstance`, e fornece a mesma checagem de erro daquelas dicas de tipo—mas apenas durante a execução, claro. +Minha implementação de `max` em Python tem mais ou menos o mesmo tamanho +daquelas importações e declarações de tipo. Graças à tipagem pato, meu código +não tem nenhuma checagem usando `isinstance`, e fornece a mesma checagem de erro +daquelas dicas de tipo—mas apenas durante a execução, claro. -Um benefício fundamental de `@overload` é declarar o tipo devolvido da forma mais precisa possível, de acordo com os tipos dos argumentos recebidos. -Veremos esse benefício a seguir, estudando as sobreposições de `max`, em grupos de duas ou três por vez. +Uma vantagem importante de `@overload` é declarar o tipo devolvido da forma +mais precisa possível, de acordo com os tipos dos argumentos recebidos. Veremos +este vantagem a seguir, estudando as sobrecargas de `max`, em grupos de duas ou +três por vez. -===== Argumentos implementando SupportsLessThan, mas key e default não são fornecidos +===== Argumentos implementando `SupportsLessThan`, sem `key` ou `default` [source, python] ---- @@ -196,10 +250,12 @@ def max(__iterable: Iterable[LT], *, key: None = ...) -> LT: ... ---- -Nesses casos, as entradas são ou argumentos separados do tipo `LT` que implementam `SupportsLessThan`, ou um `Iterable` de itens desse tipo. -O tipo devolvido por `max` é do mesmo tipo dos argumentos ou itens reais, como vimos na <>. +Nestes casos, as entradas são ou argumentos separados do tipo `LT` que +implementam `SupportsLessThan`, ou um `Iterable` de itens desse tipo. O tipo +devolvido por `max` é do mesmo tipo dos argumentos ou itens reais, como vimos na +<>. -Amostras de chamadas que casam com essas sobreposições: +Amostras de chamadas que casam com essas sobrecargas: [source, python] ---- @@ -207,7 +263,7 @@ max(1, 2, -3) # returns 2 max(['Go', 'Python', 'Rust']) # returns 'Rust' ---- -===== Argumento key fornecido, mas default não +===== Argumento `key` fornecido, mas `default` não [source, python] ---- @@ -221,10 +277,11 @@ def max(__iterable: Iterable[T], *, key: Callable[[T], LT]) -> T: ---- As entradas podem ser item separados de qualquer tipo `T` ou um único -`Iterable[T]`, e `key=` deve ser um invocável que recebe um argumento do mesmo tipo `T`, e devolve um valor que implementa `SupportsLessThan`. -O tipo devolvido por `max` é o mesmo dos argumentos reais. +`Iterable[T]`, e `key=` deve ser um invocável que recebe um argumento do mesmo +tipo `T`, e devolve um valor que implementa `SupportsLessThan`. O tipo devolvido +por `max` é o mesmo dos argumentos reais. -Amostras de chamadas que casam com essas sobreposições: +Amostras de chamadas que casam com essas sobrecargas: [source, python] ---- @@ -232,7 +289,7 @@ max(1, 2, -3, key=abs) # returns -3 max(['Go', 'Python', 'Rust'], key=len) # returns 'Python' ---- -===== Argumento default fornecido, key não +===== Argumento `default` fornecido, `key` não [source, python] ---- @@ -244,9 +301,10 @@ def max(__iterable: Iterable[LT], *, key: None = ..., A entrada é um iterável de itens do tipo `LT` que implemente `SupportsLessThan`. O argumento `default=` é o valor devolvido quando `Iterable` é vazio. -Assim, o tipo devolvido por `max` deve ser uma `Union` do tipo `LT` e do tipo do argumento `default`. +Assim, o tipo devolvido por `max` deve ser uma `Union` do tipo `LT` e +do tipo do argumento `default`. -Amostras de chamadas que casam com essas sobreposições: +Amostras de chamadas que casam com essas sobrecargas: [source, python] ---- @@ -255,7 +313,7 @@ max([], default=None) # returns None ---- -===== Argumentos key e default fornecidos +===== Argumentos `key` e `default` fornecidos [source, python] ---- @@ -268,10 +326,12 @@ def max(__iterable: Iterable[T], *, key: Callable[[T], LT], As entradas são: * Um `Iterable` de itens de qualquer tipo `T` -* Invocável que recebe um argumento do tipo `T` e devolve um valor do tipo `LT`, que implementa `SupportsLessThan` +* Invocável que recebe um argumento do tipo `T` +e devolve um valor do tipo `LT`, que implementa `SupportsLessThan` * Um valor default de qualquer tipo `DT` -O tipo devolvido por `max` deve ser uma `Union` do tipo `T` e do tipo do argumento `default`: +O tipo devolvido por `max` deve ser uma `Union` do tipo `T` e do tipo do +argumento `default`: [source, python] @@ -282,24 +342,33 @@ max([], key=abs, default=None) # returns None -==== Lições da sobreposição de max +==== Lições da sobrecarga de max -Dicas de tipo permitem ao Mypy marcar uma chamada como `max([None, None])` com essa mensagem de erro: +Dicas de tipo permitem ao Mypy marcar uma chamada como `max([None, None])` com +essa mensagem de erro: ---- mymax_demo.py:109: error: Value of type variable "_LT" of "max" cannot be "None" ---- -Por outro lado, ter de escrever tantas linhas para suportar o checador de tipos pode desencorajar a criação de funções convenientes e flexíveis como `max`. -Se eu precisasse reinventar também a função `min`, poderia refatorar e reutilizar a maior parte da implementação de `max`. -Mas teria que copiar e colar todas as declarações de sobreposição—apesar delas serem idênticas para `min`, exceto pelo nome da função. +Por outro lado, escrever tantas linhas para suportar o checador de tipos +pode desencorajar a criação de funções convenientes e flexíveis como `max`. Se +eu precisasse reinventar também a função `min`, poderia refatorar e reutilizar a +maior parte da implementação de `max`. Mas teria que copiar e colar todas as +declarações de sobrecarga—apesar delas serem idênticas para `min`, exceto pelo +nome da função. -Meu amigo João S. O. Bueno—um dos desenvolvedores Python mais inteligentes que conheço—escreveu o seguinte https://fpy.li/15-4[tweet]: +Meu amigo João S. O. Bueno—um dos desenvolvedores Python mais inteligentes que +conheço—escreveu o seguinte https://fpy.li/15-4[tweet]: ____ -Apesar de ser difícil expressar a assinatura de `max`—ela se encaixa muito facilmente em nossa estrutura mental. Considero a expressividade das marcas de anotação muito limitadas, se comparadas à de Python. + +Apesar de ser difícil expressar a assinatura de `max`—ela se encaixa muito +facilmente em nossa estrutura mental. Considero a expressividade das marcas de +anotação muito limitadas, se comparadas à de Python. + ____ Vamos agora examinar o elemento de tipagem `TypedDict`. @@ -312,12 +381,21 @@ Experimentar com `TypedDict` demonstra as limitações da tipagem estática para [WARNING] ==== -É((("gradual type system", "TypedDict", id="GTStypeddict15")))((("TypedDict", id="typeddict15"))) tentador usar `TypedDict` para se proteger contra erros ao tratar estruturas de dados dinâmicas como as respostas da API JSON. -Mas os exemplos aqui deixam claro que o tratamento correto do JSON precisa acontecer durante a execução, e não com checagem estática de tipo. -Para checar estruturas similares a JSON usando dicas de tipo durante a execução, dê uma olhada no pacote https://fpy.li/15-5[_pydantic_] no PyPI. + +É((("gradual type system", "TypedDict", id="GTStypeddict15")))((("TypedDict", +id="typeddict15"))) tentador usar `TypedDict` para se proteger contra erros ao +tratar estruturas de dados dinâmicas como as respostas da API JSON. Mas os +exemplos aqui deixam claro que o tratamento correto de JSON precisa acontecer +durante a execução, e não com checagem estática de tipo. Para checar estruturas +similares a JSON usando dicas de tipo durante a execução, dê uma olhada no +pacote https://fpy.li/15-5[_pydantic_] no PyPI. + ==== -Algumas vezes os dicionários de Python são usados como registros, as chaves interpretadas como nomes de campos e os valores como valores dos campos de diferentes tipos. Considere, por exemplo, um registro descrevendo um livro, em JSON ou Python: +Algumas vezes os dicionários de Python são usados como registros, as chaves +interpretadas como nomes de campos e os valores como valores dos campos de +diferentes tipos. Considere, por exemplo, um registro descrevendo um livro, em +JSON ou Python: @@ -329,17 +407,24 @@ Algumas vezes os dicionários de Python são usados como registros, as chaves in "pagecount": 478} ---- -Antes de Python 3.8, não havia uma boa maneira de anotar um registro como esse, pois os tipos de mapeamento que vimos na <> limitam os valores a um mesmo tipo. +Antes de Python 3.8, não havia uma boa maneira de anotar um registro como esse, +pois os tipos de mapeamento que vimos na <> limitam os valores +a um mesmo tipo. Aqui estão duas tentativas ruins de anotar um registro como o objeto JSON acima: -`Dict[str, Any]`:: -Os valores podem ser de qualquer tipo. +`dict[str, Any]`:: +As chaves são `str` mas os valores podem ser de qualquer tipo. -`Dict[str, Union[str, int, List[str]]]`:: -Difícil de ler, e não preserva a relação entre os nomes dos campos e seus respectivos tipos: `title` deve ser uma `str`, ele não pode ser um `int` ou uma `List[str]`. +`dict[str, str|int|list[str]]`:: +Difícil de ler, e não preserva a relação entre os nomes dos campos e seus +respectivos tipos: `title` deve ser uma `str`, ele não pode ser um `int` ou uma +`List[str]`. -A https://fpy.li/pep589[PEP 589—TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys (_TypedDict: Dicas de Tipo para Dicionários com um Conjunto Fixo de Chaves_)] enfrenta esse problema. O <> mostra um `TypedDict` simples. +A https://fpy.li/pep589[_PEP 589—TypedDict: Type Hints for Dictionaries with a +Fixed Set of Keys_] (TypedDict: dicas de tipo para dicionários com um conjunto +fixo de chaves_) resolve este problema. O <> mostra um +`TypedDict` simples. [[bookdict_ex]] ._books.py_: a definição de `BookDict` @@ -349,26 +434,33 @@ A https://fpy.li/pep589[PEP 589—TypedDict: Type Hints for Dictionaries with a include::../code/15-more-types/typeddict/books.py[tags=BOOKDICT] ---- ==== -À primeira vista, `typing.TypedDict` pode parecer uma fábrica de classes de dados, similar a `typing.NamedTuple`—tratada no <>. -A similaridade sintática é enganosa. `TypedDict` é muito diferente. -Ele existe apenas para o benefício de checadores de tipos, e não tem qualquer efeito durante a execução. +À primeira vista, `typing.TypedDict` pode parecer uma fábrica de classes de +dados, similar a `typing.NamedTuple`—tratada no <>. + +A similaridade sintática é enganosa. `TypedDict` é muito diferente. Ele existe +apenas para orientar um checador de tipos, e não tem qualquer efeito +durante a execução. `TypedDict` fornece duas coisas: -* Uma sintaxe similar à de classe para anotar uma `dict` com dicas de tipo para os valores de cada "campo". -* Um construtor que informa ao checador de tipos para esperar um `dict` com chaves e valores como especificados. +* Uma sintaxe similar à de classe para anotar um `dict` com dicas de tipo para +os valores de cada campo identificado por um chaves. +* Um construtor que informa que o checador de tipos +deve esperar um `dict` com chaves e valores como especificados. Durante a execução, um construtor de `TypedDict` como `BookDict` é um placebo: -ele tem o mesmo efeito de uma chamada ao construtor de `dict` com os mesmos argumentos. +ele tem o mesmo efeito de uma chamada ao construtor de `dict` com os mesmos +argumentos. O fato de `BookDict` criar um `dict` simples também significa que: -* Os "campos" na definiçao da pseudoclasse não criam atributos de instância. +* Os "campos" na definição da pseudoclasse não criam atributos de instância. * Não é possível escrever inicializadores com valores default para os "campos". -* Definições de métodos não são permitidas. +* Não é permitido definir métodos. -Vamos explorar o comportamento de um `BookDict` durante a execução (no <>). +Vamos explorar o comportamento de um `BookDict` durante a execução (no +<>). [[bookdict_first_use_ex]] .Usando um `BookDict`, mas não exatamente como planejado @@ -397,7 +489,7 @@ AttributeError: 'dict' object has no attribute 'title' ---- ==== <1> É possível invocar `BookDict` como um construtor de `dict`, com argumentos nomeados, ou passando um argumento `dict`—incluindo um literal `dict`. -<2> Oops...Esqueci que `authors` deve ser uma lista. Mas tipagem gradual significa que não há checagem de tipo durante a execução. +<2> Ops... esqueci que `authors` deve ser uma lista. Mas não há checagem de tipos estáticos durante a execução. <3> O resultado da chamada a `BookDict` é um `dict` simples... <4> ...assim não é possível ler os campos usando a notação `objeto.campo`. <5> As dicas de tipo estão em `+BookDict.__annotations__+`, e não em `pp`. @@ -408,7 +500,7 @@ As fábricas de classes do <>, por outro lado, são úteis mesmo se você não usar um checador de tipos, porque durante a execução elas geram uma classe customizada que pode ser instanciada. Elas também fornecem vários métodos ou funções úteis, -listadas na <> (<>). +listadas na <>. O <> cria um `BookDict` válido e tenta executar algumas operações com ele. A seguir, o <> mostra como `TypedDict` permite que o Mypy encontre erros. @@ -422,17 +514,27 @@ include::../code/15-more-types/typeddict/demo_books.py[] ---- ==== <1> Lembre-se de adicionar o tipo devolvido, assim o Mypy não ignora a função. -<2> Este é um `BookDict` válido: todas as chaves estão presentes, com valores do tipo correto. -<3> O Mypy vai inferir o tipo de `authors` a partir da anotação na chave `'authors'` em `BookDict`. -<4> `typing.TYPE_CHECKING` só é `True` quando os tipos no programa estão sendo checados. Durante a execução ele é sempre falso. -<5> O `if` anterior evita que `reveal_type(authors)` seja chamado durante a execução. -`reveal_type` não é uma função de Python disponível durante a execução, mas sim um instrumento de depuração fornecido pelo Mypy. -Por isso não há um `import` para ela. -Veja sua saída no <>. -<6> As últimas três linhas da função `demo` são ilegais. -Elas vão causar mensagens de erro no <>. - -Verificando a tipagem em _demo_books.py_, do <>, obtemos o <>. + +<2> Este é um `BookDict` válido: todas as chaves estão presentes, com valores do +tipo correto. + +<3> O Mypy vai inferir o tipo de `authors` a partir da anotação na chave +`'authors'` em `BookDict`. + + +<4> `typing.TYPE_CHECKING` só é `True` quando os tipos no programa estão sendo +checados. Durante a execução ele é sempre falso. + +<5> O `if` anterior evita que `reveal_type(authors)` seja chamado durante a +execução. `reveal_type` não é uma função de Python disponível durante a +execução, mas sim um instrumento de depuração fornecido pelo Mypy. Por isso não +há um `import` para ela. Veja sua saída no <>. + +<6> As últimas três linhas da função `demo` são ilegais. Elas vão disparar +mensagens de erro no <>. + +Verificando a tipagem em _demo_books.py_, do <>, obtemos o +<>. [[bookdict_demo_check]] .Verificando os tipos em _demo_books.py_ @@ -440,25 +542,35 @@ Verificando a tipagem em _demo_books.py_, do <>, obtemos o < +demo_books.py:13: note: Revealed type is + 'built-ins.list[built-ins.str]' <1> demo_books.py:14: error: Incompatible types in assignment - (expression has type "str", variable has type "List[str]") <2> + (expression has type "str", + variable has type "List[str]") <2> demo_books.py:15: error: TypedDict "BookDict" has no key 'weight' <3> -demo_books.py:16: error: Key 'title' of TypedDict "BookDict" cannot be deleted <4> +demo_books.py:16: error: Key 'title' of TypedDict "BookDict" + cannot be deleted <4> Found 3 errors in 1 file (checked 1 source file) ---- ==== -[role="pagebreak-before less_space"] -<1> Essa observação é o resultado de `reveal_type(authors)`. -<2> O tipo da variável `authors` foi inferido a partir do tipo da expressão que a inicializou, `book['authors']`. -Você não pode atribuir uma `str` para uma variável do tipo `List[str]`. -Checadores de tipo em geral não permitem que o tipo de uma variável mude.footnote:[Em maio de 2020, o pytype ainda permite isso. -Mas seu https://fpy.li/15-6[FAQ] (EN) diz que tal operação será proibida no futuro. -Veja a pergunta "Why didn’t pytype catch that I changed the type of an annotated variable?" (_Por que o pytype não avisou quando eu mudei o tipo de uma variável anotada?_) no https://fpy.li/15-6[FAQ] (EN) do pytype.] -<3> Não é permitido atribuir a uma chave que não é parte da definição de `BookDict`. +<1> Esta observação é o resultado de `reveal_type(authors)`. + +<2> O tipo da variável `authors` foi inferido a partir do tipo da expressão que +a inicializou, `book['authors']`. Você não pode atribuir uma `str` para uma +variável do tipo `List[str]`. Checadores de tipo em geral não permitem que o +tipo de uma variável mude.footnote:[Em maio de 2020, o pytype ainda permite +isso. Mas seu https://fpy.li/15-6[FAQ] diz que tal operação será proibida no +futuro. Veja a pergunta _Why didn't pytype catch that I changed the type of an +annotated variable?_ (Por que o pytype não avisou quando eu mudei o tipo de uma +variável anotada?) no https://fpy.li/15-6[FAQ] do pytype.] + +<3> Não é permitido atribuir a uma chave que não é parte da definição de +`BookDict`. + <4> Não se pode apagar uma chave que é parte da definição de `BookDict`. -Vejamos agora `BookDict` sendo usado em assinaturas de função, para checar o tipo em chamadas de função. +Vejamos agora `BookDict` sendo usado em assinaturas de função, para checar o +tipo em chamadas de função. Imagine que você precisa gerar XML a partir de registros de livros como esse: @@ -473,7 +585,13 @@ Imagine que você precisa gerar XML a partir de registros de livros como esse: ---- -Se você estivesse escrevendo o código em MicroPython, para ser integrado a um pequeno microcontrolador, poderia escrever uma função parecida com a que aparece no <>.footnote:[Prefiro usar o pacote https://fpy.li/15-8[lxml] (EN) para gerar e interpretar XML: ele é fácil de começar a usar, completo e rápido. Infelizmente, nem o lxml nem o https://docs.python.org/pt-br/3/library/xml.etree.elementtree.html[_ElementTree_] do próprio Python cabem na RAM limitada de meu microcontrolador hipotético.] +Se você estivesse escrevendo o código em MicroPython, para ser integrado a um +pequeno microcontrolador, poderia escrever uma função parecida com o +<>.footnote:[Prefiro usar o pacote https://fpy.li/15-8[lxml] para +gerar e interpretar XML: ele é fácil de começar a usar, completo e rápido. +Infelizmente, nem o lxml nem o +https://fpy.li/7f[_ElementTree_] +do próprio Python cabem na RAM limitada de meu microcontrolador hipotético.] [[to_xml_ex]] ._books.py_: a função `to_xml` @@ -484,14 +602,23 @@ include::../code/15-more-types/typeddict/books.py[tags=TOXML] ---- ==== <1> O principal objetivo do exemplo: usar `BookDict` em uma assinatura de função. -<2> Se a coleção começa vazia, o Mypy não tem inferir o tipo dos elementos. -Por isso a anotação de tipo é necessária aqui.footnote:[A documentação do Mypy discute isso na seção https://fpy.li/15-11["Types of empty collections" (_Tipos de coleções vazias_)] (EN) da página https://fpy.li/15-10["Common issues and solutions" (_Problemas comuns e suas soluções_)] (EN).] -<3> O Mypy entende testes com `isinstance`, e trata `value` como uma `list` neste bloco. -<4> Quando usei `key == 'authors'` como condição do `if` que guarda esse bloco, -o Mypy encontrou um erro nessa linha: `"object" has no attribute "++__iter__++"` (_"object" não tem um atributo "+__iter__+"_ ), porque inferiu o tipo de `value` devolvido por `book.items()` como `object`, que não suporta o método `+__iter__+` exigido pela expressão geradora. -O teste com `isinstance` funciona porque garante que `value` é uma `list` nesse bloco. +<2> Se a coleção começa vazia, o Mypy não tem como inferir o tipo dos elementos. +Por isso a anotação de tipo é necessária aqui.footnote:[A documentação do Mypy discute isso na seção +https://fpy.li/15-11[_Types of empty collections_] (Tipos de coleções vazias) da página +https://fpy.li/15-10[_Common issues and solutions_] (Problemas comuns e soluções).] + +<3> O Mypy entende testes com `isinstance`, e trata `value` como uma `list` +neste bloco. -O <> mostra uma função que interpreta uma `str` JSON e devolve um `BookDict`. +<4> Quando usei `key == 'authors'` como condição do `if` que guarda este bloco, +o Mypy encontrou um erro nessa linha: `"object" has no attribute "++__iter__++"` +(_"object" não tem um atributo "+__iter__+"_ ), porque inferiu o tipo de `value` +devolvido por `book.items()` como `object`, que não suporta o método +`+__iter__+` exigido pela expressão geradora. O teste com `isinstance` funciona +porque garante que `value` é uma `list` neste bloco. + +O <> mostra uma função que interpreta uma `str` JSON e devolve +um `BookDict`. [[from_json_any_ex]] .books_any.py: a função `from_json` @@ -501,14 +628,22 @@ O <> mostra uma função que interpreta uma `str` JSON e devol include::../code/15-more-types/typeddict/books_any.py[tags=FROMJSON] ---- ==== -<1> O tipo devolvido por `json.loads()` é `Any`.footnote:[Brett Cannon, Guido van Rossum e outros vem discutindo como escrever dicas de tipo para `json.loads()` desde 2016, em -https://fpy.li/15-12[Mypy issue #182: Define a JSON type (_Definir um tipo JSON_)] (EN).] -<2> Posso devolver `whatever`—de tipo `Any`—porque `Any` é _consistente-com_ todos os tipos, incluindo o tipo declarado do valor devolvido, `BookDict`. -O segundo ponto do <> é muito importante de ter em mente: -O Mypy não vai apontar qualquer problema nesse código, mas durante a execução o valor em `whatever` pode não se adequar à estrutura de `BookDict`—na verdade, pode nem mesmo ser um `dict`! +<1> O tipo devolvido por `json.loads()` é `Any`.footnote:[Brett Cannon, Guido +van Rossum e outros vem discutindo como escrever dicas de tipo para +`json.loads()` desde 2016, em https://fpy.li/15-12[Mypy issue #182: Define a +JSON type (_Definir um tipo JSON_)].] + +<2> Posso devolver `whatever`—de tipo `Any`—porque `Any` é _consistente-com_ +todos os tipos, incluindo o tipo declarado do valor devolvido, `BookDict`. + +É muito importante de ter em mente segundo ponto do <>: +o Mypy não vai apontar qualquer problema neste código, mas durante a execução +o valor em `whatever` pode não se adequar à estrutura de `BookDict`—pode até +não ser um `dict`! -Se você rodar o Mypy com `--disallow-any-expr`, ele vai reclamar sobre as duas linhas no corpo de `from_json`: +Se você rodar o Mypy com `--disallow-any-expr`, ele vai reclamar sobre as duas +linhas no corpo de `from_json`: [source] ---- @@ -519,7 +654,8 @@ Found 2 errors in 1 file (checked 1 source file) ---- As linhas 30 e 31 mencionadas no trecho acima são o corpo da função `from_json`. -Podemos silenciar o erro de tipo acrescentando uma dica de tipo à inicialização da variável `whatever`, como no <>. +Podemos silenciar o erro de tipo acrescentando uma dica de tipo à inicialização +da variável `whatever`, como no <>. [[from_json_ex]] .books.py: a função `from_json` com uma anotação de variável @@ -529,17 +665,27 @@ Podemos silenciar o erro de tipo acrescentando uma dica de tipo à inicializaç include::../code/15-more-types/typeddict/books.py[tags=FROMJSON] ---- ==== -<1> `--disallow-any-expr` não gera erros quando uma expressão de tipo `Any` é imediatamente atribuída a uma variável com uma dica de tipo. + +<1> `--disallow-any-expr` não gera erros quando uma expressão de tipo `Any` é +imediatamente atribuída a uma variável com uma dica de tipo. + <2> Agora `whatever` é do tipo `BookDict`, o tipo declarado do valor devolvido. [WARNING] ==== -Não se deixe enganar por uma falsa sensação de tipagem segura com o <>! -Olhando o código estático, o checador de tipos não tem como prever se `json.loads()` irá devolver qualquer coisa parecida com um `BookDict`. + +Não se deixe enganar por uma falsa sensação de tipagem segura com o +<>! Olhando o código estático, o checador de tipos não tem como +prever se `json.loads()` irá devolver qualquer coisa parecida com um `BookDict`. Apenas a validação durante a execução pode garantir isso. + ==== -A checagem de tipos estática é incapaz de prevenir erros cm código inerentemente dinâmico, como `json.loads()`, que cria objetos Python de tipos diferentes durante a execução. O <>, o <> e o <> demonstram isso. +A checagem de tipos estática é incapaz de prevenir erros cm código inerentemente +dinâmico, como `json.loads()`, que cria objetos Python de tipos diferentes +durante a execução. O <>, o +<> e o <> demonstram +isso. [[bookdict_demo_not_book_ex]] .demo_not_book.py: `from_json` devolve um `BookDict` inválido, e `to_xml` o aceita @@ -552,11 +698,11 @@ include::../code/15-more-types/typeddict/demo_not_book.py[] <1> Essa linha não produz um `BookDict` válido—veja o conteúdo de `NOT_BOOK_JSON`. <2> Vamos deixar o Mypy revelar alguns tipos. <3> Isso não deve causar problemas: `print` consegue lidar com `object` e com qualquer outro tipo. -<4> `BookDict` não tem uma chave `'flavor'`, mas o fonte JSON tem...o que vai acontecer?? -<5> Lembre-se da assinatura: `def to_xml(book: BookDict) -> str:`. -<6> Como será a saída XML? +<4> `BookDict` não tem uma chave `'flavor'`, mas o fonte JSON tem...o que acontecerá? +<5> Lembre-se da assinatura: `to_xml(book: BookDict) {rt-arrow} str:` +<6> Como será a saída em XML? -Agora checamos _demo_not_book.py_ com o Mypy (no <>). +Agora checamos _demo_not_book.py_ com o Mypy: [[bookdict_demo_not_book_check]] .Relatório do Mypy para _demo_not_book.py_, reformatado por legibilidade @@ -574,11 +720,16 @@ demo_not_book.py:16: error: TypedDict "BookDict" has no key 'flavor' <3> Found 1 error in 1 file (checked 1 source file) ---- ==== -<1> O tipo revelado é o tipo nominal, não o conteúdo de `not_book` durante a execução. -<2> De novo, este é o tipo nominal de `not_book['authors']`, como definido em `BookDict`. Não o tipo durante a execução. -<3> Esse erro é para a linha `print(not_book['flavor'])`: essa chave não existe no tipo nominal. +<1> O tipo revelado é o tipo estático, não o conteúdo de `not_book` durante a execução. + +<2> De novo, este é o tipo estático de `not_book['authors']`, como definido em +`BookDict`. Não o tipo durante a execução. + +<3> Este erro é para a linha `print(not_book['flavor'])`: esta chave não existe +no tipo estático. -Agora vamos executar _demo_not_book.py_, mostrando o resultado no <>. +Agora vamos executar _demo_not_book.py_, mostrando o resultado no +<>. [[bookdict_demo_not_book_run]] .Resultado da execução de `demo_not_book.py` @@ -599,37 +750,53 @@ pistachio <2> <2> O valor de `not_book['flavor']`. <3> `to_xml` recebe um argumento `BookDict`, mas não há qualquer checagem durante a execução: entra lixo, sai lixo. -O <> mostra que _demo_not_book.py_ devolve bobagens, mas não há qualquer erro durante a execução. -Usar um `TypedDict` ao tratar dados em formato JSON não resultou em uma tipagem segura. +O <> mostra que _demo_not_book.py_ devolve bobagens, +mas não há qualquer erro durante a execução. Usar um `TypedDict` ao tratar dados +em formato JSON não resultou em uma tipagem segura. -Olhando o código de `to_xml` no <> através das lentes do _duck typing_, o argumento `book` deve fornecer um método `.items()` que devolve um iterável de tuplas na forma `(chave, valor)`, onde: +Olhando o código de `to_xml` no <> do ponto de vista da tipagem pato, +o argumento `book` deve fornecer um método `.items()` que devolve um iterável de +tuplas na forma `(chave, valor)`, onde: * `chave` deve ter um método `.upper()` -* `valor` pode ser qualquer coisa +* `valor` pode ser qualquer coisa. -A conclusão desta demonstração: quando estamos lidando com dados de estrutura dinâmica, tal como JSON ou XML, `TypedDict` não é, de forma alguma, um substituto para a validaçào de dados durante a execução. Para isso, use o https://fpy.li/15-5[_pydantic_] (EN). +A conclusão desta demonstração: quando estamos lidando com dados de estrutura +dinâmica, tal como JSON ou XML, `TypedDict` não é, de forma alguma, um +substituto para a validaçào de dados durante a execução. Para isso, use o +https://fpy.li/15-5[_pydantic_]. -`TypedDict` tem mais recursos, incluindo suporte a chaves opcionais, uma forma limitada de herança e uma sintaxe de declaração alternativa. Para saber mais sobre ele, revise a -https://fpy.li/pep589[PEP 589—TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys (_TypedDict: Dicas de Tipo para Dicionários com um Conjunto Fixo de Chaves_)] (EN). +`TypedDict` tem mais recursos, incluindo suporte a chaves opcionais, uma forma +limitada de herança e uma sintaxe de declaração alternativa. Para saber mais +sobre ele, estude a +https://fpy.li/pep589[_PEP 589—TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys_] +(TypedDict: dicas de tipo para dicionários com um conjunto fixo de chaves). -Vamos agora voltar nossas atenções para uma função que é melhor evitar, mas que algumas vezes é inevitável: `typing.cast`.((("", startref="typeddict15")))((("", startref="typeddict15"))) +Vamos agora voltar nossa atenção para uma função que é melhor evitar, mas que +algumas vezes é inevitável: `typing.cast`.((("", startref="typeddict15")))((("", +startref="typeddict15"))) [[type_casting_sec]] -=== Coerção de Tipo +=== Coerção de tipo (_type casting_) -Nenhum((("gradual type system", "type casting", id="GTStypecast15")))((("type casting", id="typecast15"))) sistema de tipos é perfeito, nem tampouco os checadores estáticos de tipo, as dicas de tipo no projeto _typeshed_ ou as dicas de tipo em pacotes de terceiros que as oferecem. +Nenhum((("gradual type system", "type casting", id="GTStypecast15")))((("type casting", +id="typecast15"))) sistema de tipos é perfeito, nem tampouco os +checadores estáticos de tipo, as dicas de tipo no projeto _typeshed_ ou as dicas +de tipo em pacotes de terceiros, quando existem. -A função especial `typing.cast()` fornece uma forma de lidar com defeitos ou incorreções nas dicas de tipo em código que não podemos consertar. -A https://fpy.li/15-14[documentação do Mypy 0.930] (EN) explica: +A função especial `typing.cast()` é uma forma de lidar com defeitos ou +incorreções nas dicas de tipo em código que não podemos consertar. A +https://fpy.li/15-14[documentação do Mypy 0.930] explica (tradução nossa): [quote] ____ -Coerções são usadas para silenciar avisos espúrios do checador de tipos, e dão uma ajuda ao checador quando ele não consegue entender direito o que está acontecendo. +Coerções são usadas para silenciar avisos espúrios do checador de tipos, +e ajudam o checador quando ele não consegue entender o que está acontecendo. ____ -Durante a execução, `typing.cast` não faz absolutamente nada. Essa é sua -https://fpy.li/15-15[implementação]: +Durante a execução, `typing.cast` não faz absolutamente nada. +Esta é sua https://fpy.li/15-15[implementação]: [source, python] ---- @@ -643,22 +810,39 @@ def cast(typ, val): return val ---- -A PEP 484 exige que os checadores de tipos "acreditem cegamente" em `cast`. -A https://fpy.li/15-16[seção "Casts" (_Coerções_) da PEP 484] mostra um exemplo onde o checador precisa da orientação de `cast`: +A docstring diz: "Coage um valor para um tipo. +Isto devolve o valor inalterado. +Para o checador de tipos, isto sinaliza que o valor +devolvido tem o tipo designado, mas na execução +não fazemos nenhuma checagem (queremos que isto seja +tão rápido quanto possível)". + +A PEP 484 exige que os checadores de tipos "acreditem cegamente" em `cast`. A +https://fpy.li/15-16[seção "Casts" (_Coerções_) da PEP 484] mostra um exemplo +onde o checador precisa da orientação de `cast`: [source, python] ---- include::../code/15-more-types/cast/find.py[tags=CAST] ---- -A chamada `next()` na expressão geradora vai devolver ou o índice de um item `str` ou gerar `StopIteration`. Assim, `find_first_str` vai sempre devolver uma `str` se não for gerada uma exceção, e `str` é o tipo declarado do valor devolvido. +A chamada `next()` na expressão geradora vai devolver o índice de um item +`str` ou levantar `StopIteration`. Assim, `find_first_str` vai sempre devolver uma +`str` se não for gerada uma exceção, e `str` é o tipo declarado do valor +devolvido. -Mas se a última linha for apenas `return a[index]`, o Mypy inferiria o tipo devolvido como `object`, porque o argumento `a` é declarado como `list[object]`. -Então `cast()` é necessário para guiar o Mypy.footnote:[O uso de `enumerate` no exemplo serve para confundir intencionalmente o checador de tipos. Uma implementação mais simples, produzindo strings diretamente, sem passar pelo índice de `enumerate`, seria corretamente analisada pelo Mypy, e o `cast()` não seria necessário.] +Mas se a última linha for apenas `return a[index]`, o Mypy inferiria o tipo +devolvido como `object`, porque o argumento `a` é declarado como `list[object]`. +Então `cast()` é necessário para orientar o Mypy.footnote:[O uso de `enumerate` no +exemplo serve para confundir intencionalmente o checador de tipos. Uma +implementação mais simples, produzindo strings diretamente, sem passar pelo +índice de `enumerate`, seria corretamente analisada pelo Mypy, e o `cast()` não +seria necessário.] -Aqui está outro exemplo com `cast`, desta vez para corrigir uma dica de tipo desatualizada na biblioteca padrão de Python. -No <>, criei um objeto _asyncio_ , `Server`, e queria obter o endereço que o servidor estava ouvindo. -Escrevi essa linha de código: +Aqui está outro exemplo com `cast`, desta vez para corrigir uma dica de tipo +desatualizada na biblioteca padrão de Python. No <> do <>, criei +um objeto `asyncio.Server`, e queria obter o endereço onde o servidor está +ouvindo (aceitando conexões). Escrevi esta linha de código: [source, python] ---- @@ -671,13 +855,18 @@ Mas o Mypy informou o seguinte erro: Value of type "Optional[List[socket]]" is not indexable ---- -A dica de tipo para `Server.sockets` no _typeshed_, em maio de 2021, é válida para Python 3.6, onde o atributo `sockets` podia ser `None`. -Mas no Python 3.7, `sockets` se tornou uma propriedade, com um _getter_ que sempre devolve uma `list`—que pode ser vazia, se o servidor não tiver um _socket_. -E desde o Python 3.8, esse _getter_ devolve uma `tuple` (usada como uma sequência imutável). +A dica de tipo para `Server.sockets` no _typeshed_, em maio de 2021, é válida +para Python 3.6, onde o atributo `sockets` podia ser `None`. Mas no Python 3.7, +`sockets` se tornou uma propriedade, com um _getter_ que sempre devolve uma +`list`—que pode ser vazia, se o servidor não tiver um _socket_. E desde o Python +3.8, esse _getter_ devolve uma `tuple` (usada como uma sequência imutável). -Já que não posso consertar o _typeshed_ nesse instante,footnote:[Relatei o problema em -https://fpy.li/15-17[issue #5535] no _typeshed_, -"Dica de tipo errada para o atributo `sockets` em asyncio.base_events.Server sockets attribute.", e ele foi rapidamente resolvido por Sebastian Rittau. Mas decidi manter o exemplo, pois ele ilustra um caso de uso comum para `cast`, e o `cast` que escrevi é inofensivo.] acrescentei um `cast`, assim: +Já que não posso consertar o _typeshed_ nesse instante,footnote:[Relatei o +problema em https://fpy.li/15-17[issue #5535] no _typeshed_, "Dica de tipo +errada para o atributo `sockets` em asyncio.base_events.Server sockets +attribute.", e ele foi rapidamente resolvido por Sebastian Rittau. Mas decidi +manter o exemplo, pois ele ilustra um caso de uso comum para `cast`, e o `cast` +que escrevi é inofensivo.] acrescentei um `cast`, assim: [source, python] ---- @@ -688,23 +877,36 @@ include::../code/15-more-types/cast/tcp_echo.py[tags=CAST_IMPORTS] include::../code/15-more-types/cast/tcp_echo.py[tags=CAST_USE] ---- -//// -PROD: Whitespace at page end, and wrong indenation after page break -//// - -Usar `cast` nesse caso exigiu algumas horas para entender o problema e ler o código-fonte de _asyncio_, para encontrar o tipo correto para _sockets_: -a classe `TransportSocket` do módulo não-documentado `asyncio.trsock`. -Também precisei adicionar duas instruções `import` e mais uma linha de código para melhorar a legibilidade.footnote:[Para ser franco, originalmente eu anexei um comentário `# type: ignore` às linhas com +`server.sockets[0]`+ porque, após pesquisar um pouco, encontrei linhas similares na https://docs.python.org/pt-br/3/library/asyncio-stream.html#tcp-echo-server-using-streams[documentação] do _asyncio_ e em um https://fpy.li/15-19[caso de teste] (EN), e aí comecei a suspeitar que o problema não estava em meu código.] Mas agora o código está mais seguro. +Usar `cast` nesse caso exigiu algumas horas para entender o problema e ler o +código-fonte de _asyncio_, para encontrar o tipo correto para _sockets_: a +classe `TransportSocket` do módulo não-documentado `asyncio.trsock`. Também +precisei adicionar duas instruções `import` e mais uma linha de código para +melhorar a legibilidade.footnote:[Na realidade, inicialmente coloquei um +comentário `# type: ignore` às linhas com `+server.sockets[0]+` porque, após +pesquisar um pouco, encontrei linhas similares na +https://fpy.li/7g[documentação] +do _asyncio_ e em um https://fpy.li/15-19[caso de teste], e aí comecei a +suspeitar que o problema não estava no meu código.] Mas agora o código está mais +seguro. -O leitor atento pode ser notado que `sockets[0]` poderia gerar um `IndexError` +A leitora atenta pode ter notado que `sockets[0]` poderia gerar um `IndexError` se `sockets` estiver vazio. -Entretanto, até onde entendo o `asyncio`, isso não pode acontecer no <>, pois no momento em que leio o atributo `sockets`, o `server` já está pronto para aceitar conexões , portanto o atributo não estará vazio. E, de qualquer forma, `IndexError` ocorre durante a execução. O Mypy não consegue localizar esse problema nem mesmo em um caso trivial como `print([][0])`. +Entretanto, até onde entendo o _asyncio_, isso não pode acontecer no +<> do <>, pois no momento em que leio o atributo `sockets`, o +`server` já está pronto para aceitar conexões , portanto o atributo não estará +vazio. E, de qualquer forma, `IndexError` ocorre durante a execução. O Mypy não +consegue localizar esse problema nem mesmo em um caso trivial como +`print([][0])`. [WARNING] ==== -Não fique muito confortável usando `cast` para silenciar o Mypy, porque normalmente o Mypy está certo quando aponta um erro. -Se você estiver usando `cast` com frequência, isso é um https://fpy.li/15-20[code smell (_cheiro no código_)] (EN). -Sua equipe pode estar fazendo um mau uso das dicas de tipo, ou sua base de código pode ter dependências de baixa qualidaade. + +Não se acostume a usar `cast` para silenciar o Mypy toda hora, porque +normalmente o Mypy está certo quando aponta um erro. Se você estiver aplicando +`cast` com frequência, isso é um https://fpy.li/15-20[_code smell_] +(cheiro no código). Sua equipe pode estar fazendo um mau uso das dicas de tipo, ou sua +base de código pode ter dependências de baixa qualidade. + ==== Apesar de suas desvantagens, há usos válidos para `cast`. @@ -712,28 +914,41 @@ Eis algo que Guido van Rossum escreveu sobre isso: [quote] ____ -O que está errado com uma chamada a `cast()` ou um comentário `# type: ignore` ocasionais?footnote:[https://fpy.li/15-21[Mensagem de 18 de maio de 2020] para a lista de e-mail typing-sig.] + +O que está errado com uma ocasional chamada a `cast()` ou um comentário +`# type: ignore`?footnote:[https://fpy.li/15-21[Mensagem de 18 de maio de 2020] +para a lista de e-mail typing-sig.] ____ -[role="pagebreak-before less_space"] -É insensato banir inteiramente o uso de `cast`, principalmente porque as alternativas para contornar esses problemas são piores: +É insensato banir inteiramente o uso de `cast`, principalmente porque as +alternativas para contornar esses problemas são piores: -* `# type: ignore` é menos informativo.footnote:[A sintaxe -+`# type: ignore[code]`+ permite especificar qual erro do Mypy está sendo silenciado, mas os códigos nem sempre são fáceis de interpretar. -Veja a página https://fpy.li/15-22["Error codes"] na documentação do Mypy.] -* Usar `Any` é contagioso: já que `Any` é _consistente-com_ todos os tipos, seu abuso pode produzir efeitos em cascata através da inferência de tipo, minando a capacidade do checador de tipos para detectar erros em outras partes do código. +* `# type: ignore` é menos informativo.footnote:[A sintaxe `+# type: +ignore[code]+` permite especificar qual erro do Mypy está sendo silenciado, +mas os códigos nem sempre são fáceis de interpretar. Veja a página +https://fpy.li/15-22[_Error codes_] na documentação do Mypy.] + +* Usar `Any` é contagioso: já que `Any` é _consistente-com_ todos os tipos, seu +abuso pode produzir efeitos em cascata através da inferência de tipo, minando a +capacidade do checador de tipos para detectar erros em outras partes do código. Claro, nem todos os contratempos de tipagem podem ser resolvidos com `cast`. -Algumas vezes precisamos de `# type: ignore`, do `Any` ocasional, ou mesmo deixar uma função sem dicas de tipo. +Algumas vezes precisamos de `# type: ignore`, do `Any` aqui ou ali, ou mesmo +deixar uma função sem dicas de tipo. -A seguir, vamos falar sobre o uso de anotações durante a execução.((("", startref="typecast15")))((("", startref="GTStypecast15"))) +A seguir, vamos falar sobre o uso de anotações durante a execução.((("", +startref="typecast15")))((("", startref="GTStypecast15"))) [[runtime_annot_sec]] === Lendo dicas de tipo durante a execução -Durante((("gradual type system", "reading hints at runtime", id="GTSruntime15"))) a importação, Python lê as dicas de tipo em funções, classes e módulos, e as armazena em atributos chamados `+__annotations__+`. -Considere, por exemplo, a função `clip` function no <>.footnote:[Não vou entrar nos detalhes da implementação de `clip`, mas se você tiver curiosidade, pode ler o módulo completo em +Durante((("gradual type system", "reading hints at runtime", +id="GTSruntime15"))) a importação, Python lê as dicas de tipo em funções, +classes e módulos, e as armazena em atributos chamados `+__annotations__+`. +Considere, por exemplo, a função `clip` no +<>.footnote:[Não vou entrar nos detalhes da implementação de +`clip`, mas se você tiver curiosidade, pode ler o módulo completo em https://fpy.li/15-23[_clip_annot.py_].] @@ -755,7 +970,7 @@ As dicas de tipo são armazenadas em um `dict` no atributo `+__annotations__+` d {'text': , 'max_len': , 'return': } ---- -A chave `'return'` está mapeada para a dica do tipo devolvido após o símbolo `->` no <>. +A chave `'return'` está mapeada para a dica do tipo devolvido após o símbolo {rt-arrow} no <>. Observe que as anotações são avaliadas pelo interpretador no momento da importação, ao mesmo tempo em que os valores default dos parâmetros são avaliados. Por isso os valores nas anotações são as classes Python `str` e `int`, @@ -769,18 +984,25 @@ mas isso pode mudar se a https://fpy.li/pep563[PEP 563] ou a https://fpy.li/pep6 O aumento do uso de dicas de tipo gerou dois problemas: -* Importar módulos usa mais CPU e memória quando são usadas muitas dicas de tipo. -* Referências a tipos ainda não definidos exigem o uso de strings em vez do tipos reais. +* Importar módulos usa mais CPU e memória quando são há dicas de tipo. +* Referências a tipos ainda não definidos exigem o uso de strings em vez dos tipos reais. + +As duas questões são relevantes. A primeira pelo que acabamos de ver: anotações +são avaliadas pelo interpretador durante a importação e armazenadas no atributo +`+__annotations__+`. Quando uma empresa tem milhares de servidores importanto +arquivos Python, o custo pode ser significativo, mesmo considerando que a +importação de cada módulo só acontece no início do processo. -As duas questões são relevantes. -A primeira pelo que acabamos de ver: anotações são avaliadas pelo interpretador durante a importação e armazenadas no atributo `+__annotations__+`. Vamos nos concentrar agora no segundo problema. -Armazenar anotações((("forward reference problem"))) como string é necessário algumas vezes, por causa do problema da "referência adiantada" (_forward reference_): quando uma dica de tipo precisa se referir a uma classe definida mais adiante no mesmo módulo. -Entretanto uma manifestação comum desse problema no código-fonte não se parece de forma alguma com uma referência adiantada: -quando um método devolve um novo objeto da mesma classe. -Já que o objeto classe não está definido até Python terminar a avaliação do corpo da classe, as dicas de tipo precisam usar o nome da classe como string. -Eis um exemplo: +Armazenar anotações((("forward reference problem"))) como string é necessário +algumas vezes, por causa do problema da referência futura (_forward +reference_): quando uma dica de tipo precisa se referir a uma classe definida +mais adiante no mesmo módulo. Entretanto uma manifestação comum desse problema +no código-fonte não se parece de forma alguma com uma referência futura: +quando um método devolve um novo objeto da mesma classe. Já que o objeto classe +não está definido até Python terminar a avaliação do corpo da classe, as dicas +de tipo precisam usar o nome da classe como string. Eis um exemplo: [source, python] ---- @@ -790,41 +1012,62 @@ class Rectangle: return Rectangle(width=self.width * factor) ---- -Escrever dicas de tipo com referências adiantadas como strings é a prática padrão e exigida no Python 3.10. Os checadores de tipos estáticos foram projetados desde o início para lidar com esse problema. +Escrever dicas de tipo com referências futuras como strings é a prática +padrão e exigida no Python 3.10. Os checadores de tipos estáticos foram +projetados desde o início para lidar com esse problema. -Mas durante a execução, se você escrever código para ler a anotação `return` de `stretch`, vai receber a string `'Rectangle'` em vez de uma referência ao tipo real, a classe `Rectangle`. -E aí seu código precisa descobrir o que aquela string significa. +Mas durante a execução, se você escrever código para ler a anotação `return` de +`stretch`, vai receber a string `'Rectangle'` em vez de uma referência ao tipo +real, a classe `Rectangle`. E aí seu código precisa descobrir o que aquela +string significa. -O módulo `typing` inclui três funções e uma classe categorizadas -https://docs.python.org/pt-br/3/library/typing.html#introspection-helpers[Introspection helpers (Auxiliares de introspecção)], -a mais importantes delas sendo `typing.get_type_hints`. -Parte de sua documentação afirma: +O módulo `typing` inclui três funções e uma classe categorizadas como +https://fpy.li/7h[Auxiliares de introspecção], +sendo `typing.get_type_hints` a mais importante delas. Parte de sua documentação afirma: `get_type_hints(obj, globals=None, locals=None, include_extras=False)`:: - [...] Isso é muitas vezes igual a `+obj.__annotations__+`. Além disso, referências adiantadas codificadas como strings literais são tratadas por sua avaliação nos espaços de nomes `globals` e `locals`. [...] + [...] Isso é muitas vezes igual a `+obj.__annotations__+`. + Além disso, referências futuras codificadas como strings + literais são tratadas por sua avaliação nos espaços de nomes + `globals` e `locals`. [...] [WARNING] ==== -Desde o Python 3.10, a nova função https://fpy.li/15-25[`inspect.get_annotations(…)`] deve ser usada, em vez de pass:[typing.​get_​type_​hints]. -Entretanto, alguns leitores podem ainda não estar trabalhando com Python 3.10, então usarei a pass:[typing.​get_​type_​hints] nos exemplos, pois essa função está disponível desde a adição do módulo `typing`, no Python 3.5. + +Desde o Python 3.10, a nova função +https://fpy.li/15-25[`inspect.get_annotations(…)`] deve ser usada, em vez de +`get_type_hints`. Entretanto, alguns leitores podem ainda não estar trabalhando +com Python 3.10, então usarei `get_type_hints` nos exemplos, pois essa função +está disponível desde a inclusão do módulo `typing`, no Python 3.5. + ==== -A https://fpy.li/pep563[PEP 563—Postponed Evaluation of Annotations (_Avaliação Adiada de Anotações)] (EN) foi aprovada para tornar desnecessário escrever anotações como strings, e para reduzir o custo das dicas de tipo durante a execução. -A ideia principal está descrita nessas duas sentenças do https://fpy.li/15-26["Abstract"] (EN): +A https://fpy.li/pep563[_PEP 563—Postponed Evaluation of Annotations_] +(Avaliação Adiada de Anotações) foi aprovada para tornar desnecessário escrever +anotações como strings, e para reduzir o custo das dicas de tipo durante a +execução. A ideia principal está descrita nessas duas sentenças do +https://fpy.li/15-26[_Abstract_]: [quote] ____ -Esta PEP propõe modificar as anotações de funções e de variáveis, de forma que elas não mais sejam avaliadas no momento da definição da função. Em vez disso, elas são preservadas em +__annotations__+ na forma de strings.. + +Esta PEP propõe modificar as anotações de funções e de variáveis, de forma que +elas não mais sejam avaliadas no momento da definição da função. Em vez disso, +elas são preservadas em +__annotations__+ na forma de strings. + ____ -A partir de Python 3.7, é assim que anotações são tratadas em qualquer módulo que comece com a seguinte instrução `import`: +A partir de Python 3.7, é assim que anotações são tratadas em qualquer módulo +que comece com a seguinte instrução `import`: [source, python] ---- from __future__ import annotations ---- -Para demonstrar seu efeito, coloquei a mesma função `clip` do <> em um módulo _clip_annot_post.py_ com aquela linha de importação `+__future__+` no início. +Para demonstrar seu efeito, coloquei a mesma função `clip` do <> +em um módulo _clip_annot_post.py_ com aquela linha de importação `+__future__+` +no início. No console, esse é o resultado de importar aquele módulo e ler as anotações de `clip`: @@ -835,9 +1078,11 @@ No console, esse é o resultado de importar aquele módulo e ler as anotações {'text': 'str', 'max_len': 'int', 'return': 'str'} ---- -Como se vê, todas as dicas de tipo são agora strings simples, apesar de não terem sido escritas como strings na definição de `clip` (no <>). +Como se vê, todas as dicas de tipo são agora strings simples, apesar de não +terem sido escritas como strings na definição de `clip` (no <>). -A função `typing.get_type_hints` consegue resolver muitas dicas de tipo, incluindo essas de `clip`: +A função `typing.get_type_hints` consegue resolver muitas dicas de tipo, +incluindo essas de `clip`: [source, python] ---- @@ -847,39 +1092,64 @@ A função `typing.get_type_hints` consegue resolver muitas dicas de tipo, inclu {'text': , 'max_len': , 'return': } ---- -A chamada a `get_type_hints` nos dá os tipos reais — mesmo em alguns casos onde a dica de tipo original foi escrita como uma string. -Essa é a maneira recomendada de ler dicas de tipo durante a execução. +A chamada a `get_type_hints` nos dá os tipos reais—mesmo em alguns casos onde +a dica de tipo original foi escrita como uma string. Esta é a maneira +recomendada de ler dicas de tipo durante a execução. -O comportamento prescrito na PEP 563 estava previsto para se tornar o default no Python -3.10, tornando a importação com `+__future__+` desnecessária. -Entretanto, os mantenedores da _FastAPI_ e do _pydantic_ soaram o alarme, essa mudança quebraria seu código, que se baseia em dicas de tipo durante a execução e não podem usar `get_type_hints` de forma confiável. +O comportamento prescrito na PEP 563 estava previsto para se tornar o default no +Python 3.10, tornando a importação com `+__future__+` desnecessária. Entretanto, +os mantenedores da _FastAPI_ e do _pydantic_ avisaram de que tal mudança +quebraria seu código, que se baseia em dicas de tipo durante a execução e não +podem usar `get_type_hints` de forma confiável. -Na discussão que se seguiu na lista de e-mail python-dev, Łukasz Langa—autor da PEP 563—descreveu algumas limitações daquela função: +Na discussão que se seguiu na lista de e-mail python-dev, Łukasz Langa—autor da +PEP 563—descreveu algumas limitações daquela função: [quote] ____ -[...] a verdade é que `typing.get_type_hints()` tem limites que tornam seu uso geral custoso durante a execução e, mais importante, insuficiente para resolver todos os tipos. O exemplo mais comum se refere a contextos não-globais nos quais tipos são gerados (isto é, classes aninhadas, classes dentro de funções, etc.). -Mas um dos principais exemplos de referências adiantadas, -classes com métodos aceitando ou devolvendo objetos de seu próprio tipo, também não é tratado de forma apropriada por `typing.get_type_hints()` se um gerador de classes for usado. -Há alguns truques que podemos usar para ligar os pontos mas, de uma forma geral, isso não é bom.footnote:[Mensagem -https://fpy.li/15-27["PEP 563 in light of PEP 649" (_PEP 563 à luz da PEP 649_)], publicado em 16 de abril de 2021.] + +[...] a verdade é que `typing.get_type_hints()` tem limites que tornam seu uso +geral custoso durante a execução e, mais importante, insuficiente para resolver +todos os tipos. O exemplo mais comum se refere a contextos não-globais nos quais +tipos são gerados (isto é, classes aninhadas, classes dentro de funções, etc.). +Mas um dos principais exemplos de referências futuras, classes com métodos +aceitando ou devolvendo objetos de seu próprio tipo, também não é tratado de +forma apropriada por `typing.get_type_hints()` se um gerador de classes for +usado. Há alguns truques que podemos usar para ligar os pontos mas, de uma forma +geral, isso não é bom.footnote:[Mensagem https://fpy.li/15-27[_PEP 563 in light +of PEP 649_] (PEP 563 à luz da PEP 649), publicado em 16 de abril de 2021.] + ____ -O Steering Council de Python decidiu adiar a elevação da PEP 563 a comportamento padrão até Python 3.11 ou posterior, dando mais tempo aos desenvolvedores para criar uma solução para os problemas que a PEP 563 tentou resolver, sem quebrar o uso dissseminado das dicas de tipo durante a execução. -A https://fpy.li/pep649[PEP 649—Deferred Evaluation Of Annotations Using Descriptors (_Avaliação Adiada de Anotações Usando Descritores_)] (EN) está sendo considerada como uma possível solução, mas algum outro acordo ainda pode ser alcançado. +O Steering Council de Python decidiu adiar a elevação da PEP 563 a comportamento +padrão até Python 3.11 ou posterior, dando mais tempo aos desenvolvedores para +criar uma solução para os problemas que a PEP 563 tentou resolver, sem quebrar o +uso dissseminado das dicas de tipo durante a execução. A +https://fpy.li/pep649[_PEP 649—Deferred Evaluation Of Annotations Using Descriptors_] +(Avaliação adiada de anotações usando descritores_) está sendo +considerada como uma possível solução, mas algum outro acordo ainda pode ser +alcançado. -Resumindo: ler dicas de tipo durante a execução não é 100% confiável no Python 3.10 e provavelmente mudará em alguma futura versão. +Resumindo: ler dicas de tipo durante a execução não é 100% confiável no Python +3.10 e provavelmente mudará em alguma futura versão. [NOTE] ==== -Empresas usando Python em escala muito ampla desejam os benefícios da tipagem estática, -mas não querem pagar o preço da avaliação de dicas de tipo no momento da importação. -A checagem estática acontece nas estações de trabalho dos desenvolvedores e em servidores de integração contínua dedicados, -mas o carregamento de módulos acontece em uma frequência e um volume mais altos, -em servidores de produção, e esse custo não é desprezível em grande escala. -Isso cria uma tensão na comunidade Python, entre aqueles que querem as dicas de tipo armazenadas apenas como strings—para reduzir os custos de carregamento—versus aqueles que também querem usar as dicas de tipo durante a execução, como os criadores e os usuários do _pydantic_ e da _FastAPI_, para quem seria mais fácil acessar diretamente os tipos, -ao invés de precisarem analisar strings nas anotações, uma tarefa desafiadora. +Empresas usando Python em escala muito grande desejam os benefícios da tipagem +estática, mas não querem pagar o preço da avaliação de dicas de tipo no momento +da importação. A checagem estática acontece nas estações de trabalho dos +desenvolvedores e em servidores de integração contínua dedicados, mas o +carregamento de módulos acontece com uma frequência e um volume muito maiores, +em servidores de produção, e este custo não é desprezível em grande escala. + +Isto cria uma tensão na comunidade Python, entre aqueles que querem as dicas de +tipo armazenadas apenas como strings—para reduzir os custos de +carregamento—versus aqueles que também querem usar as dicas de tipo durante a +execução, como os criadores e os usuários do _pydantic_ e da _FastAPI_, para +quem seria mais fácil acessar diretamente os tipos, ao invés de precisarem +analisar strings nas anotações, uma tarefa complicada. + ==== ==== Lidando com o problema @@ -890,8 +1160,8 @@ Dada a instabilidade da situação atual, se você precisar ler anotações dura * Escreva uma função customizada própria, como um invólucro para pass:[in​spect​.get_annotations] ou `typing.get_type_hints`, e faça o restante de sua base de código chamar aquela função, de forma que mudanças futuras fiquem restritas a um único local. -Para demonstrar esse segundo ponto, aqui estão as primeiras linhas da classe `Checked`, definida no -<>, classe que estudaremos no <>: +Para demonstrar esse segundo ponto, aqui estão as primeiras linhas da classe `Checked`, +que estudaremos no <> do <>: [source, python] ---- @@ -907,9 +1177,9 @@ O método de `Checked._fields` evita que outras partes do módulo dependam diret [WARNING] ==== -Dadas as discussões correntes e as mudanças propostas para a inspeção de dicas de tipo durante a execução, a página da documentação oficial https://docs.python.org/pt-br/3.10/howto/annotations.html["Boas Práticas de Anotação"] é uma leitura obrigatória, e a página deve ser atualizada até o lançamento de Python 3.11. +Dadas as discussões correntes e as mudanças propostas para a inspeção de dicas de tipo durante a execução, a página da documentação oficial https://fpy.li/7j["Boas Práticas de Anotação"] é uma leitura obrigatória, e a página deve ser atualizada até o lançamento de Python 3.11. Aquele _how-to_ foi escrito por Larry Hastings, autor da -https://fpy.li/pep649[PEP 649—Deferred Evaluation Of Annotations Using Descriptors (_Avaliação Adiada de Anotações Usando Descritores_)] (EN), uma proposta alternativa para tratar os problemas gerados durante a execução pela https://fpy.li/pep563[PEP 563—Postponed Evaluation of Annotations (_Avaliação Adiada de Anotações)] (EN). +https://fpy.li/pep649[PEP 649—Deferred Evaluation Of Annotations Using Descriptors (_Avaliação Adiada de Anotações Usando Descritores_)], uma proposta alternativa para tratar os problemas gerados durante a execução pela https://fpy.li/pep563[PEP 563—Postponed Evaluation of Annotations (_Avaliação Adiada de Anotações)]. ==== As seções restantes desse capítulo cobrem tipos genéricos, começando pela forma de definir uma classe genérica, que pode ser parametrizada por seus usuários.((("", startref="GTSruntime15"))) @@ -918,7 +1188,9 @@ As seções restantes desse capítulo cobrem tipos genéricos, começando pela f [[impl_generic_class_sec]] === Implementando uma classe genérica -No((("gradual type system", "implementing generic classes", id="GTSgeneric15")))((("classes", "implementing generic classes", id="CAPgeneric15")))((("generic classes, implementing", id="genclasimp15"))) <>, definimos a ABC `Tombola`: uma interface para classes que funcionam como um recipiente para sorteio de bingo. A classe `LottoBlower` do <> é uma implementação concreta. +No((("gradual type system", "implementing generic classes", id="GTSgeneric15")))((("classes", "implementing generic classes", id="CAPgeneric15")))((("generic classes, implementing", id="genclasimp15"))) +<> do <> +definimos a ABC `Tombola`: uma interface para classes que funcionam como um recipiente para sorteio de bingo. A classe `LottoBlower` (<> do <>) é uma implementação concreta. Vamos agora estudar uma versão genérica de `LottoBlower`, usada da forma que aparece no <>. @@ -930,11 +1202,13 @@ Vamos agora estudar uma versão genérica de `LottoBlower`, usada da forma que a include::../code/15-more-types/lotto/generic_lotto_demo.py[tags=LOTTO_USE] ---- ==== -<1> Para instanciar uma classe genérica, passamos a ela um parâmetro de tipo concreto, como `int` aqui. +<1> Para instanciar uma classe genérica, +passamos a ela um parâmetro de tipo concreto, como `int` aqui. <2> O Mypy irá inferir corretamente que `first` é um `int`... <3> ... e que `remain` é uma `tuple` de inteiros. -Além disso, o Mypy aponta violações do tipo parametrizado com mensagens úteis, como ilustrado no <>. +Além disso, o Mypy aponta violações do tipo parametrizado com mensagens úteis, +como ilustrado no <>. [[ex_generic_lotto_errors]] .generic_lotto_errors.py: erros apontados pelo Mypy @@ -945,7 +1219,10 @@ include::../code/15-more-types/lotto/generic_lotto_errors.py[] ---- ==== <1> Na instanciação de `LottoBlower[int]`, o Mypy marca o `float`. -<2> Na chamada `.load('ABC')`, o Mypy explica porque uma `str` não serve: `+str.__iter__+` devolve um `Iterator[str]`, mas `LottoBlower[int]` exige um `Iterator[int]`. + +<2> Na chamada `.load('ABC')`, o Mypy explica porque uma `str` não serve: +`+str.__iter__+` devolve um `Iterator[str]`, mas `LottoBlower[int]` exige um +`Iterator[int]`. O <> é a implementação. @@ -959,25 +1236,42 @@ O <> é a implementação. include::../code/15-more-types/lotto/generic_lotto.py[] ---- ==== -<1> Declarações de classes genéricas muitas vezes usam herança múltipla, porque precisamos de uma subclasse de `Generic` para declarar os parâmetros de tipo formais—nesse caso, `T`. -<2> O argumento `items` em `+__init__+` é do tipo `Iterable[T]`, que se torna `Iterable[int]` quando uma instância é declarada como `LottoBlower[int]`. -<3> O método `load` é igualmente restrito. + +<1> Declarações de classes genéricas muitas vezes usam herança múltipla, porque +precisamos incluir a superclasse `Generic` para declarar os parâmetros de tipo +formais—neste caso, `T`. + +<2> O argumento `items` em `+__init__+` é do tipo `Iterable[T]`, que se torna +`Iterable[int]` quando uma instância é declarada como `LottoBlower[int]`. + +<3> O método `load` é igualmente anotado. + <4> O tipo do valor devolvido `T` agora se torna `int` em um `LottoBlower[int]`. + <5> Nenhuma variável de tipo aqui. + <6> Por fim, `T` define o tipo dos itens na `tuple` devolvida. [TIP] ==== -A seção https://docs.python.org/pt-br/3/library/typing.html#user-defined-generic-types["User-defined generic types" (_Tipos genéricos definidos pelo usuário_)] (EN), na documentação do módulo `typing`, é curta, inclui bons exemplos e fornece alguns detalhes que não menciono aqui. + +A seção +https://fpy.li/7k[_User-defined generic types_] +(Tipos genéricos definidos pelo usuário), na documentação do +módulo `typing`, é curta, inclui bons exemplos e fornece alguns detalhes que não +menciono aqui. + ==== -Agora que vimos como implementar um classe genérica, vamos definir a terminologia para falar sobre tipos genéricos. +Agora que vimos como implementar um classe genérica, vamos definir a +terminologia para falar sobre tipos genéricos. -[role="pagebreak-before less_space"] ==== Jargão básico para tipos genéricos -Aqui estão algumas definições que encontrei estudando genéricos:footnote:[Os termos são do livro clássico de Joshua Bloch, _Java Efetivo_, 3rd ed. (Alta Books). As definições e exemplos são meus.] +Aqui estão algumas definições que encontrei estudando genéricos:footnote:[Os +termos são do livro clássico de Joshua Bloch, _Effective Java_, 3rd ed. As +traduções, definições e exemplos são meus.] Tipo genérico:: Um tipo declarado com uma ou mais variáveis de tipo. + @@ -992,28 +1286,48 @@ Um((("parameterized types"))) tipo declarado com os parâmetros de tipo reais. + Exemplos: `LottoBlower[int]`, `abc.Mapping[str, float]` Parâmetro de tipo real:: -Os((("actual type parameters"))) tipos reais passados como parâmetros quando um tipo parametrizado é declarado. + +Os((("actual type parameters"))) tipos reais passados como parâmetros +quando um tipo parametrizado é declarado. + Exemplo: o `int` em `LottoBlower[int]` -O próximo tópico é sobre como tornar os tipos genéricos mais flexíveis, introduzindo os conceitos de covariância, contravariância e invariância.((("", startref="genclasimp15")))((("", startref="CAPgeneric15")))((("", startref="GTSgeneric15"))) +O próximo tópico é sobre como tornar os tipos genéricos mais flexíveis, +introduzindo os conceitos de covariância, contravariância e invariância.((("", +startref="genclasimp15")))((("", startref="CAPgeneric15")))((("", +startref="GTSgeneric15"))) [[variance_sec]] === Variância [NOTE] ==== -Dependendo((("gradual type system", "variance and", id="GTSvar15")))((("variance", "relevance of"))) de sua experiência com genéricos em outras linguagens, essa pode ser a parte mais difícil do livro. -O conceito de variância é abstrato, e uma apresentação rigorosa faria essa seção se parecer com páginas tiradas de um livro de matemática. -Na prática, a variância é mais relevante para autores de bibliotecas que querem suportar novos tipos de contêineres genéricos ou fornecer uma API baseada em _callbacks_. -Mesmo nesses casos, é possível evitar muita complexidade suportando apenas contêineres invariantes—que é quase só o que temos hoje na biblioteca padrão. -Então, em uma primeira leitura você pode pular toda essa seção, ou ler apenas as partes sobre tipos invariantes. +Dependendo((("gradual type system", "variance and", +id="GTSvar15")))((("variance", "relevance of"))) de sua experiência com +genéricos em outras linguagens, esta pode ser a seção mais difícil do livro. O +conceito de variância é abstrato, e uma apresentação rigorosa faria essa seção +se parecer com páginas de um livro de matemática. + +Na prática, a variância é mais relevante para autores de bibliotecas que querem +suportar novos tipos de coleções genéricas ou fornecer uma API baseada em +_callbacks_. Mesmo nestes casos, é possível evitar muita complexidade suportando +apenas coleções invariantes—que é o que temos hoje na biblioteca +padrão. Então, em uma primeira leitura você pode pular toda esta seção, ou ler +apenas as partes sobre tipos invariantes. + ==== -Já vimos o conceito de _variância_ na <>, aplicado a tipos genéricos `Callable` parametrizados. Aqui vamos expandir o conceito para abarcar tipo genéricos de coleções, usando uma analogia do "mundo real" para tornar mais concreto esse conceito abstrato. +Já vimos o conceito de _variância_ na <>, aplicado a +tipos genéricos `Callable` parametrizados. Aqui vamos expandir o conceito para +abarcar tipos genéricos de coleções, usando uma analogia do "mundo real" para +tornar mais concreto esse conceito abstrato. -Imagine uma cantina escolar que tenha como regra que apenas máquinas servindo sucos podem ser instaladas ali.footnote:[A primeira vez que vi a analogia da cantina para variância foi no prefácio de Erik Meijer para o livro _The Dart Programming Language_ ("A Linguagem de Programação Dart"), de Gilad Bracha (Addison-Wesley).] -Máquinas de bebida genéricas não são permitidas, pois podem servir refrigerantes, que foram banidos pela direção da escola.footnote:[Muito melhor que banir livros!] +Imagine uma cantina escolar que tenha como regra que apenas máquinas servindo +sucos podem ser instaladas.footnote:[A primeira vez que vi a analogia da +cantina para variância foi no prefácio de Erik Meijer para o livro _The Dart +Programming Language_ ("A Linguagem de Programação Dart"), de Gilad Bracha +(Addison-Wesley).] Máquinas de bebida genéricas não são permitidas, pois podem +servir refrigerantes, que foram banidos pela direção da escola.footnote:[Muito +melhor que banir livros!] ==== Uma máquina de bebida invariante @@ -1034,7 +1348,7 @@ include::../code/15-more-types/cafeteria/invariant.py[tags=BEVERAGE_TYPES] <3> `BeverageDispenser` é parametrizada pelo tipo de bebida. <4> `install` é uma função global do módulo. Sua dica de tipo faz valer a regra de que apenas máquinas de suco são aceitáveis. -Dadas as definições no <>, o seguinte código é legal: +Dadas as definições no <>, o seguinte código é válido: [source, python] @@ -1042,7 +1356,7 @@ Dadas as definições no <>, o seguinte código é include::../code/15-more-types/cafeteria/invariant.py[tags=INSTALL_JUICE_DISPENSER] ---- -Entretanto, isso não é legal: +Entretanto, isto não é válido: [source, python] ---- @@ -1051,7 +1365,7 @@ include::../code/15-more-types/cafeteria/invariant.py[tags=INSTALL_BEVERAGE_DISP Uma máquina que serve qualquer `Beverage` não é aceitável, pois a cantina exige uma máquina especializada em `Juice`. -De forma um tanto surpreendente, este código também é ilegal: +De forma um tanto surpreendente, este código também é inválido: [source, python] ---- @@ -1113,7 +1427,7 @@ Neste exemplo didático, vamos fazer algumas suposições e classificar o lixo e * `Refuse` (_Resíduo_) é o tipo mais geral de lixo. Todo lixo é resíduo. -* `Biodegradable` (_Biodegradável_) é um tipo de lixo que é decomposto por microrganismos ao longo do tempo. +* `Biodegradable` (_Biodegradável_) é um tipo de lixo decomposto por microrganismos ao longo do tempo. Parte do `Refuse` não é `Biodegradable`. * `Compostable` (_Compostável_) é um tipo específico de lixo `Biodegradable` que pode ser transformado de em fertilizante orgânico, @@ -1187,7 +1501,7 @@ class list(MutableSequence[_T], Generic[_T]): # etc... ---- -Veja que `_T` aparece entre os argumentos de `+__init__+`, `append` e `extend`, +Veja que `_T` aparece entre os parâmetros de `+__init__+`, `append` e `extend`, e como tipo devolvido por `pop`. Não há como tornar segura a tipagem dessa classe se ela for covariante ou contravariante em `_T`. @@ -1209,7 +1523,7 @@ Tipos genéricos covariantes seguem a relação de subtipo do tipo real dos par Contêineres imutáveis podem ser covariantes. Por exemplo, é assim que a classe `typing.FrozenSet` está -https://docs.python.org/pt-br/3.10/library/typing.html#typing.FrozenSet[documentada] como covariante com uma variável de tipo usando o nome convencional `T_co`: +https://fpy.li/7m[documentada] como covariante com uma variável de tipo usando o nome convencional `T_co`: [source, python] ---- @@ -1224,18 +1538,19 @@ Aplicando a notação `:>` a tipos parametrizados, temos: frozenset[float] :> frozenset[int] ---- -Iteradores são outro exemplo de genéricos covariantes: -eles não são coleções apenas para leitura como um `frozenset`, -mas apenas produzem saídas. -Qualquer código que espere um `abc.Iterator[float]` que produz números de ponto flutuante pode usar com segurança um `abc.Iterator[int]` que produz inteiros. -Tipos `Callable` são covariantes no tipo devolvido por uma razão similar. +Iteradores são outro exemplo de genéricos covariantes: eles não são coleções +apenas para leitura como um `frozenset`, mas apenas produzem itens sob demanda. +Qualquer código que espere um `abc.Iterator[float]` que produz números de ponto +flutuante pode usar com segurança um `abc.Iterator[int]` que produz inteiros. +Tipos `Callable` são covariantes no tipo devolvido pela mesma razão. [[contravariant_types_sec]] ===== Tipos contravariantes Dado `A :> B`, um tipo genérico `K` é contravariante se `K[A] <: K[B]`. -Tipos genéricos contravariantes revertem a relação de subtipo dos tipos reais dos parâmetros . +Tipos genéricos contravariantes revertem a relação de subtipo dos tipos reais +dos parâmetros. A classe `TrashCan` exemplifica isso: @@ -1245,54 +1560,72 @@ A classe `TrashCan` exemplifica isso: TrashCan[Refuse] <: TrashCan[Biodegradable] ---- -Um contêiner contravariante normalmente é uma estrutura de dados só para escrita, também conhecida como "coletor" ("sink"). -Não há exemplos de coleções desse tipo na biblioteca padrão, mas existem alguns tipos com parâmetros de tipo contravariantes. +Um contêiner contravariante normalmente é uma estrutura de dados só para +escrita, também conhecida como "coletor" (_sink_). Não há exemplos de coleções +deste tipo na biblioteca padrão, mas existem alguns tipos com parâmetros de tipo +contravariantes. -`Callable[[ParamType, …], ReturnType]` é contravariante nos tipos dos parâmetros, mas covariante no `ReturnType`, como vimos na <>. -Além disso, -https://fpy.li/15-32[`Generator`], -https://fpy.li/typecoro[`Coroutine`], e -https://fpy.li/15-33[`AsyncGenerator`] -têm um parâmetro de tipo contravariante. -O tipo `Generator` está descrito na <>; -`Coroutine` e `AsyncGenerator` são descritos no <>. +`Callable[[ParamType, …], ReturnType]` é contravariante nos tipos dos +parâmetros, mas covariante no `ReturnType`, como vimos na +<>. Além disso, https://fpy.li/15-32[`Generator`], +https://fpy.li/typecoro[`Coroutine`], e https://fpy.li/15-33[`AsyncGenerator`] +têm um parâmetro de tipo contravariante. O tipo `Generator` está descrito na +<>; `Coroutine` e `AsyncGenerator` são +descritos no <>. -Para efeito da presente discussão sobre variância, -o ponto principal é que parâmetros formais contravariantes definem o tipo dos argumentos usados para invocar ou enviar dados para o objeto, enquanto parâmetros formais covariantes definem os tipos de saídas produzidos pelo objeto—o tipo devolvido por uma função -ou produzido por um gerador. -Os significados de "enviar" e "produzir" são explicados na <>. +Para efeito da presente discussão sobre variância, o ponto principal é que +parâmetros formais contravariantes definem o tipo dos argumentos usados para +invocar ou enviar dados para o objeto, enquanto parâmetros formais covariantes +definem os tipos de saídas produzidos pelo objeto—o tipo devolvido por uma +função ou produzido por um gerador. Os significados precisos de "enviar" e +"produzir" são definidos na <>. -Dessas observações sobre saídas covariantes e entradas contravariantes podemos derivar algumas orientações úteis. +A partir destas observações sobre saídas covariantes e entradas contravariantes +podemos derivar algumas orientações úteis. [[variance_rules_sec]] ===== Regras gerais de variância -Por fim, aqui((("variance", "rules of thumb"))) estão algumas regras gerais a considerar quando estamos pensando sobre variância: +Por fim, aqui((("variance", "rules of thumb"))) estão algumas regras gerais a +considerar quando estamos pensando sobre variância: -* Se um parâmetro de tipo formal define um tipo para dados que saem de um objeto, ele pode ser covariante. +. Se um parâmetro de tipo formal define um tipo para dados que saem de um +objeto, ele pode ser covariante. -* Se um parâmetro de tipo formal define um tipo para dados que entram em um objeto, ele pode ser contravariante. +. Se um parâmetro de tipo formal define um tipo para dados que entram em um +objeto, ele pode ser contravariante. -* Se um parâmetro de tipo formal define um tipo para dados que saem de um objeto e o mesmo parâmetro define um tipo para dados que entram em um objeto, ele deve ser invariante. +. Se um parâmetro de tipo formal define um tipo para dados que saem de um objeto +e o mesmo parâmetro define um tipo para dados que entram em um objeto, ele deve +ser invariante. -* Na dúvida, use parâmetros de tipo formais invariantes. Não haverá prejuízo se no futuro precisar usar parâmetros de tipo covariantes ou contravariantes, pois nestes casos a tipagem é mais aberta e não quebrará códigos existentes. +. Na dúvida, use parâmetros de tipo formais invariantes. Não haverá prejuízo se +no futuro precisar usar parâmetros de tipo covariantes ou contravariantes, pois +nestes casos a tipagem ficará mais tolerante e não quebrará códigos existentes. -`Callable[[ParamType, …], ReturnType]` demonstra as regras #1 e #2: -O `ReturnType` é covariante, e cada `ParamType` é contravariante. +`Callable[[ParamType, …], ReturnType]` demonstra as regras 1 e 2: O +`ReturnType` é covariante, e cada `ParamType` é contravariante. -Por default, `TypeVar` cria parâmetros formais invariantes, e é assim que as coleções mutáveis na biblioteca padrão são anotadas. +Por default, `TypeVar` cria parâmetros formais invariantes, e é assim que as +coleções mutáveis na biblioteca padrão são anotadas. -Nossa discussão sobre variância continua na <>. +Veremos mais exemplos de variância em +<>. -A seguir, vamos ver como definir protocolos estáticos genéricos, aplicando a ideia de covariância a alguns novos exemplos.((("", startref="GTSvar15"))) +A seguir, vamos ver como definir protocolos estáticos genéricos, aplicando a +ideia de covariância a alguns novos exemplos.((("", startref="GTSvar15"))) [[implementing_generic_static_proto_sec]] === Implementando um protocolo estático genérico -A((("gradual type system", "implementing generic static protocols", id="GTSgenstatpro15")))((("generic static protocols", id="genstatpro15")))((("protocols", "implementing generic static protocols", id="Pgenstatpro15")))((("static protocols", "implementing generic static protocols", id="SPgenstatpro15"))) biblioteca padrão de Python 3.10 fornece alguns protocolos estáticos genéricos. -Um deles é `SupportsAbs`, implementado assim no -https://fpy.li/15-34[módulo _typing_]: +A((("gradual type system", "implementing generic static protocols", +id="GTSgenstatpro15")))((("generic static protocols", +id="genstatpro15")))((("protocols", "implementing generic static protocols", +id="Pgenstatpro15")))((("static protocols", "implementing generic static protocols", +id="SPgenstatpro15"))) biblioteca padrão de Python 3.10 fornece +alguns protocolos estáticos genéricos. Um deles é `SupportsAbs`, +implementado assim no https://fpy.li/15-34[módulo _typing_]: [source, python] ---- @@ -1333,9 +1666,12 @@ include::../code/15-more-types/protocol/abs_demo.py[] De acordo com o https://fpy.li/15-35[_typeshed_], `+int.__abs__+` devolve um `int`, o que é _consistente-com_ o parametro de tipo `float` declarado na dica de tipo `is_unit` para o argumento `v`. -De forma similar, podemos escrever uma versão genérica do protocolo `RandomPicker`, apresentado na <>, que foi definido com um único método `pick` devolvendo `Any`. +De forma similar, podemos escrever uma versão genérica do protocolo +`RandomPicker`, apresentado no <> do <>, que foi definido com +um único método `pick` devolvendo `Any`. -O <> mostra como criar um `RandomPicker` genérico, covariante no tipo devolvido por `pick`. +O <> mostra como criar um `RandomPicker` +genérico, covariante no tipo devolvido por `pick`. [[ex_generic_randompick_protocol]] ._generic_randompick.py_: definição do `RandomPicker` genérico @@ -1349,37 +1685,69 @@ include::../code/15-more-types/protocol/random/generic_randompick.py[] <2> Isso torna `RandomPicker` genérico, com um parâmetro de tipo formal covariante. <3> Usa `T_co` como tipo do valor devolvido. -[role="pagebreak-before less_space"] -O protocolo genérico `RandomPicker` pode ser covariante porque seu único parâmetro formal é usado em um tipo de saída. - -Com isso, podemos dizer que temos um capítulo.((("", startref="GTSgenstatpro15")))((("", startref="genstatpro15")))((("", startref="Pgenstatpro15")))((("", startref="SPgenstatpro15"))) +O protocolo genérico `RandomPicker` pode ser covariante porque seu único +parâmetro formal é usado em um tipo de saída. -=== Resumo do capítulo - -Começamos((("gradual type system", "overview of"))) com um exemplo simples de uso de `@overload`, seguido por um exemplo mais complexo, que estudamos em detalhes: -as assinaturas sobrecarregadas exigidas para anotar corretamente a função embutida `max`. - -A seguir veio o artefato especial da linguagem `typing.TypedDict`. -Escolhi tratar dele aqui e não no <>, onde vimos `typing.NamedTuple`, porque `TypedDict` não é uma fábrica de classes; ele é meramente uma forma de acrescentar dicas de tipo a uma variável ou a um argumento que exige um `dict` com um conjunto específico de chaves do tipo string, e tipos específicos para cada chave—algo que acontece quando usamos um `dict` como registro, muitas vezes no contexto do tratamento de dados JSON. -Aquela seção foi um pouco mais longa porque usar `TypedDict` pode levar a um falso sentimento de segurança, e queria mostrar como as checagens durante a execução e o tratamento de erros são inevitáveis quando tentamos criar registros estruturados estaticamente a partir de mapeamentos, que por natureza são dinâmicos. +Com isso, podemos dizer que temos mais um capítulo.((("", +startref="GTSgenstatpro15")))((("", startref="genstatpro15")))((("", +startref="Pgenstatpro15")))((("", startref="SPgenstatpro15"))) -Então falamos sobre `typing.cast`, uma função projetada para nos permitir guiar o trabalho do checador de tipos. É importante considerar cuidadosamente quando usar `cast`, porque seu uso excessivo atrapalha o checador de tipos. -O acesso a dicas de tipo durante a execução veio em seguida. O ponto principal era usar pass:[typing.​get_type_hints] em vez de ler o atributo `+__annotations__+` diretamente. Entretanto, aquela função pode não ser confiável para algumas anotações, e vimos que os mantenedores de Python ainda estão discutindo uma forma de tornar as dicas de tipo usáveis durante a execução, e ao mesmo tempo reduzir seu impacto sobre o uso de CPU e memória. - -A última seção foi sobre genéricos, começando com a classe genérica `LottoBlower`—que mais tarde aprendemos ser uma classe genérica invariante. -Aquele exemplo foi seguido pelas definições de quatro termos básicos: -tipo genérico, parâmetro de tipo formal, tipo parametrizado e parâmetro de tipo real. - -Continuamos pelo grande tópico da variância, usando máquinas bebidas para uma cantina e latas de lixo como exemplos da "vida real" para tipos genéricos invariantes, covariantes e contravariantes. -Então revisamos, formalizamos e aplicamos aqueles conceitos a exemplos na biblioteca padrão de Python. +=== Resumo do capítulo -Por fim, vimos como é definido um protocolo estático genérico, primeiro considerando o protocolo `typing.SupportsAbs`, e então aplicando a mesma ideia ao exemplo do `RandomPicker`, tornando-o mais rigoroso que o protocolo original do <>. +Começamos((("gradual type system", "overview of"))) com um exemplo simples de +uso de `@overload`, seguido por um exemplo mais complexo, que estudamos em +detalhes: as assinaturas sobrecarregadas exigidas para anotar corretamente a +função embutida `max`. + +A seguir veio o tipo especial `typing.TypedDict`. Escolhi tratar dele aqui e não +no <>, onde vimos `typing.NamedTuple`, porque `TypedDict` parece +mas não é uma fábrica de classes; ele é meramente uma forma de acrescentar dicas +de tipo a uma variável ou a um argumento que exige um `dict` com um conjunto +específico de chaves do tipo string, e tipos específicos para cada chave—algo +que acontece quando usamos um `dict` como registro, muitas vezes no contexto do +tratamento de dados JSON. Aquela seção foi um pouco mais longa porque usar +`TypedDict` pode levar a um falso sentimento de segurança, e eu queria mostrar +como as checagens durante a execução e o tratamento de erros são inevitáveis +quando tentamos criar registros estruturados estaticamente a partir de +mapeamentos, que são dinâmicos por natureza. + +Então falamos sobre `typing.cast`, uma função criada para nos permitir orientar +o checador de tipos. É importante considerar cuidadosamente quando usar `cast`, +porque seu uso excessivo atrapalha o checador de tipos. + +O acesso a dicas de tipo durante a execução veio em seguida. O ponto principal +era usar `typing.get_type_hints` em vez de ler o atributo `+__annotations__+` +diretamente. Entretanto, aquela função pode não ser confiável para algumas +anotações, e vimos que os mantenedores de Python ainda estão discutindo uma +forma de tornar as dicas de tipo usáveis durante a execução, e ao mesmo tempo +reduzir seu impacto sobre o uso de CPU e memória. + +A última seção foi sobre genéricos, começando com a classe genérica +`LottoBlower`—que mais tarde aprendemos ser uma classe genérica invariante. +Aquele exemplo foi seguido pelas definições de quatro termos básicos: tipo +genérico, parâmetro de tipo formal, tipo parametrizado e parâmetro de tipo real. + +Continuamos pelo grande tópico da variância, usando máquinas bebidas e latas de +lixo para uma cantina como exemplos da "vida real" para tipos genéricos +invariantes, covariantes e contravariantes. Então revisamos, formalizamos e +aplicamos aqueles conceitos a exemplos na biblioteca padrão de Python. + +Por fim, vimos como é definido um protocolo estático genérico, primeiro +considerando o protocolo `typing.SupportsAbs`, e então aplicando a mesma ideia +ao exemplo do `RandomPicker`, tornando-o mais rigoroso que o protocolo original +do <>. [NOTE] ==== -O sistema de tipos de Python é um campo imenso e em rápida evolução. Este capítulo não é abrangente. Escolhi me concentrar em tópicos que são ou amplamente aplicáveis, ou particularmente complexos ou conceitualmente importantes, e que assim provavelmente se manterão relevantes por um longo tempo. + +O sistema de tipos de Python é um campo imenso e em rápida expansão. Este +capítulo não é abrangente. Escolhi me concentrar em tópicos que são +amplamente aplicáveis, ou particularmente complexos, ou conceitualmente +importantes, e que assim provavelmente se manterão relevantes por mais +tempo. + ==== @@ -1392,109 +1760,148 @@ A <> lista todas as PEPs que encontrei até maio de 2021. Seria necessário um livro inteiro para cobrir tudo. [[typing_peps_tbl]] -.PEPs sobre dicas de tipo, com links nos títulos. PEPs com números marcados com * são importantes o suficiente para serem mencionadas no parágrafo de abertura da https://docs.python.org/pt-br/3/library/typing.html[documentação de `typing`]. Pontos de interrogação na coluna Python indica PEPs em discussão ou ainda não implementadas; "n/a" aparece em PEPs informacionais sem relação com uma versão específica de Python. Todos os textos das PEPs estão em inglês. Dados coletados em maio 2021. +.PEPs sobre dicas de tipo, com links nos títulos. PEPs com números marcados com * são importantes o suficiente para serem mencionadas no parágrafo de abertura da https://fpy.li/4a[documentação de `typing`]. Pontos de interrogação na coluna Python indica PEPs em discussão ou ainda não implementadas; "n/a" aparece em PEPs informacionais sem relação com uma versão específica de Python. Dados coletados em maio de 2021. [options="header"] +[cols="3,24,4,3"] |================================================================================================================================= -|PEP |Title |Python|Year -|3107|https://fpy.li/pep3107[Function Annotations (_Anotações de Função_)] |3.0 |2006 -|483*|https://fpy.li/pep483[The Theory of Type Hints (_A Teoria das Dicas de Tipo_)] |n/a |2014 -|484*|https://fpy.li/pep484[Type Hints (_Dicas de Tipo_)] |3.5 |2014 -|482 |https://fpy.li/pep482[Literature Overview for Type Hints (_Revisão da Literatura sobre Dicas de Tipo_)] |n/a |2015 -|526*|https://fpy.li/pep526[Syntax for Variable Annotations (_Sintaxe para Anotações de Variáveis_)] |3.6 |2016 -|544*|https://fpy.li/pep544[Protocols: Structural subtyping (static duck typing) (_Protocolos: subtipagem estrutural (duck typing estático_))] |3.8 |2017 -|557 |https://fpy.li/pep557[Data Classes (_Classes de Dados_)] |3.7 |2017 -|560 |https://fpy.li/pep560[Core support for typing module and generic types (_Suporte nativo para tipagem de módulos e tipos genéricos_)] |3.7 |2017 -|561 |https://fpy.li/pep561[Distributing and Packaging Type Information (Distribuindo e Empacotando Informação de Tipo_)] |3.7 |2017 -|563 |https://fpy.li/pep563[Postponed Evaluation of Annotations (_Avaliação Adiada de Anotações_)] |3.7 |2017 -|586*|https://fpy.li/pep586[Literal Types (_Tipos Literais_)] |3.8 |2018 -|585 |https://fpy.li/pep585[Type Hinting Generics In Standard Collections (_Dicas de Tipo para Genéricos nas Coleções Padrão_)] |3.9 |2019 -|589*|https://fpy.li/pep589[TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys (_TypedDict: Dicas de Tipo para Dicionários com um Conjunto Fixo de Chaves_)] |3.8 |2019 -|591*|https://fpy.li/pep591[Adding a final qualifier to typing (_Acrescentando um qualificador final à tipagem_)] |3.8 |2019 -|593 |https://fpy.li/pep593[Flexible function and variable annotations (_Anotações flexíveis para funções e variáveis_)] |? |2019 -|604 |https://fpy.li/pep604[Allow writing union types as X | Y (_Permitir a definição de tipos de união como_ X | Y )] |3.10 |2019 -|612 |https://fpy.li/pep612[Parameter Specification Variables (_Variáveis de Especificação de Parâmetros_)] |3.10 |2019 -|613 |https://fpy.li/pep613[Explicit Type Aliases (_Aliases de Tipo Explícitos_)] |3.10 |2020 -|645 |https://fpy.li/pep645[Allow writing optional types as x? (_Permitir a definição de tipos opcionais como_ x? )] |? |2020 -|646 |https://fpy.li/pep646[Variadic Generics (_Genéricos Variádicos_)] |? |2020 -|647 |https://fpy.li/pep647[User-Defined Type Guards (_Guardas de Tipos Definidos pelo Usuário_)] |3.10 |2021 -|649 |https://fpy.li/pep649[Deferred Evaluation Of Annotations Using Descriptors (_Avaliação Adiada de Anotações Usando Descritores_)] |? |2021 -|655 |https://fpy.li/pep655[Marking individual TypedDict items as required or potentially-missing (_Marcando itens TypedDict individuais como obrigatórios ou potencialmente ausentes_)]|? |2021 +|PEP |título |Python|ano +|3107|https://fpy.li/pep3107[_Function Annotations_] (Anotações de Função) |3.0 |2006 +|483*|https://fpy.li/pep483[_The Theory of Type Hints_] (A Teoria das Dicas de Tipo) |n/a |2014 +|484*|https://fpy.li/pep484[_Type Hints_] (Dicas de Tipo) |3.5 |2014 +|482 |https://fpy.li/pep482[_Literature Overview for Type Hints_] (Revisão da Literatura sobre Dicas de Tipo) |n/a |2015 +|526*|https://fpy.li/pep526[_Syntax for Variable Annotations_] (Sintaxe para Anotações de Variáveis) |3.6 |2016 +|544*|https://fpy.li/pep544[_Protocols: Structural subtyping (static duck typing)_] (Protocolos: subtipagem estrutural (duck typing estático)) |3.8 |2017 +|557 |https://fpy.li/pep557[_Data Classes_] (Classes de Dados) |3.7 |2017 +|560 |https://fpy.li/pep560[_Core support for typing module and generic types_] (Suporte nativo para tipagem de módulos e tipos genéricos) |3.7 |2017 +|561 |https://fpy.li/pep561[_Distributing and Packaging Type Information_] (Distribuindo e Empacotando Informação de Tipo) |3.7 |2017 +|563 |https://fpy.li/pep563[_Postponed Evaluation of Annotations_] (Avaliação Adiada de Anotações) |3.7 |2017 +|586*|https://fpy.li/pep586[_Literal Types_] (Tipos Literais) |3.8 |2018 +|585 |https://fpy.li/pep585[_Type Hinting Generics In Standard Collections_] (Dicas de Tipo para Genéricos nas Coleções Padrão) |3.9 |2019 +|589*|https://fpy.li/pep589[_TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys_] (TypedDict: Dicas de Tipo para Dicionários com um Conjunto Fixo de Chaves) |3.8 |2019 +|591*|https://fpy.li/pep591[_Adding a final qualifier to typing_] (Acrescentando um qualificador final à tipagem) |3.8 |2019 +|593 |https://fpy.li/pep593[_Flexible function and variable annotations_] (Anotações flexíveis para funções e variáveis) |? |2019 +|604 |https://fpy.li/pep604[_Allow writing union types as X | Y_] (Permitir a definição de tipos de união como X | Y) |3.10 |2019 +|612 |https://fpy.li/pep612[_Parameter Specification Variables_] (Variáveis de Especificação de Parâmetros) |3.10 |2019 +|613 |https://fpy.li/pep613[_Explicit Type Aliases_] (Aliases de Tipo Explícitos) |3.10 |2020 +|645 |https://fpy.li/pep645[_Allow writing optional types as x?_] (Permitir a definição de tipos opcionais como x?) |? |2020 +|646 |https://fpy.li/pep646[_Variadic Generics_] (Genéricos Variádicos) |? |2020 +|647 |https://fpy.li/pep647[_User-Defined Type Guards_] (Guardas de Tipos Definidos pelo Usuário) |3.10 |2021 +|649 |https://fpy.li/pep649[_Deferred Evaluation Of Annotations Using Descriptors_] (Avaliação Adiada de Anotações Usando Descritores) |? |2021 +|655 |https://fpy.li/pep655[_Marking individual TypedDict items as required or potentially-missing_] (Marcando itens individuais de TypedDict como obrigatórios ou potencialmente ausentes)|? |2021 |================================================================================================================================= -A documentação oficial de Python mal consegue acompanhar tudo aquilo, então https://fpy.li/mypy[a documentação do Mypy] (EN) é uma referência essencial. -pass:[Robust Python] (EN), -de Patrick Viafore (O'Reilly), é o primeiro livro com um tratamento abrangente do sistema de tipagem estática de Python que conheço, publicado em agosto de 2021. Você pode estar lendo o segundo livro sobre o assunto nesse exato instante. +A documentação oficial de Python mal consegue acompanhar tudo aquilo, então +https://fpy.li/mypy[a documentação do Mypy] é uma referência essencial. +_Robust Python_, de Patrick Viafore (O'Reilly), é o primeiro livro com um tratamento abrangente do +sistema de tipagem estática de Python que conheço, publicado em agosto de 2021. +Você pode estar lendo o segundo livro sobre o assunto nesse exato instante. -O sutil tópico da variância tem sua própria https://fpy.li/15-37[seção na PEP 484] (EN), e também é abordado na página https://fpy.li/15-38["Generics" (_Genéricos_)] (EN) do Mypy, bem como em sua inestimável página https://fpy.li/15-39["Common Issues" (_Problemas Comuns_)]. +O tópico sutil da variância tem sua própria +https://fpy.li/15-37[seção na PEP 484], e também é abordado na página +https://fpy.li/15-38[_Generics_] do Mypy, bem como em sua inestimável página +https://fpy.li/15-39[_Common Issues_] (Problemas Comuns). -A https://fpy.li/pep362[PEP 362—Function Signature Object (_O Objeto Assinatura de Função_)] +A https://fpy.li/pep362[_PEP 362—Function Signature Object_] (O objeto assinatura de função) vale a pena ler se você pretende usar o módulo `inspect`, que complementa a função `typing.get_type_hints`. -Se tiver interesse na história de Python, pode gostar de saber que Guido van Rossum publicou https://fpy.li/15-40["Adding Optional Static Typing to Python" (_Acrescentando Tipagem Estática Opcional ao Python_)] em 23 de dezembro de 2004. - -https://fpy.li/15-41["Python 3 Types in the Wild: A Tale of Two Type Systems" (_Os Tipos de Python 3 na Natureza: Um Conto de Dois Sistemas de Tipo_)] (EN) é um artigo científico de Ingkarat Rak-amnouykit e outros, do Rensselaer Polytechnic Institute e do IBM TJ Watson Research Center. -O artigo avalia o uso de dicas de tipo em projetos de código aberto no GitHub, mostrando que a maioria dos projetos não as usam , e também que a maioria dos projetos que incluem dicas de tipo aparentemente não usam um checador de tipos. -Achei particularmente interessante a discussão das semânticas diferentes do Mypy e do _pytype_ do -Google, onde os autores concluem que eles são "essencialmente dois sistemas de tipos diferentes." - -Dois artigos fundamentais sobre tipagem gradual são https://fpy.li/15-42["Pluggable Type Systems" (_Sistemas de Tipo Conectáveis_)] (EN), de Gilad Bracha, e -https://fpy.li/15-43["Static Typing Where Possible, Dynamic Typing When Needed: The End of the Cold War Between Programming Languages" (_Tipagem Estática Quando Possível, Tipagem Dinâmica Quando Necessário: O Fim da Guerra Fria Entre Linguagens de Programação_)] (EN), -de Eric Meijer e Peter Drayton.footnote:[O leitor de notas de rodapé se lembrará que dei o crédito a Erik Meijer pela analogia da cantina para explicar variância.] - -Aprendi muito lendo as partes relevantes de alguns livros sobre outras linguagens que implementam algumas das mesmas ideias: - -* https://fpy.li/15-44[_Atomic Kotlin_] (EN), de Bruce Eckel e Svetlana Isakova -[.keep-together]#(Mindview)# -* https://fpy.li/15-45[_Effective Java_, 3rd ed.,] (EN), de Joshua Bloch (Addison-Wesley) -* https://fpy.li/15-46[_Programming with Types: TypeScript Examples_] (EN), de Vlad Riscutia (Manning) - -* https://fpy.li/15-47[_Programming TypeScript_] (EN), de Boris Cherny (O'Reilly) -* https://fpy.li/15-48[_The Dart Programming Language_] (EN) de Gilad Bracha (Addison-Wesley).footnote:[Esse livro foi escrito para o Dart 1. Há mudanças significativas no Dart 2, inclusive no sistema de tipos. Mesmo assim, Bracha é um pesquisador importante na área de design de linguagens de programação, e achei o livro valioso por sya perspectiva sobre o design do Dart.] - +Se tiver interesse na história de Python, saiba que Guido van Rossum publicou +https://fpy.li/15-40[_Adding Optional Static Typing to Python_] +(Acrescentando tipagem estática opcional ao Python). + +https://fpy.li/15-41[_Python 3 Types in the Wild: A Tale of Two Type Systems_] +(Os tipos de Python 3 na natureza: um conto de dois sistemas de tipo) é um +artigo científico de Ingkarat Rak-amnouykit e outros, do Rensselaer Polytechnic +Institute e do IBM TJ Watson Research Center. O artigo avalia o uso de dicas de +tipo em projetos de código aberto no GitHub, mostrando que a maioria dos +projetos não as usa, e também que a maioria dos projetos que têm dicas de +tipo aparentemente não usa um checador de tipos. Achei particularmente +interessante a discussão das semânticas diferentes do Mypy e do _pytype_ do +Google, onde os autores concluem que eles são "essencialmente dois sistemas de +tipos diferentes." + +Dois artigos fundamentais sobre tipagem gradual são +https://fpy.li/15-42[_Pluggable Type Systems_] (Sistemas de tipo conectáveis), +de Gilad Bracha, e +https://fpy.li/15-43[_Static Typing Where Possible, Dynamic Typing When Needed: The End of the Cold War Between Programming Languages_] +(Tipagem Estática Quando Possível, Tipagem Dinâmica Quando Necessário: O Fim da +Guerra Fria Entre Linguagens de Programação), de Eric Meijer e Peter +Drayton.footnote:[O leitor de notas de rodapé se lembrará que dei o crédito a +Erik Meijer pela analogia da cantina para explicar variância.] + +Aprendi muito lendo as partes relevantes de alguns livros sobre outras +linguagens que implementam algumas das mesmas ideias: + +* https://fpy.li/15-44[_Atomic Kotlin_], de Bruce Eckel e Svetlana Isakova +(Mindview) + +* https://fpy.li/15-45[_Effective Java_, 3rd ed.,], de Joshua Bloch +(Addison-Wesley) + +* https://fpy.li/15-46[_Programming with Types: TypeScript Examples_], de Vlad +Riscutia (Manning) + +* https://fpy.li/15-47[_Programming TypeScript_], de Boris Cherny (O'Reilly) + +* https://fpy.li/15-48[_The Dart Programming Language_] de Gilad Bracha +(Addison-Wesley).footnote:[Esse livro foi escrito para o Dart 1. Há mudanças +significativas no Dart 2, inclusive no sistema de tipos. Mesmo assim, Bracha é +um pesquisador importante na área de design de linguagens de programação, e +achei o livro valioso por sua perspectiva sobre o design do Dart.] Para algumas visões críticas sobre os sistemas de tipagem, recomendo os posts de Victor Youdaiken -https://fpy.li/15-49["Bad ideas in type theory" (_Más ideias na teoria dos tipos_)] (EN) -e https://fpy.li/15-50["Types considered harmful II" (_Tipos considerados nocivos II_)] (EN). +https://fpy.li/15-49[_Bad ideas in type theory_] (Ideias ruins em teoria dos tipos) +e https://fpy.li/15-50[_Types considered harmful II_] (Tipos considerados nocivos II). -Por fim, me surpreeendi ao encontrar https://fpy.li/15-51["Generics Considered Harmful" (_Genéricos Considerados Nocivos_)], de Ken Arnold, -um desenvolvedor principal de Java desde o início, bem como co-autor das primeiras quatro edições do livro oficial -_The Java Programming Language_ (Addison-Wesley)—junto com James Gosling, -o principal criador de Java. +Por fim, me surpreendi ao encontrar +https://fpy.li/15-51[_Generics Considered Harmful_] (Genéricos Considerados Nocivos), +de Ken Arnold, um desenvolvedor +principal de Java desde o início, bem como co-autor das primeiras quatro edições +do livro oficial _The Java Programming Language_ (Addison-Wesley)—com +James Gosling, o principal criador de Java. -Infelizmente, as críticas de Arnold também se aplicam ao sistema de tipagem estática de Python. -Quando leio as muitas regras e casos especiais das PEPs de tipagem, sou constantemente lembrado dessa passagem do post de Arnold: +Infelizmente, as críticas de Arnold também se aplicam ao sistema de tipagem +estática de Python. Quando leio as muitas regras e casos especiais das PEPs de +tipagem, sou constantemente lembrado dessa passagem do post de Arnold: [quote] ____ + O que nos traz ao problema que sempre cito para o {cpp}: -eu a chamo de "exceção de enésima ordem à regra de exceção". -Ela soa assim: "Você pode fazer x, exceto no caso y, a menos que y faça z, caso em que você pode se..." +a "exceção de enésima ordem à regra de exceção". + +É mais ou menos assim: "Você pode fazer x, exceto no caso y, a menos que y faça z, caso +em que você pode se..." + ____ -Felizmente, Python tem uma vantagem crítica sobre o Java e o {cpp}: -um sistema de tipagem opcional. -Podemos silenciar os checadores de tipos e omitir as dicas de tipo quando se tornam muito incômodos. +Felizmente, Python tem uma vantagem crítica sobre o Java e o {cpp}: um sistema +de tipagem opcional. Podemos silenciar os checadores de tipos e omitir as dicas +de tipo quando se tornam muito inconvenientes. [[type_hints_in_classes_soapbox]] .Ponto de Vista **** -[role="soapbox-title"] -As tocas de coelho da tipagem - -Quando((("gradual type system", "Soapbox discussion")))((("Soapbox sidebars", "undocumented classes")))((("classes", "undocumented classes")))((("undocumented classes"))) usamos um checador de tipos, algumas vezes somos obrigados a descobrir e importar classes que não precisávamos conhecer, e que nosso código não precisa usar—exceto para escrever dicas de tipo. -Tais classes não são documentadas, provavelmente porque são consideradas detalhes de implementação pelos autores dos pacotes. Aqui estão dois exemplos da biblioteca padrão. +**As tocas de coelho da tipagem** -Tive que vasculhar a imensa documentação do _asyncio_, -e depois navegar o código-fonte de vários módulos daquele pacote para descobrir a classe não-documentada -`TransportSocket` no módulo igualmente não documentado `asyncio.trsock` -só para usar `cast()` no exemplo do `server.sockets`, na <>. -Usar `socket.socket` em vez de `TransportSocket` seria incorreto, pois esse último não é subtipo do primeiro, como explicitado em uma -https://fpy.li/15-52[docstring] (EN) no código-fonte. +Quando((("gradual type system", "Soapbox discussion")))((("Soapbox sidebars", +"undocumented classes")))((("classes", "undocumented classes")))((("undocumented classes"))) +usamos um checador de tipos, algumas vezes somos obrigados a +descobrir e importar classes que não precisávamos conhecer, e que nosso código +não precisa usar—exceto para escrever dicas de tipo. Tais classes não são +documentadas, provavelmente porque são consideradas detalhes de implementação +pelos autores dos pacotes. Aqui estão dois exemplos da biblioteca padrão. +Tive que vasculhar a imensa documentação do _asyncio_, e depois navegar o +código-fonte de vários módulos daquele pacote para descobrir a classe +não-documentada `TransportSocket` no módulo igualmente não documentado +`asyncio.trsock` só para usar `cast()` no exemplo do `server.sockets`, na +<>. Usar `socket.socket` em vez de `TransportSocket` seria +incorreto, pois esse último não é subtipo do primeiro, como explicitado em uma +https://fpy.li/15-52[docstring] no código-fonte. Caí em uma toca de coelho similar quando acrescentei dicas de tipo ao -<>, uma demonstração simples de `multiprocessing`. +<> do <>, uma demonstração simples de `multiprocessing`. Aquele exemplo usa objetos `SimpleQueue`, obtidos invocando `multiprocessing.SimpleQueue()`. Entretanto, não pude usar aquele nome em uma dica de tipo, @@ -1511,28 +1918,51 @@ provavelmente evitaria tais caças ao tesouro por causa de uma única linha, e simplesmente colocaria `# type: ignore`. Algumas vezes essa é a única solução com custo-benefício positivo. -[role="soapbox-title"] -Notação de variância em outras linguagens +**Notação de variância em outras linguagens** -A variância((("Soapbox sidebars", "variance notation in other classes")))((("variance", "variance notation in other classes"))) é um tópico complicado, e a sintaxe das dicas de tipo de Python não é tão boa quanto poderia ser. -Essa citação direta da PEP 484 evidencia isso: +**Notação de variância em outras linguagens** + +A variância((("Soapbox sidebars", "variance notation in other languages")))((("variance", +"variance notation in other languages"))) é um tópico complicado, +e a sintaxe das dicas de tipo de Python deixa a desejar. +Esta citação direta da PEP 484 evidencia isso: [quote] ____ -Covariância ou contravariância não são propriedaades de uma variável de tipo, -mas sim uma propriedade da classe genérica definida usando essa variável.footnote:[Veja o último parágrafo da seção https://fpy.li/15-37["Covariance and Contravariance" (_Covariância e Contravariância_)] (EN) na PEP 484.] + +Covariância ou contravariância não são propriedades de uma variável de tipo, +mas sim uma propriedade da classe genérica definida usando essa +variável.footnote:[Veja o último parágrafo da seção +https://fpy.li/15-37[_Covariance and Contravariance_] (Covariância e +Contravariância) na PEP 484.] + ____ -Se esse é o caso, por que a covariância e a contravarância são declaradas com `TypeVar` -e não na classe genérica? +Se esse é o caso, por que a covariância e a contravarância são declaradas com +`TypeVar` e não na classe genérica? + +Os autores da PEP 484 optaram por introduzir dicas de tipo sem fazer +qualquer modificação no interpretador. +Em Python, todo identificador aparece pela primeira vez no código-fonte +de um módulo através de uma +atribuição, ou uma instrução especial como `import`, `class`, ou `def`. +Por isso tiveram que criar `TypeVar` para declarar uma variável de tipo +através de uma atribuição: + +[source, python] +---- +T = TypeVar('T') +---- -Os autores da PEP 484 trabalharam sob a severa restrição auto-imposta de suportar dicas de tipo sem fazer qualquer modificação no interpretador. -Isso exigiu a introdução de `TypeVar` para definir variáveis de tipo, -e também levou ao abuso de `[]` para fornecer a sintaxe `Klass[T]` para genéricos—em vez da notação `Klass` usada em outras linguagens populares, incluindo C#, Java, Kotlin e TypeScript. -Nenhuma dessas linguagens exige que variáveis de tipo seja declaradas antes de serem usadas. +Para não mexer no _parser_, reutilizaram o operador `[]` na sintaxe +`Klass[T]` para genéricos—em vez da +notação `Klass` usada em outras linguagens populares, incluindo C#, Java, +Kotlin e TypeScript. Estas linguagens não exigem que variáveis de tipo sejam +declaradas antes de serem usadas. -Além disso, a sintaxe do Kotlin e do C# torna claro se um parâmetro de tipo é covariante, -contravariante ou invariante exatamente onde isso faz sentido: na declaração de classe ou interface. +Além disso, a sintaxe do Kotlin e do C# torna claro se um parâmetro de tipo é +covariante, contravariante ou invariante exatamente onde isso faz sentido: na +declaração de classe ou interface. Em Kotlin, poderíamos declarar a `BeverageDispenser` assim: @@ -1544,9 +1974,7 @@ class BeverageDispenser { ---- O modificador `out` no parâmetro de tipo formal significa que `T` é um tipo de -_output_ (saída)), e portanto `BeverageDispenser` é covariante. - -[role="pagebreak-before less_space"] +_output_ (saída), e portanto `BeverageDispenser` é covariante. Você provavelmente consegue adivinhar como `TrashCan` seria declarada: [source, kotlin] @@ -1557,14 +1985,13 @@ class TrashCan { ---- Dado `T` como um parâmetro de tipo formal de _input_ (entrada), -segue que `TrashCan` é contravariante. - +então `TrashCan` é contravariante. Se nem `in` nem `out` aparecem, então a classe é invariante naquele parâmetro. -É fácil lembrar das <> quando `out` e `in` são usado nos parâmetros de tipo formais. - -Isso sugere que uma boa convenção para nomenclatura de variáveis de tipo covariante e -contravariantes no Python seria: +É fácil lembrar das regras gerais de variância (<>) +quando `out` e `in` são usados nos parâmetros de tipo formais. +Isso sugere uma convenção melhor para nomear de variáveis de tipo +covariantes e contravariantes: [source, python] ---- @@ -1583,6 +2010,7 @@ class TrashCan(Generic[T_in]): ... ---- -Será que é tarde demais para modificar a convenção de nomenclatura definida na PEP 484? +Será tarde demais para adotar `T_out` e `T_in` em vez de +`T_co` e `T_contra` que foram sugeridos na PEP 484? **** diff --git a/online/cap16.adoc b/online/cap16.adoc index 23a466b..ef6eedc 100644 --- a/online/cap16.adoc +++ b/online/cap16.adoc @@ -5,22 +5,35 @@ [quote, James Gosling, Criador de Java] ____ -Existem algumas coisas que me deixam meio dividido, como a sobrecarga de operadores. Deixei a sobrecarga de operadores de fora em uma decisão bastante pessoal, pois tinha visto gente demais abusar [desse recurso] no {cpp}.footnote:[Fonte: https://fpy.li/16-1["The C Family of Languages: Interview with Dennis Ritchie, Bjarne Stroustrup, and James Gosling" (_A Família de Linguagens C: Entrevista com Dennis Ritchie, Bjarne Stroustrup, e James Gosling_)] (EN).] + +Certas coisas me deixam meio dividido, como a sobrecarga de operadores. Deixei +a sobrecarga de operadores de fora em uma decisão bastante pessoal, pois tinha +visto gente demais abusar [deste recurso] no {cpp}.footnote:[Fonte: +https://fpy.li/16-1[_The C Family of Languages: Interview with Dennis Ritchie, +Bjarne Stroustrup, and James Gosling_] (A Família de Linguagens C: entrevista +com Dennis Ritchie, Bjarne Stroustrup, e James Gosling).] + ____ -Em((("operator overloading", "infix operators")))((("infix operators"))) Python, podemos calcular juros compostos usando uma fórmula escrita assim: +Em((("operator overloading", "infix operators")))((("infix operators"))) Python, +podemos calcular juros compostos usando uma fórmula escrita assim: [source, python] ---- interest = principal * ((1 + rate) ** periods - 1) ---- -Operadores que aparecem entre operandos, como em `1 + rate`, são _operadores infixos_. -No Python, operadores infixos podem lidar com qualquer tipo arbitrário. -Assim, se você está trabalhando com dinheiro real, pode se assegurar que `principal`, `rate`, e `periods` sejam números exatos—instâncias da classe `decimal.Decimal` de Python—e a fórmula vai funcionar como está escrita, produzindo um resultado exato. +Operadores que aparecem entre operandos, como `{plus}` em `1 + rate`, são +_operadores infixos_. No Python, operadores infixos podem lidar com qualquer +tipo arbitrário. Assim, se você está trabalhando com dinheiro de verdade, pode +armazenar `principal`, `rate`, e `periods` como números exatos—instâncias da +classe `decimal.Decimal` de Python. A mesma fórmula vai funcionar como escrita, +calculando um resultado exato. -Mas em Java, se você mudar de `float` para `BigDecimal`, para obter resultados exatos, não é mais possível usar operadores infixos, porque naquela linguagem eles só funcionam com tipos primitivos. -Abaixo vemos a mesma fórmula escrita em Java para funcionar com números `BigDecimal`: +Mas em Java, se você mudar de `float` para `BigDecimal`, para obter resultados +exatos, não é mais possível usar operadores infixos, porque naquela linguagem +eles só funcionam com tipos primitivos como `float` ou `long`. +Veja a mesma fórmula escrita em Java para funcionar com números `BigDecimal`: [source, java] ---- @@ -28,73 +41,139 @@ BigDecimal interest = principal.multiply(BigDecimal.ONE.add(rate) .pow(periods).subtract(BigDecimal.ONE)); ---- -Está claro que operadores infixos tornam as fórmulas mais legíveis. -A sobrecarga de operadores é necessária para suportar a notação infixa de operadores com tipos definidos pelo usuário ou estendidos, tal como os arrays da NumPy. -Oferecer a sobrecarga de operadores em uma linguagem de alto nível e fácil de usar foi provavelmente uma das principais razões do imenso sucesso de Python na ciência de dados, incluindo as aplicações financeiras e científicas. +Está claro que operadores infixos tornam as fórmulas mais legíveis. A sobrecarga +de operadores é necessária para suportar a notação infixa de operadores com +tipos definidos pelo usuário ou em extensões compiladas, como os arrays da NumPy. +Oferecer a sobrecarga de operadores em uma linguagem de alto nível e fácil de +usar foi talvez uma das principais razões do grande sucesso de Python na +ciência de dados, incluindo as aplicações científicas e financeiras. -Na <> (<>) vimos algumas implementações triviais de operadores em uma classe básica `Vector`. Os métodos `+__add__+` e `+__mul__+` no <> foram escritos para demonstrar como os métodos especiais suportam a sobrecarga de operadores, mas deixamos passar problemas sutis naquelas implementações. Além disso, no <> notamos que o método `+Vector2d.__eq__+` considera `True` a seguinte expressão: `Vector(3, 4) == [3, 4]`—algo que pode ou não fazer sentido. Nesse capítulo vamos cuidar desses problemas, e((("operator overloading", "topics covered"))) falaremos também de: +Na <>, vimos algumas implementações +triviais de operadores em uma classe básica `Vector`. +Escrevi os métodos `+__add__+` e `+__mul__+` no <> do <> +para demonstrar como os métodos especiais suportam a sobrecarga de operadores, +mas deixei passar alguns problemas sutis naquelas implementações. +Além disso, no <> do <> notamos que o +método `+Vector2d.__eq__+` considera `True` a seguinte expressão: +`Vector(3, 4) == [3, 4]`. Tal resultado pode fazer sentido ou não. Neste capítulo vamos +cuidar destes problemas, e((("operator overloading", "topics covered"))) +falaremos também de: -* Como um método de operador infixo deveria indicar que não consegue tratar um operando -* O uso de _duck typing_ ou _goose typing_ para lidar com operandos de vários tipos -* O comportamento especial dos operadores de comparação cheia (e.g., `==`, `>`, `<=`, etc.) -* O tratamento default de operadores de atribuição aumentada tal como `+=`, e como sobrecarregá-los +* Como um método de operador infixo deve indicar que não consegue tratar um operando +* Tipagem pato e tipagem ganso para lidar com operandos de tipos diferentes +* O comportamento especial dos operadores de comparação rica (`==`, `>`, `{lte}`, etc.) +* O tratamento padrão de operadores de atribuição aumentada, como `{iadd}`, e como sobrecarregá-los === Novidades neste capítulo -O _goose typing_((("operator overloading", "significant changes to"))) é uma parte fundamental de Python, mas as ABCs `numbers` não são suportadas na tipagem estática. Então modifiquei o <> para usar _duck typing_, em vez de uma checagem explícita usando `isinstance` contra `numbers.Real`.footnote:[O restante das ABCs na biblioteca padrão de Python ainda são valiosas para o _goose typing_ e a tipagem estática. O problema com as ABCs `numbers` é explicado na <>.] +A tipagem ganso((("operator overloading", "significant changes to"))) é uma +parte fundamental de Python, mas as ABCs `numbers` não são suportadas na tipagem +estática. Então, mudei o <> para usar tipagem pato, em vez de uma +checagem explícita usando `isinstance` contra `numbers.Real`.footnote:[As demais +ABCs na biblioteca padrão de Python funcionam bem para tipagem ganso e tipagem +estática. O problema com as ABCs `numbers` é explicado na +<>.] -Na primeira edição do _Python Fluente_, tratei do operador de multiplicação de matrizes `@` como uma mudança futura, quando Python 3.5 ainda estava em sua versão alfa. Agora o `@` está integrado ao fluxo do capítulo na <>. -Aproveitei o _goose typing_ para tornar a implementação de `+__matmul__+` aqui mais segura que a da primeira edição, sem comprometer sua flexibilidade. +Na primeira edição do _Python Fluente_, tratei do operador de multiplicação de +matrizes `@` como uma mudança futura, pois o Python 3.5 ainda estava em desenvolvimento. +Agora o `@` está integrado ao fluxo do capítulo na <>. +Aproveitei a tipagem ganso para tornar a implementação de `+__matmul__+` +mais segura na primeira edição, sem comprometer sua flexibilidade. -A <> agora inclui algumas novas referências—incluindo um post de blog de Guido van Rossum. -Também adicionei menções a duas bibliotecas que demonstram um uso efetivo da sobrecarga de operadores fora do domínio da matemática: `pathlib` e `Scapy`. +A <> agora inclui algumas novas referências—incluindo um +post do blog de Guido van Rossum. Também inclui menções a duas bibliotecas +que demonstram usos interessantes da sobrecarga de operadores em contextos +não numéricos: `pathlib` e `Scapy`. [[op_overloading_101_sec]] === Introdução à sobrecarga de operadores -A sobrecarga de operadores((("operator overloading", "basics of"))) permite que objetos definidos pelo usuário interoperem com operadores infixos como `+` e `|`, ou com operadores unários como `-` e `~`. No Python, de uma perspectiva mais geral, a invocação de funções (`()`), o acesso a atributos (`.`) e o acesso a itens e o fatiamento (`[]`) também são operadores, mas este capítulo trata dos operadores unários e infixos. +A sobrecarga de operadores((("operator overloading", "basics of"))) permite que +objetos definidos pelo usuário suportem operadores infixos como `{plus}` e +`|`, ou com operadores unários como `-` e `~`. +De forma geral, em Python a notação de invocação de função (`f()`), +o acesso a atributos (`p.x`) e o acesso a itens e o fatiamento (`v[0]`) +também são operadores, mas este capítulo trata dos operadores unários e infixos. -A sobrecarga de operadores tem má-fama em certos círculos. -É um recurso da linguagem que pode ser (e tem sido) abusado, -resultando em programadores confusos, bugs, e gargalos de desempenho inesperados. -Mas se bem utilizada, possibilita APIs agradáveis de usar e código legível. -Python alcança um bom equilíbrio entre flexibilidade, -usabilidade e segurança, -pela imposição de algumas limitações: +A sobrecarga de operadores tem má reputação em certos círculos. É um recurso que +pode ser abusado, resultando em programadores confusos, bugs, e gargalos de +desempenho inesperados. Mas se bem utilizada, possibilita APIs agradáveis de +usar e código legível. Python alcança um bom equilíbrio entre flexibilidade, +usabilidade e segurança, pela imposição de algumas limitações: * Não é permitido modificar o significado dos operadores para os tipos embutidos. * Não é permitido criar novos operadores, apenas sobrecarregar os existentes. -* Alguns poucos operadores não podem ser sobrecarregados: `is`, `and`, `or` e `not` (mas os operadores `==`, `&`, `|`, e `~` podem). +* Alguns poucos operadores não podem ser sobrecarregados: +`is`, `and`, `or` e `not` (mas os operadores `==`, `&`, `|`, e `~` podem). -No <>, na classe `Vector`, já apresentamos um operador infixo: `==`, suportado pelo método `+__eq__+`. Nesse capítulo, vamos melhorar a implementação de `+__eq__+` para lidar melhor com operandos de outros tipos além de `Vector`. Entretanto, os operadores de comparação cheia (`==`, `!=`, `>`, `<`, `>=`, `<=`) são casos especiais de sobrecarga de operadores, então começaremos sobrecarregando quatro operadores aritméticos em `Vector`: os operadores unários `-` e `+`, seguido pelos infixos `+` e `*`. +No <>, na classe `Vector`, já apresentamos um operador infixo: +`==`, suportado pelo método `+__eq__+`. Neste capítulo, vamos melhorar a +implementação de `+__eq__+` para lidar melhor com operandos de outros tipos além +de `Vector`. Entretanto, os operadores de comparação rica (`==`, `!=`, `>`, `<`, +`>=`, `{lte}`) são casos especiais de sobrecarga de operadores, então +começaremos sobrecarregando quatro operadores aritméticos em `Vector`: os +operadores unários `-` e `{plus}`, seguido pelos infixos `{plus}` e `*`. Vamos começar pelo tópico mais fácil: operadores unários. === Operadores unários -A seção https://docs.python.org/pt-br/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations["6.5. Unary arithmetic and bitwise operations" (_Aritmética unária e operações binárias_)] (EN), de _A Referência da Linguagem Python_, elenca((("operator overloading", "unary operators", id="OOunary16")))((("unary operators", id="unary16"))) três operações unárias, listadas abaixo juntamente com seus métodos especiais associados: - -`-`, implementado por `+__neg__+`:: Negativo((("__neg__"))) aritmético unário. Se `x` é `-2` então `-x == 2`. -`{plus}`, implementado por `+__pos__+`:: Positivo((("__pos__"))) aritmético unário. De forma geral, `x == +x`, mas há alguns poucos casos onde isso não é verdadeiro. Veja a <>, se estiver curioso. -`~`, implementado por `+__invert__+`:: Negação((("__invert__"))) binária, ou inversão binária de um inteiro, definida como `~x == -(x+1)`. Se `x` é `2` então `~x == -3`.footnote:[Veja https://pt.wikipedia.org/wiki/L%C3%B3gica_bin%C3%A1ria#NOT[Lógica Binária - NOT] para uma explicação da negação binária.] - -O pass:[capítulo "Modelo de Dados"] de _A Referência da Linguagem Python_ também inclui a função embutida `abs()` como um operador unário. O método especial associado é `+__abs__+`, como já vimos. - -É fácil suportar operadores unários. Basta implementar o método especial apropriado, que receberá apenas um argumento: `self`. Use a lógica que fizer sentido na sua classe, mas se atenha à regra geral dos operadores: sempre devolva um novo objeto. Em outras palavras, não modifique o receptor (`self`), crie e devolva uma nova instância do tipo adequado. - -No caso de `-` e `+`, o resultado será provavelmente uma instância da mesma classe de `self`. Para o `+` unário, se o receptor for imutável você deveria devolver `self`; caso contrário, devolva uma cópia de `self`. -Para `abs()`, o resultado deve ser um número escalar. - -Já no caso de `~`, é difícil determinar o que seria um resultado razoável se você não estiver lidando com bits de um número inteiro. -No pacote de análise de dados https://fpy.li/pandas[_pandas_], o til nega condições booleanas de filtragem; veja exemplos na documentação do _pandas_, em https://fpy.li/16-4["Boolean indexing" (_Indexação booleana)] (EN). - -Como prometido acima, vamos implementar vários novos operadores na classe `Vector`, do <>. O <> mostra o método `+__abs__+`, que já estava no <>, e os novos métodos `+__neg__+` e `+__pos__+` para operadores unários. +Na((("operator overloading", "unary operators", +id="OOunary16")))((("unary operators", id="unary16"))) +Referência da Linguagem Python, a seção +https://fpy.li/7n[Operações aritméticas unárias e bit a bit], +cita três operadores unários, listados abaixo com os seus métodos especiais: + +`-` implementado por `+__neg__+`:: +Negativo((("__neg__"))) aritmético unário. Se `x` é +`42` então `-x == -42`. + +`{plus}` implementado por `+__pos__+`:: +Positivo((("__pos__"))) aritmético unário. Em geral, +`x == +x`, mas há alguns poucos casos em que isto não ocorre. Veja: +<> (ao final desta seção). + +`~` implementado por `+__invert__+`:: +Negação((("__invert__"))) binária, ou inversão +bit a bit de um inteiro, definida como `~x == -(x+1)`. Se `x` é `2` então +`~x == -3`, porque a representação binária de `2` é `0010` e `-3` é `1101`. +Veja https://fpy.li/7p[Complemento para dois] na Wikipédia para entender +esta representação de inteiros com sinal. + +O capítulo Modelo de Dados na Referência da Linguagem Python_ também inclui a +função embutida `abs()` como um operador unário. O método especial associado é +`+__abs__+`, como já vimos. + +É fácil suportar operadores unários. Basta implementar o método especial +apropriado, que receberá apenas um argumento: `self`. Use a lógica que fizer +sentido na sua classe, mas respeite a regra geral dos operadores: sempre +devolva um novo objeto. Em outras palavras, não modifique o receptor (`self`), +mas crie e devolva uma nova instância do tipo adequado. + +No caso de `-` e `{plus}`, o resultado será provavelmente uma instância da mesma +classe de `self`. Para o `{plus}` unário, se o receptor for imutável você +deveria devolver `self`; caso contrário, devolva uma cópia de `self`. Para +`abs()`, o resultado deve ser um número escalar.footnote:[Em matemática, um +"escalar" é um número que pode ser representado por um ponto em uma linha, ou +"escala". Em Python, instâncias de `int`, `float`, `decimal.Decimal` e +`fraction.Fraction` são escalares, mas um `complex` não é um escalar.] + +Já no caso de `~`, é difícil determinar o que seria um resultado razoável se +você não estiver lidando com bits de um número inteiro. No pacote de análise de +dados https://fpy.li/pandas[_pandas_], o til nega condições booleanas de +filtragem; veja exemplos na documentação do _pandas_, em +https://fpy.li/16-4[_Boolean indexing_] (indexação booleana). + +Como prometido acima, vamos implementar vários novos operadores na classe +`Vector`, do <>. O <> mostra o método +`+__abs__+`, que já estava no <> do <>, +e os novos métodos `+__neg__+` e `+__pos__+` para operadores unários. [[ex_vector_v6_unary]] -.vector_v6.py: unary operators - and + added to <> +.vector_v6.py: operadores unários `-` e `{plus}` implementados. ==== [source, python] ---- @@ -104,22 +183,32 @@ include::../code/16-op-overloading/vector_v6.py[tags=VECTOR_V6_UNARY] <1> Para computar `-v`, cria um novo `Vector` com a negação de cada componente de `self`. <2> Para computar `+v`, cria um novo `Vector` com cada componente de `self`. -Lembre-se que instâncias de `Vector` são iteráveis, e o `+Vector.__init__+` recebe um argumento iterável, e daí as implementações de `+__neg__+` e -`+__pos__+` são curtas e rápidas. +Lembre-se que instâncias de `Vector` são iteráveis, e o `+Vector.__init__+` +recebe um argumento iterável, por isso implementações de `+__neg__+` e +`+__pos__+` são tão simples. -Não vamos implementar `+__invert__+`. Se um usuário tentar escrever `~v` para uma instância de `Vector`, Python vai gerar um `TypeError` com uma mensagem clara: “bad operand type for unary ~: `'Vector'`” (_operando inválido para o ~ unário: `'Vector'`). +Não vamos implementar `+__invert__+`. Se um usuário tentar escrever `~v` para +uma instância de `Vector`, Python vai gerar um `TypeError` com uma mensagem +clara: “bad operand type for unary ~: `'Vector'`” (operando inválido para o ~ +unário: `'Vector'`). -O quadro a seguir trata de uma curiosidade que algum dia poderá ajudar você a ganhar uma aposta sobre o `+` unário . +O quadro a seguir trata de uma curiosidade que algum dia poderá ajudar você a +ganhar uma aposta sobre o `{plus}` unário. [[when_plus_x_sec]] [role="pagebreak-before less_space"] .Quando x e +x não são iguais **** -Todo mumdo espera que `x == +x`, e isso é verdade no Python quase todo o tempo, mas encontrei dois casos na biblioteca padrão onde `x != +x`. +Todo mundo espera que `x == +x`, e isso é verdade no Python quase todo o tempo, +mas encontrei dois casos na biblioteca padrão onde `x != +x`. -O((("decimal.Decimal class"))) primeiro caso envolve a classe `decimal.Decimal`. Você pode obter -`x != +x` se `x` é uma instância de `Decimal`, criada em um dado contexto aritmético e `+x` for então avaliada em um contexto com definições diferentes. Por exemplo, `x` é calculado em um contexto com uma determinada precisão, mas a precisão do contexto é modificada e daí `+x` é avaliado. Veja a uma demonstração no <>. +O((("decimal.Decimal class"))) primeiro caso envolve a classe `decimal.Decimal`. +Você pode obter `x != +x` se `x` é uma instância de `Decimal`, criada em um dado +contexto aritmético e `+x` for então calculada em um contexto com definições +diferentes. Por exemplo, `x` é calculado em um contexto com uma determinada +precisão, mas a precisão do contexto é modificada e daí `+x` é avaliado. +Veja o <>. [[ex_unary_plus_decimal]] .Uma mudança na precisão do contexto aritmético pode fazer `x` se tornar diferente de `+x` @@ -134,13 +223,22 @@ include::../code/16-op-overloading/unary_plus_decimal.py[tags=UNARY_PLUS_DECIMAL <3> Computa `1/3` usando a precisão atual. <4> Inspeciona o resultado; há 40 dígitos após o ponto decimal. <5> `one_third == +one_third` é `True`. -<6> Diminui a precisão para `28`—a precisão default para aritmética com `Decimal`. +<6> Diminui a precisão para `28`—a precisão default de `Decimal`. <7> Agora `one_third == +one_third` é `False`. <8> Inspeciona `+one_third`; aqui há 28 dígitos após o `'.'` . -O fato é que cada ocorrência da expressão `+one_third` produz uma nova instância de `Decimal` a partir do valor de `one_third`, mas usando a precisão do contexto aritmético atual. +O fato é que cada ocorrência da expressão `+one_third` produz uma nova instância +de `Decimal` a partir do valor de `one_third`, mas usando a precisão do contexto +aritmético atual. -Podemos encontrar o segundo caso onde `x != +x` na https://docs.python.org/pt-br/3/library/collections.html#collections.Counter[documentação] de `collections.Counter`. A classe `Counter` implementa vários operadores aritméticos, incluindo o `+` infixo, para somar a contagem de duas instâncias de `Counter`. Entretanto, por razões práticas, a adição em `Counter` descarta do resultado qualquer item com contagem negativa ou zero. E o prefixo `+++` é um atalho para somar um `Counter` vazio, e portanto produz um novo `Counter`, preservando apenas as contagens maiores que zero. Veja o <>. +Encontrei o segundo caso onde `+x != +x+` na +https://fpy.li/34[documentação] de `collections.Counter`. A classe `Counter` +implementa vários operadores aritméticos, incluindo o `{plus}` infixo, para +somar a contagem de duas instâncias de `Counter`. Entretanto, por razões +práticas, a adição em `Counter` descarta do resultado qualquer item com contagem +negativa ou zero. E o `{plus}` unário é um atalho para somar um `Counter` vazio, +produzindo um novo `Counter`, que preserva só as contagens maiores que +zero. Veja o <>. [[ex_unary_plus_counter]] .O + unário produz um novo `Counter`sem as contagens negativas ou zero @@ -159,33 +257,45 @@ Counter({'a': 5, 'b': 2, 'c': 1}) ---- ==== -Como se vê, `+ct` devolve um contador onde todas as contagens são maiores que zero. +Como visto, `+ct` devolve um contador onde todas as contagens são maiores que +zero. -Agora voltamos à nossa programação normal.((("", startref="OOunary16")))((("", startref="unary16"))) +Agora voltamos à nossa programação normal.((("", startref="OOunary16")))((("", +startref="unary16"))) **** [[overloading_plus_sec]] === Sobrecarregando + para adição de Vector -A((("operator overloading", "overloading + for vector addition", id="OOplus16")))((("mathematical vector operations")))((("+ operator", id="Plusover16")))((("vectors", "overloading + for vector addition", id="Voverload16"))) classe `Vector` é um tipo sequência, -e a seção https://docs.python.org/pt-br/3/reference/datamodel.html#emulating-container-types["3.3.7. Emulando de tipos contêineres"] -do capítulo "Modelo de Dados", na documentação oficial do Python, diz que sequências devem suportar o operador -`\+` para concatenação e o `\*` para repetição. -Entretanto, aqui vamos implementar `+` e `*` como operações matemáticas de vetores, algo um pouco mais complicado porém mais útil para um tipo `Vector`. +A((("operator overloading", "overloading + for vector addition", +id="OOplus16")))((("mathematical vector operations")))((("+ operator", +id="Plusover16")))((("vectors", "overloading + for vector addition", +id="Voverload16"))) classe `Vector` é um tipo sequência, e a seção +https://fpy.li/6n[Emulando tipos contêineres] da documentação oficial do Python +diz que sequências devem suportar o operadores `{plus}` para concatenação e `\*` +para repetição. Entretanto, aqui vamos implementar `{plus}` e `*` como operações +matemáticas de vetores, algo um pouco mais complicado porém mais útil para um +tipo `Vector`. [TIP] ==== -Usuários que desejem concatenar ou repetir instâncias de `Vector` podem convertê-las para tuplas ou listas, aplicar o operador e convertê-las de volta—graças ao fato de `Vector` ser iterável e poder ser criado a partir de um iterável: + +Usuários que desejem concatenar ou repetir instâncias de `Vector` podem +convertê-las para tuplas ou listas, aplicar o operador e convertê-las de +volta—graças ao fato de `Vector` ser iterável e poder ser criado a partir de um +iterável: [source, python] ---- ->>> v_concatenated = Vector(list(v1) + list(v2)) ->>> v_repeated = Vector(tuple(v1) * 5) +>>> v_concat = Vector(list(v1) + list(v2)) +>>> v_repeat = Vector(tuple(v1) * 5) ---- + ==== -Somar dois vetores euclidianos resulta em um novo vetor no qual os componentes são as somas pareadas dos componentes dos operandos. Ilustrando: +Somar dois vetores euclidianos resulta em um novo vetor cujos componentes +são as somas dos componentes correspondentes dos operandos. Ilustrando: [source, python] ---- @@ -197,7 +307,10 @@ Vector([9.0, 11.0, 13.0]) True ---- -E o que acontece se tentarmos somar duas instâncias de `Vector` de tamanhos diferentes? Poderíamos gerar um erro, mas considerando as aplicações práticas (tal como recuperação de informação), é melhor preencher o `Vector` menor com zeros. Esse é o resultado que queremos: +E o que acontece se tentarmos somar duas instâncias de `Vector` de tamanhos +diferentes? Poderíamos gerar um erro, mas considerando as aplicações práticas +(tal como recuperação de informação), é melhor preencher o `Vector` menor com +zeros. Esse é o resultado que queremos: [source, python] ---- @@ -207,32 +320,45 @@ E o que acontece se tentarmos somar duas instâncias de `Vector` de tamanhos dif Vector([4.0, 6.0, 5.0, 6.0]) ---- -Dados esses requisitos básicos, podemos implementar `+__add__+` como no <>. +Dados esses requisitos básicos, podemos implementar `+__add__+` como no +<>. [[ex_vector_add_t1]] .Método `+Vector.__add__+`, versão #1 ==== [source, python] ---- - # inside the Vector class + # dentro da classe Vector def __add__(self, other): pairs = itertools.zip_longest(self, other, fillvalue=0.0) # <1> return Vector(a + b for a, b in pairs) # <2> ---- ==== -<1> `pairs` é um gerador que produz tuplas `(a, b)`, onde `a` vem de `self` e `b` de `other`. Se `self` e `other` tiverem tamanhos diferentes, `fillvalue` fornece os valores ausentes para o iterável mais curto. -<2> Um novo `Vector` é criado a partir de uma expressão geradora, produzindo uma soma para cada -`(a, b)` de `pairs`. -Observe como `+__add__+` devolve uma nova instância de `Vector`, sem modificar `self` ou `other`. +<1> `pairs` é um gerador que produz tuplas `(a, b)`, onde `a` vem de `self` e +`b` de `other`. Se `self` e `other` tiverem tamanhos diferentes, `fillvalue` +fornece os valores que faltam no o iterável mais curto. + +<2> Um novo `Vector` é criado a partir de uma expressão geradora, produzindo uma +soma para cada `(a, b)` de `pairs`. + +Note que `+__add__+` devolve uma nova instância de `Vector`, sem modificar +`self` ou `other`. [WARNING] ==== -Métodos especiais implementando operadores unários ou infixos não devem nunca modificar o valor dos operandos. Se espera que expressões com tais operandos produzam resultados criando novos objetos. Apenas operadores de atribuição aumentada podem modidifcar o primeiro operando (`self`), como discutido na <>. + +Métodos especiais implementando operadores unários ou infixos não devem nunca +modificar o valor dos operandos. Espera-se que expressões com tais operandos +produzam resultados criando novos objetos. Só operadores de atribuição +aumentada podem modificar o primeiro operando (`self`), quando ele é mutável, +como discutido na <>. + ==== -O <> permite somar um `Vector` a um `Vector2d`, e `Vector` a uma tupla ou qualquer iterável que produza números, como prova o <>. +O <> permite somar um `Vector` a um `Vector2d`, a +uma tupla, como prova o <>. [[ex_vector_add_demo_mixed_ok]] .Nossa versão #1 de `+Vector.__add__+` também aceita objetos diferentes de ++Vector++ @@ -249,13 +375,17 @@ Vector([4.0, 6.0, 5.0]) ---- ==== -Os dois usos de `+++` no <> funcionam porque `+__add__+` usa -`zip_longest(…)`, capaz de consumir qualquer iterável, e a expressão geradora que cria um novo `Vector` simplesmente efetua a operação `a + b` com os pares produzidos por `zip_longest(…)`, então um iterável que produza quaisquer itens numéricos servirá. +Os dois usos de `{plus}` no <> funcionam porque +`+__add__+` usa `zip_longest(…)`, capaz de consumir qualquer iterável, e a +expressão geradora que cria um novo `Vector` simplesmente efetua a operação `a + +b` com os pares produzidos por `zip_longest(…)`, então qualquer iterável que produza +números compatíveis com `float` servirá. -Entretanto, se trocarmos a ordem dos operandos (no <>), a soma de tipos diferentes falha. +Entretanto, se trocarmos a ordem dos operandos, a soma de tipos diferentes falha. +Veja o <>. [[ex_vector_add_demo_mixed_fail]] -.A versão #1 de `+Vector.__add__+` falha com se o operador da esquerda não for um `Vector +.A versão #1 de `+Vector.__add__+` falha se o operador da esquerda não for um `Vector` ==== [source, python] ---- @@ -273,27 +403,58 @@ TypeError: unsupported operand type(s) for +: 'Vector2d' and 'Vector' ---- ==== -Para suportar operações envolvendo objetos de tipos diferentes, Python implementa um mecanismo especial de despacho para os métodos especiais de operadores infixos. Dada a expressão `a + b`, o interpretador vai executar as seguintes etapas (veja também a <>): +Para suportar operações envolvendo objetos de tipos diferentes, Python +implementa um mecanismo especial de despacho para os métodos especiais de +operadores infixos. Dada a expressão `a + b`, o interpretador executará os +seguintes passos (veja também a <>): + +. Se `a` implementa `+__add__+`, Python invoca `+a.__add__(b)+` e devolve o +resultado, a menos que seja `NotImplemented`. -. Se `a` implementa `+__add__+`, invoca `+a.__add__(b)+` e devolve o resultado, a menos que seja `NotImplemented`. -. Se `a` não implementa `+__add__+`, ou a chamada devolve `NotImplemented`, verifica se `b` implementa `+__radd__+`, e então invoca `+b.__radd__(a)+` e devolve o resultado, a menos que seja `NotImplemented`. -. Se `b` não implementa `+__radd__+`, ou a chamada devolve `NotImplemented`, gera um `TypeError` com a mensagem 'unsupported operand types' (_tipos de operandos não suportados_). +. Se `a` não implementa `+__add__+`, ou a chamada `+a.__add__(b)+` devolve +`NotImplemented`, Python verifica se `b` implementa `+__radd__+`, e então invoca +`+b.__radd__(a)+` e devolve o resultado, a menos que seja `NotImplemented`. + +. Se `b` não implementa `+__radd__+`, ou a chamada `+b.__radd__(a)+` +devolve `NotImplemented`, Python gera um `TypeError` com a mensagem +"unsupported operand types" (tipos de operandos não suportados). [TIP] ==== -O método `+__radd__+` é chamado de variante "reversa" ou "refletida" de `+__add__+`. -Adotei o termo geral "métodos especiais reversos".footnote:[A documentação de Python usa os dois termos. O https://fpy.li/dtmodel[capítulo "Modelo de Dados"] usa "refletido", mas em https://fpy.li/16-7["9.1.2.2. Implementando operações aritméticas"], a documentação do módulo menciona métodos de "adiante" (_forward_) e "reverso" (_reverse_), uma terminologia que considero melhor, pois "adiante" e "reverso" são claramente sentidos opostos, mas o oposto de "refletido" não é tão evidente.] +O método `+__radd__+` é chamado de variante "reversa" ou "refletida" +de `+__add__+`. Adotei o termo geral "métodos especiais reversos". +A documentação de Python usa os dois termos. O +https://fpy.li/dtmodel[capítulo Modelo de Dados] +usa "refletido", mas em +https://fpy.li/16-7[Implementando operações aritméticas], +a documentação do módulo menciona métodos "adiante" (_forward_) +e "reverso" (_reverse_), uma terminologia que considero +melhor, pois "adiante" e "reverso" descrevem sentidos opostos, +mas o oposto de "refletido" não é tão evidente. ==== [[operator_flowchart]] .Fluxograma para computar `a + b` com `+__add__+` e `+__radd__+`. image::../images/flpy_1601.png[Fluxograma de operador] -Assim, para fazer as somas de tipos diferentes no <> funcionarem, precisamos implementar o método `+Vector.__radd__+`, que Python vai invocar como alternativa, se o operando à esquerda não implementar `+__add__+`, ou se implementar mas devolver `NotImplemented`, indicando que não sabe como tratar o operando à direita. +Assim, para fazer as somas de tipos diferentes no +<> funcionarem, precisamos implementar o método +`+Vector.__radd__+`, que Python vai invocar como alternativa, se o operando à +esquerda não implementar `+__add__+`, ou se implementar mas devolver +`NotImplemented`, indicando que não sabe como tratar o operando à direita. [WARNING] ==== -Não confunda `NotImplemented` com `NotImplementedError`. O primeiro é um valor _singleton_ especial, que um método especial de operador infixo deve devolver para informar o interpretador que não consegue tratar um dado operando. `NotImplementedError`, por outro lado, é um exceção que métodos _stub_ em classes abstratas podem gerar, para avisar que subclasses devem implementar tais métodos. + +Não confunda `NotImplemented` com `NotImplementedError`. O primeiro é um valor +_singleton_ especial, que um método especial de operador infixo deve devolver +para informar o interpretador que não consegue tratar um dado operando. + +Por sua vez, `NotImplementedError` é uma exceção que um método abstrato pode +levantar para avisar que subclasses devem sobrescrever este método. Esta exceção +é antiga no Python; atualmente a melhor forma de marcar um método abstrato é +usar o decorador `@abc.abstractmethod`. + ==== A implementação viável mais simples de `+__radd__+` aparece no <>. @@ -303,7 +464,7 @@ A implementação viável mais simples de `+__radd__+` aparece no < pairs = itertools.zip_longest(self, other, fillvalue=0.0) @@ -313,12 +474,19 @@ A implementação viável mais simples de `+__radd__+` aparece no < Nenhuma mudança no `+__add__+` do <>; ele é listado aqui porque é usado por `+__radd__+`. + +<1> Nenhuma mudança no `+__add__+` do <>; ele é listado aqui +porque é usado por `+__radd__+`. + <2> `+__radd__+` apenas delega para `+__add__+`. -Muitas vezes, `+__radd__+` pode ser simples assim: apenas a invocação do operador apropriado, delegando para `+__add__+` neste caso. Isso se aplica para qualquer operador comutativo; `+` é comutativo quando lida com números ou com nossos vetores, mas não é comutativo ao concatenar sequências no Python. +Muitas vezes, `+__radd__+` pode ser simples assim: apenas a invocação do +operador apropriado, delegando para `+__add__+` neste caso. Isso se aplica para +qualquer operador comutativo. O `{plus}` é comutativo quando lida com números ou +com nossos vetores, mas não é comutativo ao concatenar sequências no Python. -Se `+__radd__+` apenas invoca `+__add__+`, aqui está outra forma de obter o mesmo efeito: +Se `+__radd__+` apenas invoca `+__add__+`, aqui está uma forma mais eficiente de +obter o mesmo efeito: [source, python] ---- @@ -329,7 +497,11 @@ Se `+__radd__+` apenas invoca `+__add__+`, aqui está outra forma de obter o mes __radd__ = __add__ ---- -Os métodos no <> funcionam com objetos `Vector` ou com qualquer iterável com itens numéricos, tal como um `Vector2d`, uma `tuple` de inteiros ou um `array` de números de ponto flutuante. Mas se alimentado com um objeto não-iterável, `+__add__+` gera uma exceção com uma mensagem não muito útil, como no <>. +Os métodos no <> funcionam com objetos `Vector` ou com +qualquer iterável com itens numéricos, tal como um `Vector2d`, uma tupla de +inteiros ou um `array` de números de ponto flutuante. Mas se alimentado com um +objeto não-iterável, `+__add__+` gera uma exceção com uma mensagem não muito +útil, como no <>. [[ex_vector_error_iter]] .O método `+Vector.__add__+` precisa de operandos iteráveis @@ -345,10 +517,12 @@ TypeError: zip_longest argument #2 must support iteration ---- ==== -E pior ainda, recebemos uma mensagem enganosa se um operando for iterável mas seus itens não puderem ser somados aos itens `float` no `Vector`. Veja o <>. +E pior ainda, recebemos uma mensagem enganosa se um operando for iterável, +mas seus itens não puderem ser somados aos itens `float` no `Vector`. Veja +o <>. [[ex_vector_error_iter_not_add]] -.O método `+Vector.__add__+` precisa de um iterável com itens numéricos +.O método `+Vector.__add__+` exige um iterável com itens numéricos ==== [source, python] ---- @@ -367,14 +541,26 @@ TypeError: unsupported operand type(s) for +: 'float' and 'str' Tentei somar um `Vector` a uma `str`, mas a mensagem reclama de `float` e `str`. -Na verdade, os problemas no <> e no <> são mais profundos que meras mensagens de erro obscuras: se um método especial de operando não é capaz de devolver um resultado válido por incompatibilidade de tipos, ele deverua devolver `NotImplemented` e não gerar um `TypeError`. Ao devolver `NotImplemented`, a porta fica aberta para a implementação do operando do outro tipo executar a operação, quando Python tentar invocar o método reverso. - -No espírito do _duck typing_, vamos nos abster de testar o tipo do operando `other` ou o tipo de seus elementos. Vamos capturar as exceções e devolver `NotImplemented`. Se o interpretador ainda não tiver invertido os operandos, tentará isso agora. Se a invocação do método reverso devolver `NotImplemented`, então Python irá gerar um `TypeError` com uma mensagem de erro padrão "unsupported operand type(s) for +: 'Vector' and 'str'” (_tipos de operandos não suportados para +: `Vector` e `str`_) +Na verdade, os problemas no <> e no +<> são mais profundos que meras mensagens de erro +obscuras: se um método especial de operando não é capaz de devolver um resultado +válido por incompatibilidade de tipos, ele tem que devolver `NotImplemented` e +não gerar um `TypeError`. Ao devolver `NotImplemented`, a porta fica aberta para +o outro operando executar a operação, quando Python tentar invocar o método +reverso em sua classe. + +No espírito da tipagem pato, não vamos testar o tipo do operando `other` ou o +tipo de seus elementos. Vamos capturar as exceções e devolver `NotImplemented`. +Se o interpretador ainda não tiver invertido os operandos, tentará isso em seguida. +Se a invocação do método reverso devolver `NotImplemented`, então Python vai +gerar um `TypeError` com uma mensagem de erro padrão "unsupported operand +type(s) for +: 'Vector' and 'str'” (_tipos de operandos não suportados para +: +`Vector` e `str`_) A implementação final dos métodos especiais de adição de `Vector` está no <>. [[ex_vector_v6]] -.vector_v6.py: métodos do operador `+` adicionados a vector_v5.py (no <>) +.vector_v6.py: métodos do operador `{plus}` adicionados a vector_v5.py (<> do <>) ==== [source, python] ---- @@ -386,15 +572,26 @@ Observe que agora `+__add__+` captura um `TypeError` e devolve `NotImplemented`. [WARNING] ==== -Se um método de operador infixo gera uma exceção, ele interrompe o algoritmo de despacho do operador. No caso específico de `TypeError`, geralmente é melhor capturar essa exceção e devolver `NotImplemented`. Isso permite que o interpretador tente chamar o método reverso do operador, que pode tratar corretamente a operação com operadores invertidos, se eles forem de tipos diferentes. + +Se um método de operador infixo gera uma exceção, ele interrompe o algoritmo de +despacho do operador. No caso específico de `TypeError`, geralmente é melhor +capturar esta exceção e devolver `NotImplemented`. Isto permite que o +interpretador tente chamar o método reverso do segundo operando. + ==== -Agora que já sobrecarregamos o operador `+` com segurança, implementando `+__add__+` e `+__radd__+`, vamos enfrentar outro operador infixo: `*`.((("", startref="OOplus16")))((("", startref="Plusover16")))((("", startref="Voverload16"))) +Agora que já sobrecarregamos o operador `{plus}` com segurança, implementando `+__add__+` e `+__radd__+`, vamos enfrentar outro operador infixo: `*`.((("", startref="OOplus16")))((("", startref="Plusover16")))((("", startref="Voverload16"))) -[[overloading_mul]] -=== Sobrecarregando * para multiplicação escalar +[[overloading_mul_sec]] +=== Sobrecarregando * para multiplicação por escalar -O((("operator overloading", "overloading * for scalar multiplication", id="OOscalar16")))((("* (star) operator", id="starover16")))((("star (*) operator", id="staroverb16")))((("multiplication, scalar", id="Mscalar16"))) que significa `Vector([1, 2, 3]) * x`? Se `x` é um número, isso seria um produto escalar, e o resultado seria um novo `Vector` com cada componente multiplicado por `x`—também conhecida como multiplicação elemento a elemento (_elementwise multiplication_): +O((("operator overloading", "overloading * for scalar multiplication", +id="OOscalar16")))((("* (star) operator", id="starover16")))((("star (*) +operator", id="staroverb16")))((("multiplication, scalar", id="Mscalar16"))) que +significa `Vector([1, 2, 3]) * x`? Se `x` é um número escalar, isto é uma +"multiplicação por escalar", e o resultado deve ser um novo `Vector` com cada +componente multiplicado por `x`—também conhecida como multiplicação elemento a +elemento (_elementwise multiplication_): [source, python] ---- @@ -407,15 +604,23 @@ Vector([11.0, 22.0, 33.0]) [NOTE] ==== -Outro tipo de produto envolvendo operandos de `Vector` seria o _dot product_ (produto vetorial) de dois vetores—ou multiplicação de matrizes, se tomarmos um vetor como uma matriz de 1 × N e o outro como uma matriz de N × 1. -Vamos implementar esse operador em nossa classe `Vector` na <>. + +Outro tipo de multiplicação envolvendo vetores é o produto escalar +(_dot product_). Os operandos de um produto escalar são dois vetores, +e o resultado é um número escalar (não um vetor). +É como uma multiplicação de matrizes, considerando um +vetor como uma matriz de 1 × N e o outro como uma matriz de N × 1. +Implementaremos o produto escalar em `Vector` na +<>. + ==== -De volta a nosso produto escalar, começamos novamente com os métodos `+__mul__+` e `+__rmul__+` mais simples possíveis que possam funcionar: +Voltando à nossa multiplicação por escalar, começamos novamente com os métodos +`+__mul__+` e `+__rmul__+` mais simples possíveis que possam funcionar: [source, python] ---- - # inside the Vector class + # dentro da classe Vector def __mul__(self, scalar): return Vector(n * scalar for n in self) @@ -424,10 +629,15 @@ De volta a nosso produto escalar, começamos novamente com os métodos `+__mul__ return self * scalar ---- -Esses métodos funcionam, exceto quando recebem operandos incompatíveis. O argumento `scalar` precisa ser um número que, quando multiplicado por um `float`, produz outro `float` (porque nossa classe `Vector` usa, internamente, um `array` de números de ponto flutuante). -Então um número `complex` não serve, mas o escalar pode ser um `int`, um `bool` (porque `bool` é subclasse de `int`) ou mesmo uma instância de `fractions.Fraction`. -No <>, o método `+__mul__+` não faz qualquer checagem de tipos explícita com `scalar`. Em vez disso, o converte em um `float`, e devolve `NotImplemented` se a conversão falha. -Esse é um exemplo claro de _duck typing_. +Estes métodos funcionam, exceto quando recebem operandos incompatíveis. + +O argumento `scalar` precisa ser um número que, quando multiplicado por um +`float`, produz outro `float` (porque nossa classe `Vector` armazena +um `array` de números de ponto flutuante). Então um `complex` não serve, +mas pode ser um `int`, um `bool` (`bool` é subclasse de `int`) +ou até uma instância de `fractions.Fraction`. No <>, o método +`+__mul__+` não faz nenhuma checagem de tipos explícita com `scalar`. Em vez +disso, o converte para `float`, e devolve `NotImplemented` se a conversão +falhar. É mais um exemplo prático de tipagem pato. [[ex_vector_v7]] .vector_v7.py: métodos do operador `*` adicionados @@ -440,8 +650,8 @@ class Vector: def __init__(self, components): self._components = array(self.typecode, components) - # many methods omitted in book listing, see vector_v7.py - # in https://github.com/fluentpython/example-code-2e + # vários métodos omitidos no livro; código completo em + # https://github.com/fluentpython/example-code-2e def __mul__(self, scalar): try: @@ -455,10 +665,15 @@ class Vector: ---- ==== <1> Se `scalar` não pode ser convertido para `float`... -<2> ...não temos como lidar com ele, então devolvemos `NotImplemented`, para permitir ao Python tentar `+__rmul__+` no operando `scalar`. -<3> Neste exemplo, `+__rmul__+` funciona bem apenas executando `self * scalar`, que delega a operação para o método `+__mul__+`. -Com o <>, é possível multiplicar um `Vector` por valores escalares de tipos numéricos comuns e não tão comuns: +<2> ...não temos como lidar com ele, então devolvemos `NotImplemented`, para +permitir ao Python tentar `+__rmul__+` no operando `scalar`. + +<3> Neste exemplo, `+__rmul__+` funciona bem apenas executando `self * scalar`, +que delega a operação para o método `+__mul__+`. + +Com o <>, é possível multiplicar um `Vector` por valores escalares +de tipos numéricos comuns e não tão comuns: [source, python] ---- @@ -472,39 +687,59 @@ Vector([1.0, 2.0, 3.0]) Vector([0.3333333333333333, 0.6666666666666666, 1.0]) ---- -Agora que podemos multiplicar `Vector` por valores escalares, vamos ver como implementar o produto de um `Vector` por outro `Vector`. +Agora que podemos multiplicar `Vector` por valores escalares, vamos ver como +implementar o produto de um `Vector` por outro `Vector`. [NOTE] ==== -Na primeira edição de _Python Fluente_, usei _goose typing_ no <>: checava o argumento `scalar` de `+__mul__+` com `isinstance(scalar, numbers.Real)`. -Agora eu evito usar as ABCs de `numbers`, por não serem suportadas pelas anotações de tipo introduzidas na PEP 484. -Usar durante a execução tipos que não podem ser também checados de forma estática me parece uma má ideia. -Outra alternativa seria checar com o protocolo `typing.SupportsFloat`, que vimos na <>. Escolhi usar _duck typing_ naquele exemplo por achar que pythonistas fluentes devem se sentir confortáveis com esse padrão de programação. +Na primeira edição de _Python Fluente_, usei tipagem ganso no <>: +checava o argumento `scalar` de `+__mul__+` com `isinstance(scalar, +numbers.Real)`. Agora evito usar as ABCs de `numbers`, por não serem +suportadas pelas anotações de tipo introduzidas na PEP 484. Usar durante a +execução tipos que não podem ser também checados de forma estática me parece uma +má ideia. + +Outra alternativa seria checar com o protocolo `typing.SupportsFloat`, que vimos +na <>. Escolhi usar tipagem pato naquele exemplo +por considerar que pythonistas fluentes devem se sentir confortáveis com esse +padrão de programação. + +Mas `+__matmul__+`, no <>, que é novo e foi escrito para +essa segunda edição, é um bom exemplo de tipagem ganso.((("", +startref="starover16")))((("", startref="staroverb16")))((("", +startref="OOscalar16")))((("", startref="Mscalar16"))) -Mas `+__matmul__+`, no <>, que é novo e foi escrito para essa segunda edição, é um bom exemplo de _goose typing_.((("", startref="starover16")))((("", startref="staroverb16")))((("", startref="OOscalar16")))((("", startref="Mscalar16"))) ==== [[matmul_operator_sec]] === Usando @ como operador infixo -O símbolo `@`((("operator overloading", "using @ as infix operator", id="OOatsign16")))((("@ sign", id="atinfix16")))((("infix operators", id="infixop16"))) é bastante conhecido como o prefixo de decoradores de função, -mas desde 2015 ele também pode ser usado como um operador infixo. -Por anos, o produto escalar no NumPy foi escrito como `numpy.dot(a, b)`. -A notação de invocação de função faz com que fórmulas mais longas sejam difíceis de traduzir da notação matemática para Python,footnote:[Veja o <> para uma discussão desse problema.] -então a comunidade de computação numérica fez campanha pela -https://fpy.li/pep465[PEP 465—A dedicated infix operator for matrix multiplication (_Um operador infixo dedicado para multiplicação de matrizes_)] (EN), que foi implementada no Python 3.5. -Hoje é possível escrever `a @ b` para computar o produto escalar de dois arrays da NumPy. - -O operador `@` é suportado pelos métodos especiais `+__matmul__+`, `+__rmatmul__+` -e `+__imatmul__+`, cujos nomes derivam de "matrix multiplication" (_multiplicação de matrizes_). -Até Python 3.10, esses métodos não são usados em lugar algum na biblioteca padrão, -mas eles são reconhecidos pelo interpretador desde o Python 3.5, -então os desenvolvedores da NumPy--e o resto de nós--podemos implementar o operador `@` em nossas classes. -O analisador sintático de Python também foi modificado para aceitar o novo operador -(no Python 3.4, `a @ b` era um erro de sintaxe). - -Os testes simples abaixo mostram como `@` deve funcionar com instâncias de `Vector`: +O símbolo `@`((("operator overloading", "using @ as infix operator", +id="OOatsign16")))((("@ sign", id="atinfix16")))((("infix operators", +id="infixop16"))) é o prefixo de decoradores de função, mas desde 2015 +também pode ser usado como um operador infixo. + +Por muitos anos, o produto escalar (_dot product_) era escrito +como `numpy.dot(a, b)` na biblioteca NumPy. +A notação de invocação de função faz com que fórmulas mais longas sejam difíceis +de traduzir da notação matemática para Python,footnote:[Veja o +<> para uma discussão deste problema.] então a comunidade de +computação numérica fez campanha pela +https://fpy.li/pep465[_PEP 465—A dedicated infix operator for matrix multiplication_] +(Um operador infixo dedicado para multiplicação de matrizes), +que foi implementada no Python 3.5. Hoje podemos escrever `a @ b` +para computar o produto de dois arrays da NumPy. + +O operador `@` é suportado pelos métodos especiais `+__matmul__+`, +`+__rmatmul__+` e `+__imatmul__+`, cujos nomes derivam de "matrix +multiplication". Até o Python 3.10, estes métodos não são usados em lugar algum +na biblioteca padrão, mas são reconhecidos pelo interpretador desde o Python +3.5, então nós e os desenvolvedores da NumPy podemos implementar o operador +`@` em nossas classes. O analisador sintático de Python foi modificado para +aceitar o novo operador, pois `a @ b` era um erro de sintaxe até o Python 3.4. + +Estes testes simples mostram como `@` deve funcionar com instâncias de `Vector`: [source, python] ---- @@ -535,12 +770,12 @@ O <> mostra o código dos métodos especiais relevantes na [[ex_vector_v7_matmul]] -.vector_v7.py: operator `@` methods +.vector_v7.py: métodos para o operador `@` ==== [source, python] ---- class Vector: - # many methods omitted in book listing + # vários métodos omitidos nesta listagem def __matmul__(self, other): if (isinstance(other, abc.Sized) and # <1> @@ -564,35 +799,55 @@ class Vector: .O novo recurso de zip() no Python 3.10 [TIP] ==== -Desde o Python 3.10, a função embutida `zip` aceita um argumento opcional apenas nomeado, `strict`. Quando `strict=True`, a função gera um `ValueError` se os iteráveis têm tamanhos diferentes. O default é `False`. -Esse novo comportamento estrito se alinha à filosofia de https://fpy.li/16-8[_falhar rápido_] de Python. -No <>, substituí o `if` interno por um `try/except ValueError` e acrescentei `strict=True` à invocação de `zip`. -==== -O <> é um bom exemplo prático de _goose typing_. -Testar o operando `other` contra `Vector` negaria aos usuários a flexibilidade de usar listas ou arrays como operandos de `@`. -Desde que um dos operandos seja um `Vector`, nossa implementação de `@` suporta outros operandos que sejam instâncias de `abc.Sized` e `abc.Iterable`. -Ambas as ABCs implementam o `+__subclasshook__+`, portanto qualquer objeto que forneça `+__len__+` e `+__iter__+` satisfaz nosso teste—não há necessidade de criar subclasses concretas dessas ABCs ou sequer registrar-se com elas, como explicado na <>. -Em especial, nossa classe `Vector` não é subclasse nem de `abc.Sized` nem de `abc.Iterable`, mas passa os testes de `isinstance` contra aquelas ABCs, pois implementa os métodos necessários. +Desde o Python 3.10, a função `zip` aceita um argumento opcional apenas +nomeado, `strict`. Quando `strict=True`, a função gera um `ValueError` +se um iterável termina antes de outro. O default é `False`. Este comportamento +se alinha à filosofia de https://fpy.li/16-8[«falhar rápido»] de Python. +No <>, poderíamos trocar o `if` interno por um `try/except +ValueError` e acrescentar `strict=True` à invocação de `zip`. +Neste caso específico, como `self` e `other` suportam `+__len__+`, +prefiro o teste explícito com `if`, por clareza. +O `strict` é mais útil quando o `zip` vai lidar com iteradores, +que não têm `+__len__+`. + +==== -Vamos revisar os operadores aritméticos suportados pelo Python antes de mergulhar na categoria especial dos <>.((("", startref="atinfix16")))((("", startref="OOatsign16"))) +O <> é um bom exemplo prático de tipagem ganso. Não usamos +`isinstance(other, Vector)`, porque queremos oferecer mais flexibilidade para os +usuários. Suportamos operandos que sejam instâncias de `abc.Sized` e +`abc.Iterable`. Estas duas ABCs implementam o `+__subclasshook__+`, portanto +qualquer objeto que forneça `+__len__+` e `+__iter__+` satisfaz nosso teste—não +há necessidade de criar subclasses concretas dessas ABCs ou sequer registrar-se +com elas, como explicado na <>. Em particular, nossa classe +`Vector` não é subclasse nem de `abc.Sized` nem de `abc.Iterable`, mas passa os +testes de `isinstance` contra aquelas ABCs, pois implementa os métodos +necessários. + +Vamos revisar os operadores aritméticos suportados pelo Python antes de +mergulhar na categoria especial dos operadores de comparação rica +(<>).((("", startref="atinfix16")))((("", startref="OOatsign16"))) === Resumindo os operadores aritméticos -Ao implementar `+`, `*`, e `@`, vimos((("operator overloading", "infix operator method names"))) os padrões de programação mais comuns para operadores infixos. -As técnicas descritas são aplicáveis a todos os operadores listados na <> (os operadores internos serão tratados em <>). +Ao implementar `{plus}`, `*`, e `@`, vimos((("operator overloading", "infix +operator method names"))) os padrões de programação mais comuns para operadores +infixos. As técnicas descritas são aplicáveis a todos os operadores listados na +<> (os operadores de atribuição aritmética serão tratados na +<>). [[infix_operator_names_tbl]] .Nomes dos métodos de operadores infixos (os operadores internos são usados para atribuição aumentada; operadores de comparação estão na <>) [options="header"] +[cols="18,25,27,27,50"] |================================================================================================= -| Operador | Direto | Reverso | Interno | Descrição -| `+` | `+__add__+` | `+__radd__+` | `+__iadd__+` | Adição ou concatenação +| op | direto | reverso | interno | descrição +| `{plus}` | `+__add__+` | `+__radd__+` | `+__iadd__+` | Adição ou concatenação | `-` | `+__sub__+` | `+__rsub__+` | `+__isub__+` | Subtração | `*` | `+__mul__+` | `+__rmul__+` | `+__imul__+` | Multiplicação ou repetição -| `/` | `+__truediv__+` | `+__rtruediv__+` | `+__itruediv__+` | Divisão exata (_True division_) -| `//` | `+__floordiv__+` | `+__rfloordiv__+` | `+__ifloordiv__+` | Divisão inteira (_Floor division_) -| `%` | `+__mod__+` | `+__rmod__+` | `+__imod__+` | Módulo +| `/` | `+__truediv__+` | `+__rtruediv__+` | `+__itruediv__+` | Divisão exata +| `//` | `+__floordiv__+` | `+__rfloordiv__+` | `+__ifloordiv__+` | Divisão inteira +| `%` | `+__mod__+` | `+__rmod__+` | `+__imod__+` | Módulo (resto) | `divmod()`| `+__divmod__+` | `+__rdivmod__+` | `+__idivmod__+` | Devolve uma tupla com o quociente da divisão inteira e o módulo | `**`, `pow()` | `+__pow__+` | `+__rpow__+` | `+__ipow__+` | Exponenciaçãofootnote:[`pow` pode receber um terceiro argumento opcional, `modulo`: `pow(a, b, modulo)`, também suportado pelos métodos especiais quando invocados diretamente (por exemplo, `+a.__pow__(b, modulo)+`).] | `@` | `+__matmul__+` | `+__rmatmul__+` | `+__imatmul__+` | Multiplicação de matrizes @@ -604,47 +859,61 @@ As técnicas descritas são aplicáveis a todos os operadores listados na <`, `<`, `>=` e `<=` pelo interpretador Python é similar ao que já vimos, com duas importantes diferenças: - -* O mesmo conjunto de métodos é usado para invocações diretas ou reversas do operador. As regras estão resumidas na <>. Por exemplo, no caso de `==`, tanto a chamada direta quanto a reversa invocam `+__eq__+`, apenas permutando os argumentos; e uma chamada direta a -`+__gt__+` é seguida de uma chamada reversa a `+__lt__+`, com os argumentos permutados. -* Nos casos de `==` e `!=`, se o métodos reverso estiver ausente, ou devolver `NotImplemented`, Python vai comparar os IDs dos objetos em vez de gerar um `TypeError`. +O((("operator overloading", "rich comparison operators", +id="OOrich16")))((("rich comparison operators", id="richcomp16")))((("comparison operators", +id="comop16"))) tratamento +dos operadores de comparação rica `==`, `!=`, `>`, `<`, `>=` e `{lte}` pelo +interpretador Python é similar ao que já vimos, com uma importante diferença: +não existem métodos reversos com o prefixo `+__r…__+`. +Os mesmos métodos são usados para invocações diretas ou reversas do +operador. As regras estão resumidas na <>. [[reversed_rich_comp_op_tbl]] -.Operadores de comparação cheia: métodos reversos invocados quando a chamada inicial ao método devolve `NotImplemented` +.Comparação rica: a última coluna mostra o resultado quando as tentativas devolvem `NotImplemented` ou o operando não implementa o método. [options="header"] +[cols="22,13,23,23,50"] |================================================================================================= -| Grupo | Operador infixo | Método de invocação direta | Método de invocação reversa | Alternativa -| Igualdade | `a == b` | `+a.__eq__(b)+` | `+b.__eq__(a)+` | Devolve `id(a) == id(b)` +| grupo | op | invocação direta | invocação reversa | quando não implementado +| *igualdade* | `a == b` | `+a.__eq__(b)+` | `+b.__eq__(a)+` | Devolve `id(a) == id(b)` | | `a != b` | `+a.__ne__(b)+` | `+b.__ne__(a)+` | Devolve `not (a == b)` -| Ordenação | `a > b` | `+a.__gt__(b)+` | `+b.__lt__(a)+` | Gera um `TypeError` -| | `a < b` | `+a.__lt__(b)+` | `+b.__gt__(a)+` | Gera um `TypeError` -| | `a >= b` | `+a.__ge__(b)+` | `+b.__le__(a)+` | Gera um `TypeError` -| | `a <= b` | `+a.__le__(b)+` | `+b.__ge__(a)+` | Gera um `TypeError` +| *ordenação* | `a > b` | `+a.__gt__(b)+` | `+b.__lt__(a)+` | Levanta `TypeError` +| | `a < b` | `+a.__lt__(b)+` | `+b.__gt__(a)+` | Levanta `TypeError` +| | `a >= b` | `+a.__ge__(b)+` | `+b.__le__(a)+` | Levanta `TypeError` +| | `a {lte} b` | `+a.__le__(b)+` | `+b.__ge__(a)+` | Levanta `TypeError` |================================================================================================= -Dadas essas regras, vamos revisar e aperfeiçoar o comportamento do método `+Vector.__eq__+`, que foi escrito assim no __vector_v5.py__ (<>): +Por exemplo, no caso de `==`, tanto a chamada direta quanto a reversa invocam +`+__eq__+`, apenas permutando os argumentos. Uma chamada direta a `+__gt__+` +pode ser seguida de uma chamada reversa a `+__lt__+`, com os argumentos +permutados. + +Nos casos de `==` e `!=`, se o método não existe no segundo operando, +ou devolve `NotImplemented`, os métodos correspondentes `+__eq__+` e `+__ne__+` +herdados da classe `object` comparam os IDs dos objetos, então não ocorre `TypeError`. + +Considerando estas regras, vamos revisar e aperfeiçoar o comportamento do método +`+Vector.__eq__+`, escrito assim no __vector_v5.py__ (<> do <>): [source, python] ---- class Vector: - # many lines omitted + # várias linhas omitidas def __eq__(self, other): return (len(self) == len(other) and all(a == b for a, b in zip(self, other))) ---- -Esse método produz os resultados do <>. +Este método produz os resultados do <>. [[eq_initial_demo]] -.Comparando um `Vector` a um `Vector`, a um `Vector2d`, e a uma `tuple` +.Comparando um `Vector` a um `Vector`, a um `Vector2d`, e a uma tupla ==== [source, python] ---- @@ -664,22 +933,28 @@ True ==== <1> Duas instâncias de `Vector` com componentes numéricos iguais são iguais. <2> Um `Vector` e um `Vector2d` também são iguais se seus componentes são iguais. -<3> Um `Vector` também é considerado igual a uma `tuple` ou qualquer iterável com itens numéricos de valor igual. +<3> Um `Vector` também é considerado igual a uma tupla ou qualquer sequência +com itens escalares de valor igual. -O resultado no <> é provavelmente indesejável. -Queremos mesmo que um `Vector` seja considerado igual a uma `tuple` contendo os mesmos números? -Não tenho uma regra fixa sobre isso; depende do contexto da aplicação. -O "Zen of Python" diz: +O último resultado no <> pode ser indesejável. Queremos mesmo +que um `Vector` seja considerado igual a uma tupla contendo os mesmos números? +Não tenho uma regra fixa sobre isso; depende do contexto da aplicação. O "Zen of +Python" diz: [quote] ____ Em face da ambiguidade, rejeite a tentação de adivinhar. ____ -Liberalidade excessiva na avaliação de operandos pode levar a resultados surpreendentes, e programadores odeiam surpresas. +Liberalidade excessiva na avaliação de operandos pode levar a resultados +surpreendentes, e programadores odeiam surpresas. -Buscando inspiração no próprio Python, vemos que `[1,2] == (1, 2)` é `False`. Então, vamos ser conservadores e executar alguma checagem de tiposs. Se o segundo operando for uma instância de `Vector` (ou uma instância de uma subclasse de `Vector`), então usaremos a mesma lógica do -`+__eq__+` atual. Caso contrário, devolvemos `NotImplemented` e deixamos Python cuidar do caso. Veja o <>. +Buscando inspiração no próprio Python, vemos que `[1, 2] == (1, 2)` é `False`. +Então, seremos conservadores e faremos checagem de tipos. Se o +segundo operando for uma instância de `Vector` (ou uma instância de uma +subclasse de `Vector`), então usaremos a mesma lógica do `+__eq__+` atual. Caso +contrário, devolvemos `NotImplemented` e deixamos Python cuidar do caso. Veja o +<>. [[ex_vector_v8_eq]] .vector_v8.py: `+__eq__+` aperfeiçoado na classe `Vector` @@ -689,10 +964,14 @@ Buscando inspiração no próprio Python, vemos que `[1,2] == (1, 2)` é `False` include::../code/16-op-overloading/vector_v8.py[tags=VECTOR_V8_EQ] ---- ==== -<1> Se o operando `other` é uma instância de `Vector` (ou de uma subclasse de `Vector`), executa a comparação como antes. + +<1> Se o operando `other` é uma instância de `Vector` (ou de uma subclasse de +`Vector`), executa a comparação como antes. + <2> Caso contrário, devolve `NotImplemented`. -Rodando os testes do <> com o novo `+Vector.__eq__+` do <>, obtemos os resultados que aparecem no <>. +Rodando os testes do <> com o novo `+Vector.__eq__+` do +<>, obtemos os resultados do <>. [[eq_demo_new_eq]] .Mesmas comparações do <>: o último resultado mudou @@ -713,30 +992,52 @@ True False ---- ==== -<1> Mesmo resultado de antes, como esperaado. -<2> Mesmo resultado de antes, mas por que? Explicação a seguir. -<3> Resultado diferente; era o que queríamos. Mas por que isso funciona? Continue lendo... +<1> Mesmo resultado de antes, como esperado. +<2> Mesmo resultado de antes, mas por quê? Explicação a seguir. +<3> Resultado diferente; era o que queríamos. Mas por que isso funciona? +Continue lendo... -Dos três resultados no <>, o primeiro não é novidade, mas os dois últimos foram causados por `+__eq__+` devolver `NotImplemented` no <>. Eis o que acontece no exemplo com um `Vector` e um `Vector2d`, `vc == v2d`, passo a passo: +Dos três resultados no <>, o primeiro não é novidade, mas os +dois últimos foram causados por `+__eq__+` devolver `NotImplemented` no +<>. Eis o que acontece no exemplo com um `Vector` e um +`Vector2d`, `vc == v2d`, passo a passo: . Para avaliar `vc == v2d`, Python invoca `Vector.__eq__(vc, v2d)`. -. `+Vector.__eq__(vc, v2d)+` verifica que `v2d` não é um `Vector` e devolve `NotImplemented`. -. Python recebe o resultado `NotImplemented`, então tenta -`+Vector2d.__eq__(v2d, vc)+`. -. `+Vector2d.__eq__(v2d, vc)+` transforma os dois operandos em tuplas e os compara: o resulltado é `True` (o código de `+Vector2d.__eq__+` está no <>). -Já para a comparação `va == t3`, entre `Vector` e `tuple` no <>, os passos são: +. `+Vector.__eq__(vc, v2d)+` verifica que `v2d` não é um `Vector` e devolve +`NotImplemented`. + +. Diante do resultado `NotImplemented`, Python tenta `+Vector2d.__eq__(v2d, +vc)+`. + +. `+Vector2d.__eq__(v2d, vc)+` transforma os dois operandos em tuplas e os +compara: o resultado é `True` (o código de `+Vector2d.__eq__+` está no +<> do <>). + +Já para a comparação `va == t3`, entre `Vector` e `tuple` no <>, +os passos são: . Para avaliar `va == t3`, Python invoca `+Vector.__eq__(va, t3)+`. -. `+Vector.__eq__(va, t3)+` verifica que `t3` não é um `Vector` e devolve `NotImplemented`. -. Python recebe o resultado `NotImplemented`, e então tenta `+tuple.__eq__(t3, va)+`. -. `+tuple.__eq__(t3, va)+` não tem a menor ideia do que seja um `Vector`, então devolve `NotImplemented`. -. No caso especial de `==`, se a chamada reversa devolve `NotImplemented`, Python compara os IDs dos objetos, como último recurso. -Não precisamos implementar `+__ne__+` para `!=`, pois o comportamento alternativo do `+__ne__+` herdado de `object` nos serve: -quando `+__eq__+` é definido e não devolve `NotImplemented`, `+__ne__+` devolve o mesmo resultado negado. +. `+Vector.__eq__(va, t3)+` verifica que `t3` não é um `Vector` e devolve +`NotImplemented`. + +. Diante do resultado `NotImplemented`, Python tenta `+tuple.__eq__(t3, +va)+`. + +. `+tuple.__eq__(t3, va)+` não tem a menor ideia do que seja um `Vector`, então +devolve `NotImplemented`. + +. No caso especial de `==`, se a chamada reversa devolve `NotImplemented`, +Python compara os IDs dos objetos, como último recurso. -Em outras palavras, dados os mesmos objetos que usamos no <>, os resultados para `!=` são consistentes: +Não precisamos implementar `+__ne__+` para `!=`, pois o comportamento +alternativo do `+__ne__+` herdado de `object` nos serve: quando `+__eq__+` é +definido e não devolve `NotImplemented`, `+__ne__+` devolve a negação booleana +do resultado de `+__eq__+`. + +Em outras palavras, dados os mesmos objetos que usamos no <>, os +resultados de `!=` são consistentes: [source, python] ---- @@ -748,8 +1049,10 @@ False True ---- -O `+__ne__+` herdado de `object` funciona como o código abaixo—exceto pelo original estar escrito em C:footnote:[A lógica para `+object.__eq__+` e -`+object.__ne__+` está na função `object_richcompare` em https://fpy.li/16-9[_Objects/typeobject.c_], no código-fonte do CPython.] +O `+__ne__+` herdado de `object` funciona como o código abaixo (mas +o original é escrito em C):footnote:[A lógica de `+object.__eq__+` e +`+object.__ne__+` está na função `object_richcompare` em +https://fpy.li/16-9[_Objects/typeobject.c_], no código-fonte do CPython.] [source, python] ---- @@ -761,18 +1064,26 @@ O `+__ne__+` herdado de `object` funciona como o código abaixo—exceto pelo or return not eq_result ---- -Vimos o básico da sobrecarga de operadores infixos.Vamos agora voltar nossa atenção para uma classe diferente de operador: os operadores de atribuição aumentada.((("", startref="comop16")))((("", startref="richcomp16")))((("", startref="OOrich16"))) +Vimos o básico da sobrecarga de operadores infixos. +Agora veremos uma categoria diferente: os operadores de atribuição +aumentada.((("", startref="comop16")))((("", startref="richcomp16")))((("", +startref="OOrich16"))) [[augmented_assign_ops]] === Operadores de atribuição aumentada -Nossa((("operator overloading", "augmented assignment operators", id="OOaugmented16")))((("augmented assignment operators", id="augmented16")))((("+= (addition assignment) operator", id="addassigna16")))((("*= (star equals) operator", id="stareqa16")))((("addition assignment (+=) operator", id="adassb16")))((("star equals (*=) operator", id="stareqb16"))) classe `Vector` já suporta os operadores de atribuição aumentada `+=` e `*=`. Isso se dá porque a atribuição aumentada trabalha com recipientes imutáveis criando novas instâncias e re-vinculando a variável à esquerda do operador. +Nossa((("operator overloading", "augmented assignment operators", +id="OOaugmented16")))((("augmented assignment operators", +id="augmented16"))) +classe `Vector` já suporta os operadores de atribuição aumentada `{iadd}` e `*=`. +Isso acontece porque a atribuição aumentada trabalha com sequências imutáveis +criando novas instâncias e re-vinculando a variável à esquerda do operador. O <> os mostra em ação. [[eq_demo_augm_assign_immutable]] -.Usando `+=` e `*=` com instâncias de `Vector` +.Usando `{iadd}` e `*=` com instâncias de `Vector` ==== [source, python] ---- @@ -794,155 +1105,289 @@ Vector([55.0, 77.0, 99.0]) 4302858336 ---- ==== -<1> Cria um alias, para podermos inspecionar o objeto `Vector([1, 2, 3])` mais tarde. + +<1> Cria um alias, para podermos inspecionar o objeto `Vector([1, 2, 3])` mais +tarde. + <2> Verifica o `id` do `Vector` inicial, vinculado a `v1`. + <3> Executa a adição aumentada. + <4> O resultado esperado... + <5> ...mas foi criado um novo `Vector`. -<6> Inspeciona `v1_alias` para confirmar que o `Vector` original não foi alterado. + +<6> Inspeciona `v1_alias` para confirmar que o `Vector` original não foi +alterado. + <7> Executa a multiplicação aumentada. + <8> Novamente, o resultado é o esperado, mas um novo `Vector` foi criado. -Se uma classe não implementa os operadores internos listados na <>, os operadores de atribuição aumentada funcionam como açúcar sintático: `a += b` é avaliado exatamente como `a = a + b`. Esse é o comportamento esperado para tipos imutáveis, e se você fornecer -`+__add__+`, então `+=` funcionará sem qualquer código adicional. +Se uma classe não implementa os métodos internos listados na +<>, os operadores de atribuição aumentada funcionam +como açúcar sintático: `+a += b+` é avaliado exatamente como `+a = a + b+`. Este +é o comportamento esperado para tipos imutáveis, e se você fornecer `+__add__+`, +então `{iadd}` funcionará sem qualquer código adicional. -Entretanto, se você implementar um operador interno tal como -`+__iadd__+`, aquele método será chamado para computar o resultado de `a += b`. -Como indica seu nome, espera-se que esses operadores modifiquem internamente o operando da esquerdafootnote:[NT: O "i" nos nomes desses operadores se refere a "_in-place_"], e não criem um novo objeto como resultado. +Entretanto, se você implementar um método interno tal como `+__iadd__+`, +aquele método será chamado para computar o resultado de `a += b`. Como indica +seu nome, espera-se que esses operadores modifiquem internamente o operando da +esquerdafootnote:[NT: O prefixo "i" nos nomes destes métodos se refere a +_in-place_, traduzido como "interno" na documentação brasileira oficial de Python.], +e não criem um novo objeto como resultado. [WARNING] ==== -Os métodos especiais de atualização interna não devem nunca ser implementados para tipos imutáveis como nossa classe `Vector`. -Pode ser óbvio, mas vale a pena enfatizar. + +Nunca devemos implementar métodos internos para atribuição aumentada +em tipos imutáveis como nossa classe `Vector`. Pode ser óbvio, mas vale a pena +enfatizar. Por este motivo, deixaremos de lado o tema dos vetores nos próximos +exemplos. + ==== -Para mostrar o código de um operador de atualização interno, vamos estender a classe `BingoCage` do <> para implementar +Para mostrar o código de um método interno de atribuição aumentada, vamos +estender a classe `BingoCage` do <> do <> para implementar `+__add__+` e `+__iadd__+`. -Vamos chamar a subclasse de `AddableBingoCage`. O <> mostra o comportamento esperado para o operador `+`. +Vamos chamar a subclasse de `AddableBingoCage`. Os doctests da classe +(<>) +mostram o comportamento esperado do operador `{plus}`. [[demo_addable_bingo_add]] -.O operador `+` cria uma nova instância de `AddableBingoCage` +.O operador `{plus}` cria uma nova instância de `AddableBingoCage` ==== [source, python] ---- include::../code/16-op-overloading/bingoaddable.py[tags=ADDABLE_BINGO_ADD_DEMO] ---- ==== + <1> Cria uma instância de `globe` com cinco itens (cada uma das `vowels`). + <2> Extrai um dos itens, e verifica que é uma das `vowels`. + <3> Confirma que `globe` tem agora quatro itens. + <4> Cria uma segunda instância, com três itens. -<5> Cria uma terceira instância pela soma das duas anteriores. Essa instância tem sete itens. -<6> Tentar adicionar uma `AddableBingoCage` a uma `list` falha com um `TypeError`. A mensagem de erro é produzida pelo interpretador de Python quando nosso método `+__add__+` devolve `NotImplemented`. -Como uma `AddableBingoCage` é mutável, o <> mostra como ela funcionará quando implementarmos `+__iadd__+`. +<5> Cria uma terceira instância pela soma das duas anteriores. Esta instância +tem sete itens. + +<6> Tentar adicionar uma `AddableBingoCage` a uma `list` falha com um +`TypeError`. A mensagem de erro é produzida pelo interpretador de Python quando +nosso método `+__add__+` devolve `NotImplemented`. + +Como uma `AddableBingoCage` é mutável, o <> mostra como +ela funcionará quando implementarmos `+__iadd__+`. [[demo_addable_bingo_iadd]] -.Uma `AddableBingoCage` existente pode ser carregada com `+=` (continuando do <>) +.Uma `AddableBingoCage` existente pode ser carregada com `{iadd}` (continuando do <>) ==== [source, python] ---- include::../code/16-op-overloading/bingoaddable.py[tags=ADDABLE_BINGO_IADD_DEMO] ---- ==== + <1> Cria um alias para podermos checar a identidade do objeto mais tarde. + <2> `globe` tem quatro itens aqui. -<3> Uma instância de `AddableBingoCage` pode receber itens de outra instância da mesma classe. -<4> O operador à diretia de `+=` também pode ser qualquer iterável. -<5> Durante todo esse exemplo, `globe` sempre se refere ao mesmo objeto que `globe_orig`. -<6> Tentar adicionar um não-iterável a uma `AddableBingoCage` falha com uma mensagem de erro apropriada. -Observe que o operador `++=+` é mais liberal que `+++` quanto ao segundo operando. Com `+++`, queremos que ambos os operandos sejam do mesmo tipo (nesse caso, `AddableBingoCage`), pois se aceitássemos tipos diferentes, isso poderia causar confusão quanto ao tipo do resultado. Com o `++=+`, a situação é mais clara: o objeto à esquerda do operador é atualizado internamente, então não há dúvida quanto ao tipo do resultado. +<3> Uma instância de `AddableBingoCage` pode receber itens de outra instância +da mesma classe. + +<4> O operador à direita de `{iadd}` também pode ser qualquer iterável. + +<5> Durante todo esse exemplo, `globe` sempre se refere ao mesmo objeto que +`globe_orig`. + +<6> Tentar adicionar um não-iterável a uma `AddableBingoCage` falha com uma +mensagem de erro apropriada. + +Observe que o operador `{iadd}` é mais liberal que `{plus}` quanto ao segundo +operando. Com `{plus}`, queremos que ambos os operandos sejam do mesmo tipo +(neste caso, `AddableBingoCage`), pois se aceitássemos tipos diferentes, isso +poderia causar confusão quanto ao tipo do resultado, violando a propriedade +comutativa da adição. Com o `{iadd}`, a situação é mais clara: o objeto à +esquerda do operador é atualizado internamente, então não há dúvida quanto ao +tipo do resultado. [TIP] ==== -Eu validei os comportamentos diversos de `+` e `+=` observando como funciona o tipo embutido `list`. Ao escrever `my_list + x`, você só pode concatenar uma `list` a outra `list`, mas se você escrever `my_list += x`, você pode estender a lista da esquerda com itens de qualquer iterável `x` à direita do operador. -É assim que o método `list.extend()` funciona: ele aceita qualquer argumento iterável. + +Validei os comportamentos diversos de `{plus}` e `{iadd}` observando como +funciona o tipo embutido `list`. Ao escrever `my_list + x`, você só pode +concatenar uma `list` a outra `list`, mas se você escrever `my_list += x`, você +pode estender a lista da esquerda com itens de qualquer iterável `x` à direita +do operador. É assim que o método `list.extend()` funciona: ele aceita qualquer +argumento iterável. + ==== -Agora que esclarecemos o comportamento desejado para `AddableBingoCage`, podemos examinar sua implementação no <>. -Lembre-se que `BingoCage`, do <>, é uma subclasse concreta da ABC `Tombola` do <>. +Agora que vimos o comportamento desejado para `AddableBingoCage`, podemos +estudar sua implementação no <>. Lembre-se de que `BingoCage`, +(<> do <>), é uma subclasse concreta da ABC `Tombola` do +<> do <>. [[ex_addable_bingo]] -.bingoaddable.py: `AddableBingoCage` estende `BingoCage` para suportar `+` e `+=` +.bingoaddable.py: `AddableBingoCage` é subclasse de `BingoCage` com suporte aos operadores `{plus}` e `{iadd}` ==== [source, python] ---- include::../code/16-op-overloading/bingoaddable.py[tags=ADDABLE_BINGO] ---- ==== + <1> `AddableBingoCage` estende `BingoCage`. -<2> Nosso `+__add__+` só vai funcionar se o segundo operando for uma instância de `Tombola`. -<3> Em `+__iadd__+`, obtém os itens de `other`, se ele for uma instância de `Tombola`. -<4> Caso contrário, tenta obter um iterador sobre `other`.footnote:[A função embutida `iter` será tratada no próximo capítulo. Eu poderia ter usado `tuple(other)` aqui, e isso funcionaria, ao custo de criar uma nova `tuple` quando tudo que o método `.load(…)` precisa é iterar sobre seu argumento.] -<5> Se aquilo falhar, gera uma exceção explicando o que o usuário deve fazer. Sempre que possível, mensagens de erro devem guiar o usuário explicitamente para a solução. -<6> Se chegamos até aqui, podemos carregar o `other_iterable` para `self`. -<7> Muito importante: os métodos especiais de atribuição aumentada de objetos mutáveis devem devolver `self`. É o que os usuários esperam. -Podemos resumir toda a ideia dos operadores de atualização internos comparando as instruções `return` que produzem os resultados em `+__add__+` e em `+__iadd__+` no <>: +<2> Nosso `+__add__+` só vai funcionar se o segundo operando for uma instância +de `Tombola`. + +<3> Em `+__iadd__+`, obtém os itens de `other`, se for uma instância de +`Tombola`. + +<4> Caso contrário, tenta obter um iterador sobre `other` +(estudaremos a função embutida `iter` no <>). + +<5> Se aquilo falhar, gera uma exceção explicando o que o usuário deve fazer. +Sempre que possível, mensagens de erro devem orientar o usuário para a solução. -`+__add__+`:: O resultado é produzido chamando o construtor `AddableBingoCage` para criar uma nova instância. +<6> Se chegamos até aqui, podemos carregar o `other_iterable` em `self`. -`+__iadd__+`:: O resultado é produzido devolvendo `self`, após ele ter sido modificado. +<7> Muito importante: os métodos especiais de atribuição aumentada de objetos +mutáveis devem devolver `self`. É o que os usuários esperam. -Para concluir esse exemplo, uma última observação sobre o <>: propositalmente, nenhum método `+__radd__+` foi incluído em `AddableBingoCage`, porque não há necessidade. O método direto `+__add__+` só vai lidar com operandos à direita do mesmo tipo, então se Python tentar computar `a + b`, onde `a` é uma `AddableBingoCage` e `b` não, devolvemos `NotImplemented`—talvez a classe de `b` possa fazer isso funcionar. Mas se a expressão for `b + a` e `b` não for uma `AddableBingoCage`, e devolver `NotImplemented`, então é melhor deixar Python desistir e gerar um `TypeError`, pois não temos como tratar `b`. +Podemos resumir toda a ideia dos operadores de atribuição interna comparando +as instruções `return` que devolvem os resultados em `+__add__+` e em +`+__iadd__+` no <>: + +**`+__add__+`**: O resultado é computado chamando o construtor `AddableBingoCage` +para criar uma nova instância. + +**`+__iadd__+`**: O resultado é `self`, após ele ter sido modificado. + +Uma última observação sobre o <>: não implementei `+__radd__+` +em `AddableBingoCage`, porque não há necessidade. O método direto `+__add__+` só +vai lidar com operandos do mesmo tipo à direita, então se Python tentar computar +`a + b`, onde `a` é uma `AddableBingoCage` e `b` não, devolvemos +`NotImplemented`—talvez a classe de `b` possa fazer isso funcionar. Mas +se a expressão for `b + a` e `b` não for uma `AddableBingoCage`, e devolver +`NotImplemented`, então é melhor deixar Python desistir e gerar um `TypeError`, +pois não temos como tratar `b`. [TIP] ==== -De modo geral, se um método de operador infixo direto (por exemplo `+__mul__+`) for projetado para funcionar apenas com operandos do mesmo tipo de `self`, é inútil implementar o método reverso correspondente (por exemplo, `+__rmul__+`) pois, por definição, esse método só será invocado quando estivermos lidando com um operando de um tipo diferente. + +Se um método de operador infixo direto (por exemplo `+__mul__+`) +é projetado para funcionar apenas com operandos do mesmo tipo de `self`, é +inútil implementar o método reverso correspondente (por exemplo, `+__rmul__+`) +pois, por definição, esse método só será invocado quando estivermos lidando com +um operando de um tipo diferente. + ==== -Isso conclui nossa exploração de sobrecarga de operadores no Python.((("", startref="OOaugmented16")))((("", startref="augmented16")))((("", startref="addassigna16")))((("", startref="stareqa16")))((("", startref="adassb16")))((("", startref="stareqb16"))) +Assim terminamos nossa exploração de sobrecarga de operadores no Python.((("", +startref="OOaugmented16")))((("", startref="augmented16")))((("", +startref="addassigna16")))((("", startref="stareqa16")))((("", +startref="adassb16")))((("", startref="stareqb16"))) === Resumo do capítulo -Começamos((("operator overloading", "overview of"))) o capítulo revisando algumas restrições impostas pelo Python à sobrecarga de operadores: é proibido redefinir operadores nos próprios tipos embutidos, a sobrecarga está limitada aos operadores existentes, e alguns operadores não podem ser sobrecarregados (`is`, `and`, `or`, `not`). - -Colocamos a mão na massa com os operadores unários, implementando `+__neg__+` e `+__pos__+`. A seguir vieram os operadores infixos, começando por `+`, suportado pelo método `+__add__+`. Vimos que operadores unários e infixos devem produzir resultados criando novos objetos, sem nunca modificar seus operandos. Para suportar operações com outros tipos, devolvemos o valor especial `NotImplemented`—não uma exceção—permitindo ao interpretador tentar novamente permutando os operandos e chamando o método especial reverso para aquele operador (por exemplo, `+__radd__+`). O algoritmo usado pelo Python para tratar operadores infixos está resumido no fluxograma da <>. - -Misturar operandos de mais de um tipo exige detectar os operandos que não podemos tratar. Neste capitulo fizemos isso de duas maneiras: ao modo do _duck typing_, apenas fomos em frente e tentamos a operação, capturando uma exceção de `TypeError` se ela acontecesse; mais tarde, em `+__mul__+` e `+__matmul__+`, usamos um teste `isinstance` explícito. Há prós e contras nas duas abordagens: _duck typing_ é mais flexível, mas a checagem explícita de tipo é mais previsível. - -De modo geral, bibliotecas deveriam tirar proveito do _duck typing_--abrindo a porta para objetos independente de seus tipos, desde que eles suportem as operações necessárias. -Entretanto, o algoritmo de despacho de operadores de Python pode produzir mensagens de erro enganosas ou resultados inesperados quando combinado com o _duck typing_. -Por essa razão, a disciplina da checagem de tipos com invocações de `isinstance` contra ABCs é muitas vezes útil quando escrevemos métodos especiais para sobrecarga de operadores. -Essa é a técnica batizada de _goose typing_ por Alex Martelli—como vimos na <>. -A _goose typing_ é um bom compromisso entre a flexibilidade e a segurança, porque os tipos definidos pelo usuário, existentes ou futuros, podem ser declarados como subclasses reais ou virtuais de uma ABC. -Além disso, se uma ABC implementa o `+__subclasshook__+`, objetos podem então passar por checagens com `isinstance` contra aquela ABC apenas fornecendo os métodos exigidos--sem necessidade de ser uma subclasse ou de se registrar com a ABC. - -O próximo tópico tratado foram os operadores de comparação cheia. Implementamos `==` com -`+__eq__+` e descobrimos que Python oferece uma implementação conveniente de `!=` no `+__ne__+` herdado da classe base `object`. A forma como Python avalia esses operadores, bem como `>`, `<`, `>=`, e `<=`, é um pouco diferente, com uma lógica especial para a escolha do método reverso, e um tratamento alternativo para `==` e `!=` que nunca gera erros, pois Python compara os IDs dos objetos como último recurso. - -Na última seção, nos concentramos nos operadores de atribuição aumentada. -Vimos que Python os trata, por default, como uma combinação do operador simples seguido de uma atribuição, isto é: -`a += b` é avaliado exatamente como `a = a + b`. -Isso sempre cria um novo objeto, então funciona para tipos mutáveis ou imutáveis. -Para objetos mutáveis, podemos implementar métodos especiais de atualização interna, -tal como `+__iadd__+` para `+=`, e alterar o valor do operando à esquerda do operador. -Para demonstrar isso na prática, deixamos para trás a classe imutável `Vector` e -trabalhamos na implementação de uma subclasse de `BingoCage`, -suportando `+=` para adicionar itens ao reservatório de itens para sorteio, -de modo similar à forma como o tipo embutido `list` suporta -`+=` como um atalho para o método `list.extend()`. -Enquanto fazíamos isso, discutimos como `+` tende a ser mais estrito que `+=` em relação aos tipos aceitos. -Para tipos de sequências, `+` normalmente exige que ambos os operandos sejam do mesmo tipo, -enquanto `+=` muitas vezes aceita qualquer iterável como o operando à direita do operador. +Começamos((("operator overloading", "overview of"))) o capítulo revisando +algumas restrições impostas pelo Python à sobrecarga de operadores: é impossível +redefinir operadores nos tipos embutidos, a sobrecarga está limitada aos +operadores existentes, e alguns operadores não podem ser sobrecarregados (`is`, +`and`, `or`, `not`). + +Colocamos a mão na massa com os operadores unários, implementando `+__neg__+` e +`+__pos__+`. A seguir vieram os operadores infixos, começando por `{plus}`, +suportado pelo método `+__add__+`. Vimos que operadores unários e infixos devem +produzir resultados criando novos objetos, sem nunca modificar seus operandos. +Para suportar operações com outros tipos, devolvemos o valor especial +`NotImplemented` (não uma exceção) permitindo ao interpretador tentar novamente +chamando o método especial reverso do segundo operando (por exemplo, +`+__radd__+`). O algoritmo usado pelo Python para tratar operadores infixos está +resumido no fluxograma da <>. + +Misturar operandos de mais de um tipo exige detectar os operandos que não +podemos tratar. Neste capítulo fizemos isso de duas maneiras: ao modo da tipagem +pato, apenas fomos em frente e tentamos a operação, capturando uma exceção de +`TypeError` se ela acontecesse; mais tarde, em `+__mul__+` e `+__matmul__+`, +usamos um teste `isinstance` explícito. Há prós e contras nas duas abordagens: +tipagem pato é mais flexível, mas a checagem explícita de tipo é mais +previsível. + +De modo geral, bibliotecas devem aproveitar a tipagem pato para lidar objetos +de diferentes tipos, desde que eles suportem as operações +necessárias. Entretanto, o algoritmo de despacho de operadores de Python pode +produzir mensagens de erro enganosas ou resultados inesperados quando combinado +com a tipagem pato. Por essa razão, a disciplina da checagem de tipos com +invocações de `isinstance` contra ABCs é muitas vezes útil quando escrevemos +métodos especiais para sobrecarga de operadores. Esta é a técnica batizada de +tipagem ganso (_goose typing_) por Alex Martelli—como vimos na +<>. A tipagem ganso é um compromisso entre a flexibilidade e a +segurança, porque os tipos definidos pelo usuário, existentes ou futuros, podem +ser declarados como subclasses reais ou virtuais de uma ABC. Além disso, se uma +ABC implementa o `+__subclasshook__+`, objetos podem então passar por checagens +com `isinstance` contra aquela ABC apenas fornecendo os métodos exigidos—sem +necessidade de ser uma subclasse ou de se registrar com a ABC. + +O próximo tópico tratado foram os operadores de comparação rica. Implementamos +`==` com `+__eq__+` e descobrimos que Python oferece uma implementação +conveniente de `!=` no `+__ne__+` herdado da classe base `object`. A forma como +Python avalia esses operadores, bem como `>`, `<`, `>=`, e `{lte}`, é um pouco +diferente, com uma lógica especial para a escolha do método reverso, e um +tratamento alternativo para `==` e `!=` que nunca gera erros, pois a classe +`object` já implementa os métodos necessários. + +Na última seção, nos concentramos nos operadores de atribuição aumentada. Vimos +que Python os trata, por default, como uma combinação do operador simples +seguido de uma atribuição: `a {iadd} b` é avaliado exatamente como + +`a = a + b`. +Isto sempre cria um novo objeto, então funciona para tipos mutáveis ou +imutáveis. + +Para objetos mutáveis, podemos implementar métodos especiais de atualização +interna, tal como `+__iadd__+` para `{iadd}`, e alterar o valor do operando à +esquerda. Para demonstrar isto na prática, implementamos uma subclasse de +`BingoCage`, suportando `{iadd}` para adicionar itens ao reservatório de itens +para sorteio, de modo similar à forma como o tipo embutido `list` suporta +`{iadd}` como um atalho para o método `list.extend()`. Vimos que `{plus}` +tende a ser mais estrito que `{iadd}` em relação aos tipos aceitos. Em +sequências, `{plus}` normalmente exige que ambos os operandos sejam do mesmo +tipo, enquanto `{iadd}` muitas vezes aceita qualquer iterável como o operando à +direita do operador. [[further_reading_op_sec]] === Para saber mais -Guido van Rossum((("operator overloading", "further reading on"))) escreveu uma boa apologia da sobrecarga de operadores em https://fpy.li/16-10["Why operators are useful" (_Porque operadores são úteis_)] (EN). -Trey Hunner postou https://fpy.li/16-11["Tuple ordering and deep comparisons in Python" (_Ordenação de tuplas e comparações profundas em Python_)] (EN), argumentando que os operadores de comparação cheia de Python são mais flexíveis e poderosos do que os programadores vindos de outras linguagens costumam pensar. - -A sobrecarga de operadores é uma área da programação em Python onde testes com `isinstance` são comuns. -A melhor prática relacionada a tais testes é a _goose typing_, tratada na <>. -Se você pulou essa parte, se assegure de voltar lá e ler aquela seção. - -A principal referência para os métodos especiais de operadores é o https://docs.python.org/pt-br/3/reference/datamodel.html[capítulo "Modelos de Dados"] na documentação de Python. Outra leitura relevante é https://docs.python.org/pt-br/3/library/numbers.html#implementing-the-arithmetic-operations["Implementando as operações aritméticas"] no módulo `numbers` da _Biblioteca Padrão de Python_. - -Um exemplo brilhante de sobrecarga de operadores apareceu no pacote https://fpy.li/16-13[`pathlib`], adicionado no Python 3.4. -Sua classe `Path` sobrecarrega o operador `/` para construir caminhos do sistema de arquivos a partir de strings, como mostra o exemplo abaixo, da documentação: +Guido van Rossum((("operator overloading", "further reading on"))) escreveu uma +boa apologia da sobrecarga de operadores em +https://fpy.li/16-10[_Why operators are useful_] (Porque operadores são úteis). +Trey Hunner postou +https://fpy.li/16-11[_Tuple ordering and deep comparisons in Python_] +(Ordenação de tuplas e comparações profundas em Python), +argumentando que os operadores de comparação rica de Python são mais flexíveis e +poderosos do que os programadores vindos de outras linguagens costumam pensar. + +A sobrecarga de operadores é uma área da programação em Python onde testes com +`isinstance` são comuns. A melhor prática relacionada a tais testes é a tipagem +ganso, tratada na <>. Se você pulou essa parte, assegure-se de +voltar lá e ler aquela seção. + +A principal referência para os métodos especiais de operadores é o capítulo +https://fpy.li/2j[Modelo de Dados] na documentação de Python. Outra +leitura relevante é +https://fpy.li/7r[Implementando as operações aritméticas] +no módulo `numbers` da biblioteca padrão de Python. + +Um exemplo brilhante de sobrecarga de operadores apareceu no pacote +https://fpy.li/16-13[`pathlib`], a partir do Python 3.4. Sua classe `Path` +sobrecarrega o operador `/` para construir caminhos do sistema de arquivos a +partir de strings, como mostra o exemplo abaixo, da documentação: [source, python] ---- @@ -952,50 +1397,112 @@ Sua classe `Path` sobrecarrega o operador `/` para construir caminhos do sistema PosixPath('/etc/init.d/reboot') ---- -Outro exemplo não aritmético de sobrecarga de operadores está na biblioteca https://fpy.li/16-14[Scapy], usada para "enviar, farejar, dissecar e forjar pacotes de rede". -Na Scapy, o operador `/` operator cria pacotes empilhando campos de diferentes camadas da rede. Veja https://fpy.li/16-15["Stacking layers" (_Empilhando camadas)] (EN) para mais detalhes. - -Se você está prestes a implementar operadores de comparação, estude `functools.total_ordering`. -Esse é um decorador de classes que gera automaticamente os métodos para todos os operadores de comparação cheia em qualquer classe que defina ao menos alguns deles. -Veja a https://docs.python.org/pt-br/3/library/functools.html#functools.total_ordering[documentação do módulo functools] (EN). - -Se você tiver curiosidade sobre o despacho de métodos de operadores em linguagens com tipagem dinâmica, duas leituras fundamentais são https://fpy.li/16-17["A Simple Technique for Handling Multiple Polymorphism" (_Uma Técnica Simples para Tratar Polimorfismo Múltiplo_)] (EN), de Dan Ingalls (membro da equipe original de Smalltalk), e https://fpy.li/16-18["Arithmetic and Double Dispatching in Smalltalk-80" (_Aritmética e Despacho Duplo em Smalltalk-80_)] (EN), de Kurt J. Hebel e Ralph Johnson (Johnson ficou famoso como um dos autores do livro _Padrões de Projetos_ original). Os dois artigos fornecem discussões profundas sobre o poder do polimorfismo em linguagens com tipagem dinâmica, como Smalltalk, Python e Ruby. Python não tem despacho duplo para tratar operadores, como descrito naqueles artigos. O algoritmo de Python, usando operadores diretos e reversos, é mais fácil de suportar por classes definidas pelo usuário que o despacho duplo, mas exige tratamento especial pelo interpretador. Por outro lado, o despacho duplo clássico é uma técnica geral, que pode ser usada no Python ou em qualquer linguagem orientada a objetos, para além do contexto específico de operadores infixos. E, de fato, Ingalls, Hebel e Johnson usam exemplos muito diferentes para descrever essa técnica. - -O artigo https://fpy.li/16-1["The C Family of Languages: Interview with Dennis Ritchie, Bjarne Stroustrup, and James Gosling"(_A Família de Linguagens C: Entrevista com Dennis Ritchie, Bjarne Stroustrup, e James Gosling_)] (EN), da onde tirei a epígrafe desse capítulo, apareceu na _Java Report_, 5(7), julho de 2000, e na _{cpp} Report_, 12(7), julho/agosto de 2000, juntamente com outros trechos que usei no "Ponto de Vista" deste capítulo (abaixo). -Se você se interessa pelo projeto de linguagens de programação, faça um favor a si mesmo e leia aquela entrevista. +Outro exemplo não aritmético de sobrecarga de operadores está na biblioteca +https://fpy.li/16-14[Scapy], usada para "enviar, farejar, dissecar e forjar +pacotes de rede". Na Scapy, o operador `/` cria pacotes empilhando campos de +diferentes camadas da rede. Veja https://fpy.li/16-15[_Stacking layers_] +(Empilhando camadas) para mais detalhes. + +Se você está prestes a implementar operadores de comparação, estude +`functools.total_ordering`. Esse é um decorador de classes que gera +automaticamente os métodos para todos os operadores de comparação rica em +qualquer classe que defina ao menos alguns deles. Veja a +https://fpy.li/7q[documentação do módulo functools]. + +Se tiver curiosidade sobre o despacho de métodos de operadores em linguagens com +tipagem dinâmica, duas leituras fundamentais são +https://fpy.li/16-17[_A Simple Technique for Handling Multiple Polymorphism_] +(Uma técnica simples para tratar polimorfismo múltiplo), de Dan Ingalls +(membro da equipe original de Smalltalk), e +https://fpy.li/16-18[_Arithmetic and Double Dispatching in Smalltalk-80_] +(Aritmética e despacho duplo no Smalltalk-80), de Kurt J. +Hebel e Ralph Johnson (Johnson ficou famoso como um dos autores do livro +_Padrões de Projetos_ original). + +Os dois artigos discutem em profundidade o poder do polimorfismo em linguagens +com tipagem dinâmica, como Smalltalk, Python e Ruby. Python não implementa +despacho duplo exatamente como descrito naqueles artigos. O algoritmo de +despacho duplo em Python, usando operadores diretos e reversos, é mais fácil de +suportar em classes definidas pelo usuário que o despacho duplo clássico, mas +exige tratamento especial pelo interpretador. Por outro lado, o despacho duplo +clássico é uma técnica geral, que pode ser usada no Python ou em qualquer +linguagem orientada a objetos, para além do contexto específico de operadores +infixos. E, de fato, Ingalls, Hebel e Johnson usam exemplos muito diferentes +para descrever essa técnica. + +O texto +https://fpy.li/16-1[_The C Family of Languages: Interview with Dennis Ritchie, Bjarne Stroustrup, and James Gosling_] +(A Família de Linguagens C: entrevista com Dennis Ritchie, Bjarne Stroustrup, e James +Gosling), de onde tirei a epígrafe deste capítulo, apareceu na _Java Report_, 5(7), +julho de 2000, e na _{cpp} Report_, 12(7), julho/agosto de 2000, +juntamente com outros trechos que usei no Ponto de Vista deste capítulo (logo +adiante). Se você se interessa pelo design de linguagens de programação, faça +um favor a si mesmo e leia aquela entrevista. [[operator_soapbox]] .Ponto de Vista **** -[role="soapbox-title"] -Sobrecarga de operadores: prós e contras +**Sobrecarga de operadores: prós e contras** -James Gosling, citado((("operator overloading", "Soapbox discussion")))((("Soapbox sidebars", "operator overloading"))) no início desse capítulo, tomou a decisão consciente de excluir a sobrecarga de operadores quando projetou o Java. Naquela mesma entrevista (https://fpy.li/16-20["The C Family of Languages: Interview with Dennis Ritchie, Bjarne Stroustrup, and James Gosling" (_A Família de Linguagens C: Entrevista com Dennis Ritchie, Bjarne Stroustrup, e James Gosling_)] (EN)) ele diz: +James Gosling, citado((("operator overloading", +"Soapbox discussion")))((("Soapbox sidebars", "operator overloading"))) no início +deste capítulo, tomou a decisão consciente de excluir a sobrecarga de operadores +quando projetou o Java. Na entrevista +https://fpy.li/16-1[_The C Family of Languages_] ele diz: [quote] ____ -Provavelmente uns 20 a 30 porcento da população acha que sobrecarga de operadores é uma criação demoníaca; alguém fez algo com sobrecarga de operadores que realmente os tirou do sério, porque eles usaram algo como + para inserção em listas, e isso torna a vida muito, muito confusa. Muito daquele problema vem do fato de existirem apenas uma meia dúzia de operadores que podem ser sobrecarregados de forma razoável, mas existem milhares ou milhões de operadores que as pessoas gostariam de definir—então é preciso escolher, e muitas vezes as escolhas entram em conflito com a sua intuição. +Talvez uns 20 a 30% da população acha que sobrecarga de +operadores é obra do demônio; alguém fez algo com sobrecarga de operadores +que realmente os tirou do sério, porque usaram algo como + para inserção em +listas, e isso torna a vida muito, muito confusa. Muito do problema vem do +fato de existirem apenas uma meia dúzia de operadores que podem ser +sobrecarregados de forma razoável, mas existem milhares ou milhões de operadores +que as pessoas gostariam de definir—então é preciso escolher, e muitas vezes as +escolhas entram em conflito com a sua intuição. ____ -Guido van Rossum escolheu o caminho do meio no suporte à sobrecarga de operadores: ele não deixou a porta aberta para que os usuários criassem novos operadores arbitrários como `<=>` ou `:-)`, evitando uma Torre de Babel de operadores customizados, e permitindo ao analisador sintático de Python permanecer simples. Python também não permite a sobrecarga dos operadores de tipos embutidos, outra limitação que promove a legibilidade e o desempenho previsível. +Guido van Rossum escolheu o caminho do meio no suporte à sobrecarga de +operadores: ele não deixou a porta aberta para que os usuários criassem novos +operadores arbitrários como `{lte}>` ou `:-)`, evitando uma Torre de Babel +de operadores customizados, e que o analisador sintático de Python +continue simples. Python também não permite a sobrecarga dos operadores dos +tipos embutidos, outra limitação que promove a legibilidade e o desempenho +previsível. Gosling continua: [quote] ____ -E então há uma comunidade de aproximadamente 10 porcento que havia de fato usado a sobrecarga de operadores de forma apropriada, e que realmente gostavam disso, e para quem isso era realmente importante; essas são quase exclusivamente pessoas que fazem trabalho numérico, onde a notação é muito importante para avivar a intuição [das pessoas], porque elas chegam ali com uma intuição sobre o que + significa, e a capacidade de dizer "a + b", onde a e b são números complexos ou matrizes ou alguma outra coisa, realmente faz sentido. + +E então há uma comunidade de aproximadamente 10% que havia de fato usado a +sobrecarga de operadores de forma apropriada, e que realmente gostavam disso, e +para quem isso era realmente importante; essas são quase exclusivamente pessoas +que fazem trabalho numérico, onde a notação é muito importante para avivar a +intuição [das pessoas], porque elas vêm com uma intuição sobre o que `{plus}` +significa, e a poder dizer `a + b`, onde a e b são números complexos ou matrizes +ou alguma outra coisa, realmente faz sentido. + ____ -Claro, há benefícios em não permitir a sobrecarga de operadores em uma linguagem. Já ouvi o argumento que C é melhor que C++ para programação de sistemas, porque a sobrecarga de operadores em C++ pode fazer com que operações dispendiosas pareçam triviais. -Duas linguagens modernas bem sucedidas, que compilam para executáveis binários, fizeram escolhas opostas: Go não tem sobrecarga de operadores, https://fpy.li/16-21[Rust tem]. +Claro, há benefícios em não permitir a sobrecarga de operadores em uma +linguagem. Já ouvi o argumento de que C é melhor que {cpp}; para +programação de sistemas, porque a sobrecarga de operadores em {cpp} pode +fazer com que operações dispendiosas pareçam triviais. Duas linguagens modernas +bem sucedidas, que compilam para executáveis binários, fizeram escolhas opostas: +Go não tem sobrecarga de operadores, https://fpy.li/16-21[Rust tem]. -Mas operadores sobrecarregados, quando usados de forma sensata, tornam o código mais fácil de ler e escrever. É um ótimo recurso em uma linguagem de alto nível moderna. +Mas operadores sobrecarregados, quando usados de forma sensata, tornam o código +mais fácil de ler e escrever. É um ótimo recurso em uma linguagem de alto nível +moderna. -[role="soapbox-title"] -Um vislumbre da avaliação preguiçosa +**Um exemplo de avaliação preguiçosa** -Se você olhar de perto o _traceback_ no <>, vai encontrar evidências da avaliação https://fpy.li/16-22[_preguiçosa_] de expressões geradoras. O <> é o mesmo _traceback_, agora com explicações. +Se você olhar de perto o _traceback_ no <>, vai +encontrar evidências da avaliação https://fpy.li/16-22[preguiçosa] de +expressões geradoras. O <> é o mesmo +_traceback_, agora com explicações. [[ex_vector_error_iter_not_add_repeat]] .Mesmo que o <> @@ -1014,15 +1521,28 @@ Traceback (most recent call last): TypeError: unsupported operand type(s) for +: 'float' and 'str' ---- ==== -<1> A chamada a `Vector` recebe uma expressão geradora como seu argumento `components`. Nenhum problema nesse estágio. -<2> A genexp `components` é passada para o construtor de `array`. Dentro do construtor de `array`, Python tenta iterar sobre a genexp, causando a avaliação do primeiro item `a + b`. É quando ocorre o `TypeError`. -<3> A exceção se propaga para a chamada ao construtor de `Vector`, onde é relatada. -Isso mostra como a expressão geradora é avaliada no último instante possível, e não onde é definida no código-fonte. +<1> A chamada a `Vector` recebe uma expressão geradora como seu argumento +`components`. Nenhum problema nesse estágio. -Se, por outro lado, o construtor de `Vector` fosse invocado como -`Vector([a + b for a, b in pairs])`, então a exceção ocorreria bem ali, porque a compreensão de lista tentou criar uma `list` para ser passada como argumento para a chamada a `Vector()`. O corpo de `+Vector.__init__+` nunca seria alcançado. +<2> A genexp `components` é passada para o construtor de `array`. Dentro do +construtor de `array`, Python tenta iterar sobre a genexp, causando a avaliação +do primeiro item `a + b`. É quando ocorre o `TypeError`. + +<3> A exceção se propaga para a chamada ao construtor de `Vector`, onde é +relatada. -O <> vai tratar das expressões geradoras em detalhes, mas não eu queria deixar essa demonstração acidental de sua natureza preguiçosa passar desapercebida. +Isso mostra como a expressão geradora é avaliada no último instante possível, e +não onde é definida no código-fonte. + +Se, por outro lado, o construtor de `Vector` fosse invocado como +`Vector([a + b for a, b in pairs])`, então a exceção ocorreria bem ali, +porque a compreensão de lista tentou criar uma `list` para ser passada como +argumento para a chamada a `Vector()`. O corpo de `+Vector.__init__+` +nunca seria alcançado. + +O <> vai tratar das expressões geradoras em detalhes, mas eu não +queria deixar essa demonstração acidental de sua natureza preguiçosa passar +despercebida. **** diff --git a/online/cap17.adoc b/online/cap17.adoc index ad58a6a..4709635 100644 --- a/online/cap17.adoc +++ b/online/cap17.adoc @@ -3,88 +3,121 @@ :example-number: 0 :figure-number: 0 -//++++ -//
-//

When I see patterns in my programs, I consider it a sign of trouble. The shape of a program should reflect only the problem it needs to solve. Any other regularity in the code is a sign, to at least, that I'm using abstractions that aren't powerful enough—often that I'm generating by hand the expansions of some macro that I need to write.

- -//

Paul Graham, Lisp hacker and venture capitalistFrom "Revenge of the Nerds", a blog post.

-//
-//++++ - - [quote, Paul Graham, hacker de Lisp e investidor] ____ -Quando vejo padrões em meus programas, considero isso um mau sinal. A forma de um programa deve refletir apenas o problema que ele precisa resolver. Qualquer outra regularidade no código é, pelo menos para mim, um sinal que estou usando abstrações que não são poderosas o suficiente—muitas vezes estou gerando à mão as expansões de alguma macro que preciso escrever.footnote:[De https://fpy.li/17-1["Revenge of the Nerds" (_A Revanche dos Nerds_)], um post de blog.] -____ -//// -PROD: in the rendered PDF, the title of this chapter appears with a line break between "Classic" and "Coroutines". -Is there a way to put a line break after the comma, so the second line would be "and Classic Coroutines"? Of course, this is not important, but would make more sense and look nicer too. Thanks! +Quando vejo padrões em meus programas, considero isso um mau sinal. + +A forma de um programa deve refletir apenas o problema que ele precisa +resolver. Qualquer +outra regularidade no código é, pelo menos para mim, + +um sinal que estou usando +abstrações que não são poderosas o suficiente— + +muitas vezes estou gerando à mão +as expansões de alguma macro que preciso escrever.footnote:[De +https://fpy.li/17-1[_Revenge of the Nerds_] (A Revanche dos Nerds), um post de +blog.] + +____ -AU: Of course! -//// -A iteração((("iterators", "role of"))) é fundamental para o processamento de dados: programas aplicam computações sobre séries de dados, de pixels a nucleotídeos. -Se os dados não cabem na memória, precisamos buscar esses itens de forma _preguiçosa_—um de cada vez e sob demanda. É isso que um iterador faz. -Este capítulo mostra como o padrão de projeto _Iterator_ ("Iterador") está embutido na linguagem Python, de modo que nunca será necessário programá-lo manualmente. +A iteração((("iterators", "role of"))) é fundamental para o processamento de +dados: programas aplicam computações sobre séries de dados, de pixels a +nucleotídeos. Se os dados não cabem na memória, precisamos buscar esses itens de +forma _preguiçosa_—um de cada vez e sob demanda. É isso que um iterador faz. +Este capítulo mostra como o padrão de projeto _Iterator_ ("Iterador") está +embutido na linguagem Python, de modo que nunca será necessário programá-lo +manualmente. Todas as coleções padrão de Python são _iteráveis_. -Um _iterável_ é um objeto que fornece um _iterador_, que Python usa para suportar operações como: +Um _iterável_ é um objeto que fornece um _iterador_, +que Python usa para suportar operações como: -* loops `for` +* O laço `for` * Compreensões de lista, dict e set -* Desempacotamento para atribuições +* Atribuições com desempacotamento de tuplas * Criação de instâncias de coleções -[role="pagebreak-before less_space"] -Este((("iterators", "topics covered")))((("generators", "topics covered")))((("coroutines", "topics covered"))) capítulo cobre os seguintes tópicos: +Este((("iterators", "topics covered")))((("generators", "topics +covered")))((("coroutines", "topics covered"))) capítulo cobre os seguintes +tópicos: * Como Python usa a função embutida `iter()` para lidar com objetos iteráveis -* Como implementar o padrão _Iterator_ clássico no Python -* Como o padrão _Iterator_ clássico pode ser substituído por uma função geradora ou por uma expressão geradora -* Como funciona uma função geradora, em detalhes, com descrições linha a linha -* Aproveitando o poder das funções geradoras de uso geral da biblioteca padrão -* Usando expressões `yield from` para combinar geradoras -* Porque geradoras e corrotinas clássicas se parecem, mas são usadas de formas muito diferentes e não devem ser misturadas + +* Como é o padrão _Iterator_ clássico escrito em Python + +* Porque podemos substituir o padrão _Iterator_ clássico por uma função geradora +ou por uma expressão geradora + +* Como funciona uma função geradora, em detalhes, linha a linha + +* Como aproteitar o poder das funções geradoras de uso geral da biblioteca padrão + +* Usando expressões `yield from` para combinar geradores + +* Porque geradores e corrotinas clássicas se parecem, mas são usadas de formas +muito diferentes e não devem ser misturadas === Novidades neste capítulo -A <> aumentou((("iterators", "significant changes to")))((("generators", "significant changes to")))((("coroutines", "significant changes to"))) de uma para seis páginas. -Ela agora inclui experimentos simples, demonstrando o comportamento de geradoras com `yield from`, e um exemplo de código para percorrer uma árvore de dados, desenvolvido passo a passo. +A <> agora inclui experimentos simples +demonstrando o comportamento de geradores com `yield from`, e um exemplo de +código para percorrer uma estrutura de dados em árvore, desenvolvido passo a passo. -Novas seções explicam as dicas de tipo para os tipos `Iterable`, `Iterator` e `Generator`. +Novas seções explicam as dicas de tipo para os tipos `Iterable`, `Iterator` e +`Generator`. -A última grande seção do capítulo, <>, é agora uma introdução de 9 páginas a um tópico que ocupava um capítulo de 40 páginas na primeira edição. -Atualizei e transferi o capítulo https://fpy.li/oldcoro[Classic Coroutines (_Corrotinas Clássicas_)] para um -https://fpy.li/oldcoro[post no site que acompanha o livro], porque ele era o capítulo mais difícil para os leitores, mas seu tema se tornou menos relevante após a introdução das corrotinas nativas no Python 3.5 (estudaremos as corrotinas nativas no <>). +A última grande seção do capítulo, <>, é agora uma +introdução de 9 páginas a um tópico que ocupava um capítulo de 40 páginas na +primeira edição. Atualizei e publiquei https://fpy.li/oldcoro[«no site»] +que acompanha o livro +o capítulo _Classic Coroutines_ da primeira edição (em inglês). +Era o capítulo mais difícil do livro, +mas ficou menos relevante após a introdução das corrotinas nativas +no Python 3.5 (estudaremos as corrotinas nativas no <>). -Vamos começar examinando como a função embutida `iter()` torna as sequências iteráveis. +Vamos começar examinando como a função embutida `iter()` torna as sequências +iteráveis. === Uma sequência de palavras -Vamos((("iterators", "sequence protocol", id="Isequence17")))((("sequence protocol", id="seqpro17"))) começar nossa exploração de iteráveis implementando uma classe `Sentence`: seu construtor recebe uma string de texto e daí podemos iterar sobre a "sentença" palavra por palavra. A primeira versão vai implementar o protocolo de sequência e será iterável, pois todas as sequências são iteráveis—como sabemos desde o <>. -Agora veremos exatamente porque isso acontece. +Vamos((("iterators", "sequence protocol", id="Isequence17")))((("sequence protocol", +id="seqpro17"))) começar nossa exploração de iteráveis implementando +uma classe `Sentence`: seu construtor recebe uma string de texto e daí podemos +iterar sobre a "sentença" palavra por palavra. A primeira versão vai implementar +o protocolo de sequência e será iterável, pois todas as sequências são +iteráveis—como sabemos desde o <>. Agora veremos exatamente +porque isso acontece. -O <> mostra uma classe `Sentence` que extrai palavras de um texto por -índice. +O <> mostra uma classe `Sentence` que extrai palavras de um texto +por índice. [[ex_sentence0]] -.sentence.py: uma `Sentence` como uma sequência de palavras +.sentence.py: `Sentence` como uma sequência de palavras ==== [source, python] ---- include::../code/17-it-generator/sentence.py[tags=SENTENCE_SEQ] ---- ==== -<1> `.findall` devolve a lista com todos os trechos não sobrepostos correspondentes à expressão regular, como uma lista de strings. -<2> `self.words` mantém o resultado de `.findall`, então basta devolver a palavra em um dado índice. -<3> Para completar o protocolo de sequência, implementamos `+__len__+`, apesar dele não ser necessário para criar um iterável. -<4> `reprlib.repr` é uma função utilitária para gerar representações abreviadas, em forma de strings, de estruturas de dados que podem ser muito grandes.footnote:[Já usamos `reprlib` na <>.] +<1> `.findall` devolve a lista com todos os trechos não sobrepostos +correspondentes à expressão regular, como uma lista de strings. + +<2> `self.words` mantém o resultado de `.findall`, então basta devolver a +palavra em um dado índice. + +<3> Para completar o protocolo de sequência, implementamos `+__len__+`, apesar +dele não ser necessário para criar um iterável. + +<4> `reprlib.repr` devolve representações abreviadas, +como vimos ao implementar +__repr__+ na +classe `Vector` da <>. -Por default, `reprlib.repr` limita a string gerada a 30 caracteres. Veja como `Sentence` é usada na sessão de console do <>. + +Por default, `reprlib.repr` limita a string gerada a 30 caracteres. Veja como +`Sentence` é usada na sessão de console do <>. [[demo_sentence0]] .Testando a iteração em uma instância de `Sentence` @@ -107,13 +140,20 @@ said ['The', 'time', 'has', 'come', 'the', 'Walrus', 'said'] ---- ==== + <1> Uma sentença criada a partir de uma string. + <2> Observe a saída de `+__repr__+` gerada por `reprlib.repr`, usando `\...`. + <3> Instâncias de `Sentence` são iteráveis; veremos a razão em seguida. -<4> Sendo iteráveis, objetos `Sentence` podem ser usados como entrada para criar listas e outros tipos iteráveis. -Nas próximas páginas vamos desenvolver outras classes `Sentence` que passam nos testes do <>. -Entretanto, a implementação no <> difere das outras por ser também uma sequência, e então é possível obter palavras usando um índice: +<4> Sendo iteráveis, objetos `Sentence` podem ser usados como entrada para criar +listas e outros tipos iteráveis. + +Nas próximas páginas vamos desenvolver outras classes `Sentence` que passam nos +testes do <>. Entretanto, a implementação no <> +difere das outras por ser também uma sequência, e então é possível obter +palavras usando um índice: [source, python] ---- @@ -125,23 +165,39 @@ Entretanto, a implementação no <> difere das outras por ser tamb 'said' ---- -Programadores Python sabem que sequências são iteráveis. Agora vamos descobrir exatamente o porquê disso.((("", startref="Isequence17")))((("", startref="seqpro17"))) +Programadores Python sabem que sequências são iteráveis. Agora vamos descobrir +exatamente o porquê disso.((("", startref="Isequence17")))((("", +startref="seqpro17"))) [[iter_func_sec]] -=== Porque sequências são iteráveis: a função iter +=== Porque sequências são iteráveis: a função `iter` -Sempre((("functions", "iter() function")))((("iterators", "iter() function", id="Iinterfun17")))((("iter() function", id="iterfunc17"))) que Python precisa iterar sobre um objeto `x`, ele automaticamente invoca `iter(x)`. +Para((("functions", "iter() function")))((("iterators", "iter() function", +id="Iinterfun17")))((("iter() function", id="iterfunc17"))) +iterar sobre um objeto `x`, o interpretador Python `iter(x)`. A função embutida `iter`: -. Verifica se o objeto implementa o método `+__iter__+`, e o invoca para obter um iterador. -. Se `+__iter__+` não for implementado, mas `+__getitem__+` sim, então `iter()` cria um iterador que tenta buscar itens pelo índice, começando de 0 (zero). -. Se isso falhar, Python gera um `TypeError`, normalmente dizendo `'C' object is not iterable` (_objeto 'C' não é iterável_), onde `C` é a classe do objeto alvo. +. Verifica se o objeto implementa o método `+__iter__+`, e o invoca para obter +um iterador. + +. Se `+__iter__+` não for implementado, mas `+__getitem__+` sim, então `iter` +cria um iterador que tenta buscar itens pelo índice, a partir de `0` (zero). -Por isso todas as sequências de Python são iteráveis: por definição, todas elas implementam `+__getitem__+`. -Na verdade, todas as sequências padrão também implementam `+__iter__+`, e as suas próprias sequências também deviam implementar esse método, porque a iteração via `+__getitem__+` existe para manter a compatibilidade retroativa, e pode desaparecer em algum momento—apesar dela não ter sido descontinuada no Python 3.10, e eu duvidar que vá ser removida algum dia. +. Se isso falhar, Python gera um `TypeError`, com a mensagem `'C' object is +not iterable` ("objeto 'C' não é iterável"), onde `C` é a classe do objeto alvo. -Como mencionado na <>, essa é uma forma extrema de _duck typing_: um objeto é considerado iterável não apenas quando implementa o método especial `+__iter__+`, mas também quando implementa `+__getitem__+`. Veja isso: +Por isso todas as sequências de Python são iteráveis: por definição, todas +implementam `+__getitem__+`. Na verdade, todas as sequências padrão também +implementam `+__iter__+`, e as classes de sequências que você criar também devem +implementar esse método. A iteração via `+__getitem__+` existe para +manter a compatibilidade retroativa, e pode desaparecer em algum momento—apesar +dela não ter sido descontinuada no Python 3.10, e eu duvidar que vá ser removida +algum dia. + +Como mencionado na <>, esta é uma forma extrema de tipagem pato: +um objeto é considerado iterável não apenas quando implementa o método +especial `+__iter__+`, mas também quando implementa `+__getitem__+`. Confira: [source, python] ---- @@ -161,13 +217,20 @@ Como mencionado na <>, essa é uma forma extrema de _duck t False ---- -Se uma classe fornece `+__getitem__+`, a função embutida `iter()` aceita uma instância daquela classe como iterável e cria um iterador a partir da instância. -A maquinaria de iteração de Python chamará `+__getitem__+` com índices, começando de 0, e entenderá um `IndexError` como sinal de que não há mais itens. +Se uma classe fornece `+__getitem__+`, a função embutida `iter()` aceita uma +instância daquela classe como iterável e cria um iterador a partir da instância. +A maquinaria de iteração de Python chamará `+__getitem__+` com índices, +começando de 0, e entenderá um `IndexError` como sinal de que não há mais itens. -Observe que, apesar de `spam_can` ser iterável (seu método `+__getitem__+` poderia fornecer itens), ela não é reconhecida assim por uma chamada a `isinstance` contra `abc.Iterable`. +Observe que, apesar de `spam_can` ser iterável (seu método `+__getitem__+` +poderia fornecer itens), ela não é reconhecida assim por uma chamada a +`isinstance` contra `abc.Iterable`. -Na abordagem da _goose typing_, a definição para um iterável é mais simples, mas não tão flexível: um objeto é considerado iterável se implementa o método `+__iter__+`. -Não é necessário ser subclasse ou se registar, pois `abc.Iterable` implementa o `+__subclasshook__+`, como visto na <>. Eis uma demonstração: +Na tipagem ganso (_goose typing_), a definição de um iterável é mais simples, mas +não tão flexível: um objeto é considerado iterável se implementa o método +`+__iter__+`. Não é necessário ser subclasse ou se registar, pois `abc.Iterable` +implementa o `+__subclasshook__+`, como visto na <>. +Demonstração: [source, python] ---- @@ -185,27 +248,42 @@ True [TIP] ==== -Desde o Python 3.10, a forma mais precisa de checar se um objeto `x` é iterável é invocar -`iter(x)` e tratar a exceção `TypeError` se ele não for. -Isso é mais preciso que usar `isinstance(x, abc.Iterable)`, porque `iter(x)` também leva em consideração o método legado `+__getitem__+`, enquanto a ABC `Iterable` não considera tal método. + +Desde o Python 3.10, a forma mais precisa de checar se um objeto `x` é iterável +é invocar `iter(x)` e tratar a exceção `TypeError` se ele não for. Isso é mais +preciso que usar `isinstance(x, abc.Iterable)`, porque `iter(x)` também leva em +consideração o método legado `+__getitem__+`, enquanto a ABC `Iterable` não +considera tal método. + ==== -Verificar explicitamente se um objeto é iterável pode não valer a pena, se você for iterar sobre o objeto logo após a checagem. -Afinal, quando se tenta iterar sobre um não-iterável, a exceção gerada pelo Python é bastante clara: `TypeError: 'C' object is not iterable` (_TypeError: o objeto 'C' não é iterável_). -Se você puder fazer algo mais além de gerar um `TypeError`, então faça isso em um bloco `try/except` ao invés de realizar uma checagem explícita. -A checagem explícita pode fazer sentido se você estiver mantendo o objeto para iterar sobre ele mais tarde; nesse caso, capturar o erro mais cedo torna a depuração mais fácil. +Verificar explicitamente se um objeto é iterável pode não valer a pena, se você +for iterar sobre o objeto logo após a checagem. Afinal, quando se tenta iterar +sobre um não-iterável, a exceção gerada pelo Python é bastante clara: +`TypeError: 'C' object is not iterable` (TypeError: o objeto 'C' não é +iterável). Se você puder fazer algo mais além de gerar um `TypeError`, então +faça isso em um bloco `try/except` ao invés de realizar uma checagem explícita. +A checagem explícita pode fazer sentido se você estiver guardando o objeto para +iterar sobre ele mais tarde; neste caso, falhar logo facilita o diagnóstico de falhas. -A função embutida `iter()` é usada mais frequentemente pelo Python que no nosso código. -Há uma segunda maneira de usá-la, mas não é muito conhecida. +A função embutida `iter()` é usada mais frequentemente pelo Python que no nosso +código. Há uma segunda maneira de usá-la, mas não é muito conhecida. [[iter_closer_look_sec]] ==== Usando iter com um invocável -Podemos((("objects", "callable objects", id="Oiter17")))((("callable objects", "using iter() with", id="COiter17"))) chamar `iter()` com dois argumentos, para criar um iterador a partir de uma função ou de qualquer objeto invocável. -Nessa forma de uso, o primeiro argumento deve ser um invocável que será chamado repetidamente (sem argumentos) para produzir valores, e o segundo argumento é um https://fpy.li/17-2[_valor sentinela_] (EN): um marcador que, quando devolvido por um invocável, faz o iterador gerar um `StopIteration` ao invés de produzir o valor sentinela. +Podemos((("objects", "callable objects", id="Oiter17")))((("callable objects", +"using iter() with", id="COiter17"))) chamar `iter()` com dois argumentos, para +criar um iterador a partir de uma função ou de qualquer objeto invocável. Nesta +forma de uso, o primeiro argumento deve ser um invocável que será invocado sem argumentos +repetidamente para produzir valores, e o segundo argumento é um +https://fpy.li/17-2[«sentinel value»] (valor sentinela): um valor que, quando devolvido +pelo invocável, faz o iterador gerar um `StopIteration` ao invés de produzir o +valor sentinela. -O exemplo a seguir mostra como usar `iter` para rolar um dado de seis faces até que o valor `1` seja sorteado: +O exemplo a seguir mostra como usar `iter` para rolar um dado de seis faces até +que o valor `1` seja sorteado: [source, python] ---- @@ -224,14 +302,26 @@ O exemplo a seguir mostra como usar `iter` para rolar um dado de seis faces até 3 ---- -Observe que a função `iter` devolve um `callable_iterator`. -O loop `for` no exemplo pode rodar por um longo tempo, mas nunca vai devolver `1`, pois esse é o valor sentinela. Como é comum com iteradores, o objeto `d6_iter` se torna inútil após ser exaurido. Para recomeçar, é necessário reconstruir o iterador, invocando novamente `iter()`. +Observe que a função `iter` devolve um `callable_iterator`. O laço `for` no +exemplo pode rodar por um longo tempo, mas nunca vai devolver `1`, pois esse é o +valor sentinela. Como é comum com iteradores, o objeto `d6_iter` se torna inútil +após ser exaurido. Para recomeçar, é necessário reconstruir o iterador, +invocando novamente `iter()`. -A https://docs.python.org/pt-br/3.10/library/functions.html#iter[documentação de `iter`] inclui a seguinte explicação e código de exemplo: +A https://fpy.li/97[documentação de +`iter`] inclui a seguinte explicação e código de exemplo:footnote:[NT: +Mudei um pouco a tradução, e +https://fpy.li/98[«sugeri a melhoria»] no +repositório oficial da tradução PT-BR da documentação do Python.] [quote] ____ -Uma aplicação útil da segunda forma de `iter()` é para construir um bloco de leitura. Por exemplo, ler blocos de comprimento fixo de um arquivo binário de banco de dados até que o final do arquivo seja atingido: + +Uma aplicação útil da segunda forma de `iter()` é para construir um +leitor bloco-a-bloco (_block reader_). +Por exemplo, ler blocos de 64 bytes do arquivo binário de um de banco de dados +até que o final do arquivo seja atingido: + ____ [source, python] @@ -239,30 +329,41 @@ ____ from functools import partial with open('mydata.db', 'rb') as f: - read64 = partial(f.read, 64) - for block in iter(read64, b''): + read_block = partial(f.read, 64) + for block in iter(read_block, b''): process_block(block) ---- -Para deixar o código mais claro, adicionei a atribuição `read64`, que não está no -https://docs.python.org/pt-br/3.10/library/functions.html#iter[exemplo original]. -A função `partial()` é necessária porque o invocável passado a `iter()` não pode requerer argumentos. -No exemplo, um objeto `bytes` vazio é a sentinela, pois é isso que `f.read` devolve quando não há mais bytes para ler. +Para deixar o código mais fácil de ler, adicionei a atribuição `read_block`, que não está no +https://fpy.li/97[«exemplo original»]. +A função `partial()` é necessária porque o invocável passado a +`iter()` não pode requerer argumentos. No exemplo, um objeto `bytes` vazio é a +sentinela, pois é isso que `f.read` devolve quando não há mais bytes para ler. +A variável `block` pode receber menos de 64 bytes uma vez no final do arquivo, +mas nunca receberá 0 bytes, porque `b''` é o valor sentinela. -A próxima seção detalha a relação entre iteráveis e iteradores.((("", startref="Iinterfun17")))((("", startref="iterfunc17")))((("", startref="Oiter17")))((("", startref="COiter17"))) +A próxima seção detalha a relação entre iteráveis e iteradores.((("", +startref="Iinterfun17")))((("", startref="iterfunc17")))((("", +startref="Oiter17")))((("", startref="COiter17"))) === Iteráveis versus iteradores -Da((("iterators", "versus iterables", secondary-sortas="iterables", id="IOvie17")))((("iterables", "versus iterators", secondary-sortas="iterators", id="IEvio17"))) explicação na <> podemos extrapolar a seguinte definição: +Da((("iterators", "versus iterables", secondary-sortas="iterables", +id="IOvie17")))((("iterables", "versus iterators", secondary-sortas="iterators", +id="IEvio17"))) explicação na <> podemos extrapolar a seguinte +definição: -iterável:: Qualquer objeto a partir do qual a função embutida `iter` consegue obter um iterador. -Objetos que implementam um método `+__iter__+` devolvendo um _iterador_ são iteráveis. -Sequências são sempre iteráveis, bem como objetos que implementam um método `+__getitem__+` que aceite índices iniciando em 0. +iterável:: Qualquer objeto a partir do qual a função embutida `iter` consegue +obter um iterador. Objetos que implementam um método `+__iter__+` devolvendo um +_iterador_ são iteráveis. Sequências são sempre iteráveis, bem como objetos que +implementam um método `+__getitem__+` que aceite índices iniciando em 0. -É importante deixar clara a relação entre iteráveis e iteradores: Python obtém um iterador a partir de um iterável. +É importante deixar clara a relação entre iteráveis e iteradores: Python obtém +um iterador a partir de um iterável. -Aqui está um simples loop `for` iterando sobre uma `str`. A `str` `'ABC'` é o iterável aqui. Você não vê, mas há um iterador por trás das cortinas: +Aqui está um simples laço `for` iterando sobre uma `str`. A `str` `'ABC'` é o +iterável aqui. Você não vê, mas há um iterador por trás das cortinas: [source, python] ---- @@ -275,7 +376,8 @@ B C ---- -Se não existisse uma instrução `for` e fosse preciso emular o mecanismo do `for` à mão com um loop `while`, isso é o que teríamos que escrever: +Se não existisse uma instrução `for` e fosse preciso emular o mecanismo do `for` +à mão com um laço `while`, isso é o que teríamos que escrever: [source, python] ---- @@ -296,29 +398,30 @@ C <2> Chama `next` repetidamente com o iterador, para obter o item seguinte. <3> O iterador gera `StopIteration` quando não há mais itens. <4> Libera a referência a `it`—o objeto iterador é descartado. -<5> Sai do loop. -<6> Exibe `char`. Essa variável continua existindo depois do loop. +<5> Sai do laço. +<6> Exibe `char`. Esta variável continua existindo depois do laço. `StopIteration` sinaliza que o iterador foi exaurido. -Essa exceção é tratada internamente pela função embutida `iter()`, -que é parte da lógica dos loops `for` e de outros contextos de iteração, -como compreensões de lista, desempacotamento iterável, etc. +Esta exceção é tratada internamente pela função embutida `iter()`, +que é parte da lógica dos laços `for` e de outros contextos de iteração, +como compreensões de lista, desempacotamento de iteráveis, etc. A interface padrão de um iterador em Python tem dois métodos: -`+__next__+`:: Devolve o próximo item da série, gerando `StopIteration` se não há mais nenhum. +`+__next__+`:: Devolve o próximo item da série, +gerando `StopIteration` se não há mais nenhum. -`+__iter__+`:: Devolve `self`; isso permite que um iterador seja usado quando um iterável é esperado. Por exemplo, em um laço `for`. +`+__iter__+`:: Devolve `self`; assim o iterador pode ser +usado quando um iterável é esperado. Por exemplo, em um laço `for`. -Essa interface está formalizada na ABC `collections.abc.Iterator`, +Esta interface está formalizada na ABC `collections.abc.Iterator`, que declara o método abstrato `+__next__+`, e é uma subclasse de ++Iterable++—onde o método abstrato `+__iter__+` é declarado. Veja a <>. -[role="width-70"] [[iterable_fig]] .As ABCs `Iterable` e `Iterator`. Métodos em itálico são abstratos. Um `+Iterable.__iter__+` concreto deve devolver uma nova instância de `Iterator`. Um `Iterator` concreto deve implementar `+__next__+`. O método `+Iterator.__iter__+` apenas devolve a própria instância. -image::../images/flpy_1701.png[Diagrama UML de Iterable] +image::../images/flpy_1701.png[align="center",pdfwidth=9cm] O código-fonte de `collections.abc.Iterator` aparece no <>. @@ -346,35 +449,53 @@ class Iterator(Iterable): return NotImplemented ---- ==== -<1> `+__subclasshook__+` suporta a checagem de tipos estrutural com `isinstance` e `issubclass`. Vimos isso na <>. -<2> `_check_methods` percorre o parâmetro `+__mro__+` da classe, para checar se os métodos estão implementados em sua classe base. -Ele está definido no mesmo módulo, __Lib/_collections_abc.py__. -Se os métodos estiverem implementados, a classe `C` será reconhecida como uma subclasse virtual de `Iterator`. -Em outras palavras, `issubclass(C, Iterable)` devolverá `True`. + +<1> `+__subclasshook__+` suporta a checagem de tipos estrutural com `isinstance` +e `issubclass`. Vimos isso na <>. + +<2> `_check_methods` percorre o parâmetro `+__mro__+` da classe, para checar se +os métodos estão implementados em sua classe base. Ele está definido no mesmo +módulo, __Lib/_collections_abc.py__. Se os métodos estiverem implementados, a +classe `C` será reconhecida como uma subclasse virtual de `Iterator`. Em outras +palavras, `issubclass(C, Iterable)` devolverá `True`. [WARNING] ==== -O método abstrato da ABC `Iterator` é `+it.__next__()+` no Python 3 e `it.next()` no Python 2. Como sempre, você deve evitar invocar métodos especiais diretamente. -Use apenas `next(it)`: essa função embutida faz a coisa certa no Python 2 e no 3—algo útil para quem está migrando bases de código do 2 para o 3. + +O método abstrato da ABC `Iterator` é `+it.__next__()+` no Python 3 e +`it.next()` no Python 2. Como sempre, você deve evitar invocar métodos especiais +diretamente. Use apenas `next(it)`: essa função embutida faz a coisa certa no +Python 2 e no 3—algo útil para quem está migrando bases de código do 2 para o 3. + ==== -O código-fonte do módulo https://fpy.li/17-6[_Lib/types.py_] no Python 3.9 tem um comentário dizendo: +O código-fonte do módulo https://fpy.li/17-6[_Lib/types.py_] no Python 3.9 tem +um comentário dizendo: ---- -# Iteradores no Python não são uma questão de tipo, mas sim de protocolo. Um número -# grande e variável de tipos embutidos implementa *alguma* forma de -# iterador. Não verifique o tipo! Em vez disso, use `hasattr` para -# checar [a existência] de ambos os atributos "__iter__" e "__next__". +# Iteradores no Python não são uma questão de tipo, mas sim de protocolo. +# Um número grande e variável de tipos embutidos implementa *alguma* forma +# de iterador. Não verifique o tipo! Em vez disso, use `hasattr` para +# detectar os atributos "__iter__" e "__next__". ---- -E de fato, é exatamente o que o método `+__subclasshook__+` da ABC `abc.Iterator` faz. +E de fato, é exatamente o que o método `+__subclasshook__+` da ABC +`abc.Iterator` faz. [TIP] ==== -Dado o conselho de __Lib/types.py__ e a lógica implementada em __Lib/_collections_abc.py__, a melhor forma de checar se um objeto `x` é um iterador é invocar `isinstance(x, abc.Iterator)`. Graças ao `+Iterator.__subclasshook__+`, esse teste funciona mesmo se a classe de `x` não for uma subclasse real ou virtual de `Iterator`. + +Dado o conselho de __Lib/types.py__ e a lógica implementada em +__Lib/_collections_abc.py__, a melhor forma de checar se um objeto `x` é um +iterador é invocar `isinstance(x, abc.Iterator)`. Graças ao +`+Iterator.__subclasshook__+`, esse teste funciona mesmo se a classe de `x` não +for uma subclasse real ou virtual de `Iterator`. + ==== -Voltando à nossa classe `Sentence` no <>, usando o console de Python é possivel ver claramente como o iterador é criado por `iter()` e consumido por `next()`: +Voltando à nossa classe `Sentence` no <>, usando o console de +Python é possivel ver claramente como o iterador é criado por `iter()` e +consumido por `next()`: [source, python] ---- @@ -401,24 +522,36 @@ StopIteration <2> Obtém um iterador a partir de `s3`. <3> `next(it)` devolve a próxima palavra. <4> Não há mais palavras, então o iterador gera uma exceção `StopIteration`. -<5> Uma vez exaurido, um itereador irá sempre gerar `StopIteration`, o que faz parecer que ele está vazio.. +<5> Uma vez exaurido, um iterador vai sempre lançar `StopIteration`, +indicando que não há mais itens. <6> Para percorrer a sentença novamente é preciso criar um novo iterador. -Como os únicos métodos exigidos de um iterador são `+__next__+` e `+__iter__+`, não há como checar se há itens restantes, exceto invocando `next()` e capturando `StopIteration`. -Além disso, não é possível "reiniciar" um iterador. -Se for necessário começar de novo, é preciso invocar `iter()` no iterável que criou o iterador original. -Invocar `iter()` no próprio iterador também não funciona, pois—como já mencionado—a implementação de pass:[Iterator.__iter__] apenas devolve `self`, e isso não reinicia um iterador exaurido. +Como os únicos métodos exigidos de um iterador são `+__next__+` e `+__iter__+`, +não há como checar se há itens restantes, exceto invocando `next()` e capturando +`StopIteration`. Além disso, não é possível "reiniciar" um iterador. Se for +necessário começar de novo, é preciso invocar `iter()` no iterável que criou o +iterador original. Invocar `iter()` no próprio iterador também não funciona, +pois—como já mencionado—a implementação de `+Iterator.__iter__+` +apenas devolve `self`, e isso não reinicia um iterador exaurido. -Essa interface mínima é bastante razoável porque, na realidade, nem todos os itereadores são reiniciáveis. -Por exemplo, se um iterador está lendo pacotes da rede, não há como "rebobiná-lo".footnote:[Agradeço ao revisor técnico Leonardo Rochael por esse ótimo exemplo.] +Essa interface mínima é bastante razoável porque, na realidade, nem todos os +iteradores são reiniciáveis. Por exemplo, se um iterador está lendo pacotes da +rede, não há como "rebobiná-lo".footnote:[Agradeço ao revisor técnico Leonardo +Rochael por esse ótimo exemplo.] -A primeira versão de `Sentence`, no <>, era iterável graças ao tratamento especial dispensado pela função embutida às sequências. -A seguir vamos implementar variações de `Sentence` que implementam `+__iter__+` para devolver iteradores.((("", startref="IOvie17")))((("", startref="IEvio17"))) +A primeira versão de `Sentence`, no <>, era iterável graças ao +tratamento especial dispensado pela função `iter` às sequências. A seguir +vamos implementar variações de `Sentence` que implementam `+__iter__+` para +devolver iteradores.((("", startref="IOvie17")))((("", startref="IEvio17"))) -=== Classes Sentence com pass:[__iter__] +=== Classes `Sentence` com `+__iter__+` -As((("iterators", "Sentence classes with __iter__", id="ITsentence17")))((("__iter__", id="iter17")))((("Sentence classes", id="sentclass17"))) próximas variantes de `Sentence` implementam o protocolo iterável padrão, primeiro implementando o padrão de projeto _Iterable_ e depois com funções geradoras. +As((("iterators", "Sentence classes with __iter__", +id="ITsentence17")))((("__iter__", +id="iter17")))((("Sentence classes", id="sentclass17"))) próximas variantes de +`Sentence` implementam o protocolo iterável padrão, primeiro implementando o +padrão de projeto _Iterable_ e depois com funções geradoras. ==== Sentence versão #2: um iterador clássico @@ -438,40 +571,48 @@ que cria e devolve um `SentenceIterator`. É assim que um iterável e um iterado include::../code/17-it-generator/sentence_iter.py[tags=SENTENCE_ITER] ---- ==== -<1> O método `+__iter__+` é o único acréscimo à implementação anterior de `Sentence`. Essa versão não inclui um `+__getitem__+`, para deixar claro que a classe é iterável por implementar -`+__iter__+`. +<1> O método `+__iter__+` é o único acréscimo à implementação anterior de +`Sentence`. Essa versão não inclui um `+__getitem__+`, para deixar claro que a +classe é iterável por implementar `+__iter__+`. <2> `+__iter__+` atende ao protocolo iterável instanciando e devolvendo um iterador. <3> `SentenceIterator` mantém uma referência para a lista de palavras. <4> `self.index` determina a próxima palavra a ser recuperada. <5> Obtém a palavra em `self.index`. -<6> Se não há palavra em `self.index`, gera uma `StopIteration`. +<6> Se não há palavra em `self.index`, levanta `StopIteration`. <7> Incrementa `self.index`. <8> Devolve a palavra. -<9> Implementa `+self.__iter__+`. +<9> Implementa `+self.__iter__+` para suportar `iter(self)`. O código do <> passa nos testes do <>. -Veja que não é de fato necessário implementar `+__iter__+` em `SentenceIterator` para esse exemplo funcionar, mas é o correto a fazer: supõe-se que iteradores implementem tanto `+__next__+` quanto -`+__iter__+`, e fazer isso permite ao nosso iterador passar no teste -`issubclass(SentenceIterator, abc.Iterator)`. -Se tivéssemos tornado `SentenceIterator` uma subclasse de `abc.Iterator`, teríamos herdado o método concreto `+abc.Iterator.__iter__+`. +Veja que não é de fato necessário implementar `+__iter__+` em `SentenceIterator` +para este exemplo funcionar, mas é recomendado: supõe-se que iteradores +implementem tanto `+__next__+` quanto `+__iter__+`, e fazer isso permite ao +nosso iterador passar no teste `issubclass(SentenceIterator, abc.Iterator)`. Se +tivéssemos tornado `SentenceIterator` uma subclasse de `abc.Iterator`, teríamos +herdado o método concreto `+abc.Iterator.__iter__+`. -É um bocado de trabalho (pelo menos para nós, programadores mimados pelo Python). -Observe que a maior parte do código em `SentenceIterator` serve para gerenciar o estado interno do iterador. Logo veremos como evitar essa burocracia. -Mas antes, um pequeno desvio para tratar de um atalho de implementação que pode parecer tentador, mas é apenas errado. +É bastante trabalho (pelo menos para nós, programadores mimados pelo +Python). Observe que a maior parte do código em `SentenceIterator` serve para +gerenciar o estado interno do iterador. Logo veremos como evitar essa +burocracia. Mas antes, um pequeno desvio para tratar de um atalho de +implementação que pode parecer tentador, mas é apenas errado. [[iterable_not_self_iterator_sec]] ==== Não torne o iterável também um iterador -Uma causa comum de erros na criação de iteráveis é confundir os dois. -Para deixar claro: iteráveis têm um método `+__iter__+` que instancia um novo iterador a cada invocação. -Iteradores implementam um método `+__next__+`, que devolve itens individuais, e um método +Uma causa comum de erros na criação de iteráveis e iteradores é confundir os dois. +Para deixar claro: um iterável tem um método `+__iter__+` que instancia um novo iterador a cada invocação. +Um iterador implementam um método `+__next__+`, que devolve itens individuais, e um método `+__iter__+`, que devolve `self`. Assim, iteradores também são iteráveis, mas iteráveis não são iteradores. -Pode ser tentador implementar `+__next__+` além de `+__iter__+` na classe `Sentence`, tornando cada instância de `Sentence` ao mesmo tempo um iterável e um iterador de si mesma. -Mas raramente isso é uma boa ideia. Também é um anti-padrão comum, de acordo com Alex Martelli, que possui vasta experiência revisando código no Google. +Pode ser tentador implementar `+__next__+` além de `+__iter__+` na classe +`Sentence`, tornando cada instância de `Sentence` ao mesmo tempo um iterável e +um iterador de si mesma. Mas raramente isso é uma boa ideia. Também é um +anti-padrão comum, de acordo com Alex Martelli, que tem vasta experiência +revisando código no Google. A seção "Aplicabilidade" do padrão de projeto _Iterator_ no livro _Padrões de Projeto_ diz: @@ -486,45 +627,88 @@ Use o padrão Iterator * para fornecer uma interface uniforme para atravessar diferentes estruturas agregadas (isto é, para suportar iteração polimórfica). ____ -Para "suportar travessias múltiplas", deve ser possível obter múltiplos iteradores independentes de uma mesma instância iterável, e cada iterador deve manter seu próprio estado interno. Assim, uma implementação adequada do padrão exige que cada invocação de `iter(my_iterable)` crie um novo iterador independente. É por essa razão que precisamos da classe `SentenceIterator` neste exemplo. +Para "suportar travessias múltiplas", deve ser possível obter múltiplos +iteradores independentes a partir de um mesmo objeto iterável, e cada +iterador deve manter seu próprio estado interno. Assim, uma implementação +adequada do padrão exige que cada invocação de `iter(meu_iterável)` crie um novo +iterador independente. Por isso precisamos da classe +`SentenceIterator` neste exemplo. -Agora((("yield keyword", id="yielda17")))((("keywords", "yield keyword", id=Kyielda17"))) que demonstramos de forma apropriada o padrão _Iterator_ clássico, vamos em frente. -Python incorporou a instrução `yield` da https://fpy.li/17-7[linguagem CLU], de Barbara Liskov, para não termos que "escrever à mão" o código implementando iteradores. +Agora((("yield keyword", id="yielda17")))((("keywords", "yield keyword", +id=Kyielda17"))) que demonstramos de forma apropriada o padrão _Iterator_ +clássico, vamos em frente. Python incorporou a instrução `yield` da +https://fpy.li/17-7[linguagem CLU], de Barbara Liskov, para não termos que +"escrever à mão" o código implementando iteradores. As próximas seções apresentam versões mais idiomáticas de `Sentence`. ==== Sentence versão #3: uma função geradora -Uma((("generators", "Sentence classes with"))) implementação pythônica da mesma funcionalidade usa uma geradora, evitando todo o trabalho para implementar a classe `SentenceIterator`. A explicação completa da geradora está logo após o <>. +Uma((("generators", "Sentence classes with"))) implementação pythônica da mesma +funcionalidade usa um gerador, evitando todo o trabalho para implementar a +classe `SentenceIterator`. A explicação completa do gerador está logo após o +<>. [[ex_sentence2]] -.sentence_gen.py: `Sentence` implementada usando uma geradora +.sentence_gen.py: `Sentence` implementada usando um gerador ==== [source, python] ---- include::../code/17-it-generator/sentence_gen.py[tags=SENTENCE_GEN] ---- ==== + <1> Itera sobre `self.words`. + <2> Produz a `word` atual. -<3> Um `return` explícito não é necessário; a função pode apenas seguir em frente e retornar automaticamente. De qualquer das formas, uma função geradora não gera `StopIteration`: ela simplesmente termina quando acaba de produzir valores.footnote:[Ao revisar esse código, Alex Martelli sugeriu que o corpo deste método poderia ser simplesmente `return iter(self.words)`. Ele está certo: o resultado da invocação de `self.words.__iter__()` também seria um iterador, como deve ser. Entretanto, usei um loop `for` com `yield` aqui para introduzir a sintaxe de uma função geradora, que exige a instrução `yield`, como veremos na próxima seção. Durante a revisão da segunda edição deste livro, Leonardo Rochael sugeriu ainda outro atalho para o corpo de `+__iter__+`: `yield from self.words`. Também vamos falar de `yield from` mais adiante neste mesmo capítulo.] + +<3> Um `return` explícito não é necessário; a função pode apenas seguir em +frente e retornar automaticamente. De qualquer forma, uma função geradora +não gera `StopIteration`: ela simplesmente termina quando acaba de produzir +valores.footnote:[Ao revisar esse código, Alex Martelli sugeriu que o corpo +deste método poderia ser simplesmente `return iter(self.words)`. Ele está certo: +o resultado da invocação de `self.words.__iter__()` também seria um iterador, +como deve ser. Entretanto, usei um laço `for` com `yield` aqui para introduzir a +sintaxe de uma função geradora, que exige a instrução `yield`, como veremos na +próxima seção. Durante a revisão da segunda edição deste livro, Leonardo Rochael +sugeriu ainda outro atalho para o corpo de `+__iter__+`: `yield from +self.words`. Também vamos falar de `yield from` mais adiante neste mesmo +capítulo.] + <4> Não há necessidade de uma classe iteradora separada! -Novamente temos aqui uma implementação diferente de `Sentence` que passa nos testes do <>. +Temos aqui mais uma implementação de `Sentence` que passa nos +testes do <>. -No código de `Sentence` do <>, `+__iter__+` chamava o construtor `SentenceIterator` para criar e devolver um iterador. Agora o iterador do <> é na verdade um objeto gerador, criado automaticamente quando o método `+__iter__+` é invocado, porque aqui `+__iter__+` é uma função geradora. +No código de `Sentence` do <>, `+__iter__+` chamava o construtor +`SentenceIterator` para criar e devolver um iterador. Agora o iterador do +<> é na verdade um objeto gerador, criado automaticamente quando o +método `+__iter__+` é invocado, porque aqui `+__iter__+` é uma função geradora. -Segue abaixo uma explicação completa das geradoras. +A seguir: uma explicação bem completa sobre geradores. -==== Como funciona uma geradora +==== Como funciona um gerador -Qualquer((("generators", "yield keyword"))) função de Python contendo a instrução `yield` em seu corpo é uma função geradora: uma função que, quando invocada, devolve um objeto gerador. Em outras palavras, um função geradora é uma fábrica de geradores. +Qualquer((("generators", "yield keyword"))) função de Python contendo a +instrução `yield` em seu corpo é uma função geradora: uma função que, quando +invocada, devolve um objeto gerador. Em outras palavras, um função geradora é +uma fábrica de geradores. [role="man-height3"] [TIP] ==== -O único elemento sintático distinguindo uma função comum de uma função geradora é o fato dessa última conter a instrução `yield` em algum lugar de seu corpo. Alguns defenderam que uma nova palavra reservada, algo como `gen`, deveria ser usada no lugar de `def` para declarar funções geradoras, mas Guido não concordou. Seus argumentos estão na https://fpy.li/pep255[PEP 255 -- Simple Generators (_Geradoras Simples_)].footnote:[Eu algumas vezes acrescento um prefixo ou sufixo `gen` ao nomear funções geradoras, mas essa não é uma prática comum. E claro que não é possível fazer isso ao implementar um iterável: o método especial obrigatório deve se chamar `+__iter__+`.] + +O único elemento sintático que identifica uma função geradora +é a presença da instrução `yield` em algum lugar de seu corpo. +Alguns defenderam que uma nova palavra reservada (como `gen`), deveria ser +usada no lugar de `def` para declarar funções geradoras, mas Guido não +concordou. Seus argumentos estão na https://fpy.li/pep255[_PEP 255—Simple +Generators_] (Geradoras Simples).footnote:[Eu algumas vezes acrescento um +prefixo ou sufixo `gen` ao nomear funções geradoras, mas essa não é uma prática +comum. E claro que não é possível fazer isso ao implementar um iterável: o +método especial obrigatório deve se chamar `+__iter__+`.] + ==== O <> mostra o comportamento de uma função geradora simples.footnote:[Agradeço a David Kwast por sugerir esse exemplo.] @@ -561,26 +745,47 @@ Traceback (most recent call last): StopIteration ---- ==== -<1> O corpo de uma função geradora muitas vezes contém `yield` dentro de um loop, mas não necessariamente; aqui eu apenas repeti `yield` três vezes. + +<1> O corpo de uma função geradora muitas vezes contém `yield` dentro de um +laço, mas não necessariamente; aqui eu apenas repeti `yield` três vezes. + <2> Olhando mais de perto, vemos que `gen_123` é um objeto função. + <3> Mas quando invocado, `gen_123()` devolve um objeto gerador. -<4> Objetos geradores implementam a interface `Iterator`, então são também iteráveis. -<5> Atribuímos esse novo objeto gerador a `g`, para podermos experimentar seu funcionamento. -<6> Como `g` é um iterador, chamar `next(g)` obtém o próximo item produzido por `yield`. -<7> Quando a função geradora retorna, o objeto gerador gera uma `StopIteration`. + +<4> Objetos geradores implementam a interface `Iterator`, então são também +iteráveis. + +<5> Atribuímos esse novo objeto gerador a `g`, para podermos testar seu +funcionamento. + +<6> Como `g` é um iterador, chamar `next(g)` obtém o próximo item produzido por +`yield`. + +<7> Quando a função geradora retorna, o objeto gerador levanta uma `StopIteration`. Uma função geradora cria um objeto gerador que encapsula o corpo da função. Quando invocamos `next()` no objeto gerador, a execução avança para o próximo `yield` no corpo da função, e a chamada a `next()` resulta no valor produzido quando o corpo da função é suspenso. -Por fim, o objeto gerador externo criado pelo Python gera uma `StopIteration` quando a função retorna, de acordo com o protocolo `Iterator`. +Por fim, o objeto gerador externo criado pelo Python levanta `StopIteration` quando a função retorna, de acordo com o protocolo `Iterator`. [TIP] ==== -Acho útil ser rigoroso ao falar sobre valores obtidos a partir de um gerador. É confuso dizer que um gerador "devolve" valores. Funções devolvem valores. A chamada a uma função geradora devolve um gerador. Um gerador produz (_yields_) valores. Um gerador não "devolve" valores no sentido comum do termo: a instrução `return` no corpo de uma função geradora faz com que uma `StopIteration` seja criada pelo objeto gerador. Se você escrever `return x` na função geradora, quem a chamou pode recuperar o valor de `x` na exceção `StopIteration`, mas normalmente isso é feito automaticamente usando a sintaxe `yield from`, como veremos na <>. + +Acho útil ser rigoroso ao falar sobre valores obtidos a partir de um gerador. É +confuso dizer que um gerador "devolve" ou "retorna" valores. Funções devolvem +valores. A chamada a uma função geradora devolve um gerador. Um gerador produz +(_yields_) valores. Um gerador não "devolve" valores no sentido comum do termo: +a instrução `return` no corpo de uma função geradora faz com que uma +`StopIteration` seja levantada pelo objeto gerador. Se você escrever `return x` +na função geradora, quem a chamou pode recuperar o valor de `x` enbrulhado na +exceção `StopIteration`, mas normalmente isso é feito automaticamente usando a +sintaxe `yield from`, como veremos na <>. + ==== -O <> torna a iteração entre um loop `for` e o corpo da função mais explícita. +O <> torna a interação entre um laço `for` e o corpo da função mais explícita. [[ex_gen_ab]] .Uma função geradora que exibe mensagens quando roda @@ -605,42 +810,91 @@ end. <10> >>> <11> ---- ==== -<1> A primeira chamada implícita a `next()` no loop `for` em pass:[4] vai exibir `'start'` e parar no primeiro `yield`, produzindo o valor `'A'`. -<2> A segunda chamada implícita a `next()` no loop `for` vai exibir `'continue'` e parar no segundo `yield`, produzindo o valor `'B'`. -<3> A terceira chamada a `next()` vai exibir `'end.'` e continuar até o final do corpo da função, fazendo com que o objeto gerador crie uma `StopIteration`. -<4> Para iterar, o mecanismo do `for` faz o equivalente a `g = iter(gen_AB())` para obter um objeto gerador, e daí `next(g)` a cada iteração. -<5> O loop exibe `-->` e o valor devolvido por `next(g)`. Esse resultado só aparece após a saída das chamadas `print` dentro da função geradora. -<6> O texto `start` vem de `print('start')` no corpo da geradora. -<7> `yield 'A'` no corpo da geradora produz o valor 'A' consumido pelo loop `for`, que é atribuído à variável `c` e resulta na saída `--> A`. -<8> A iteração continua com a segunda chamada a `next(g)`, avançando no corpo da geradora de -`yield 'A'` para `yield 'B'`. O texto `continue` é gerado pelo segundo `print` no corpo da geradora. -<9> `yield 'B'` produz o valor 'B' consumido pelo loop `for`, que é atribuído à variável `c` do loop, que então exibe `--> B`. -<10> A iteração continua com uma terceira chamada a `next(it)`, avançando para o final do corpo da função. O texto `end.` é exibido por causa do terceiro `print` no corpo da geradora. -<11> Quando a função geradora chega ao final, o objeto gerador cria uma `StopIteration`. O mecanismo do loop `for` captura essa exceção, e o loop encerra naturalmente. -Espero agora ter deixado claro como `+Sentence.__iter__+` no <> funciona: `+__iter__+` é uma função geradora que, quando chamada, cria um objeto gerador que implementa a interface `Iterator`, então a classe `SentenceIterator` não é mais necessária. +<1> A primeira chamada implícita a `next()` no laço `for` em `④` vai exibir +`'start'` e parar no primeiro `yield`, produzindo o valor `'A'`. + +<2> A segunda chamada implícita a `next()` no laço `for` vai exibir `'continue'` +e parar no segundo `yield`, produzindo o valor `'B'`. + +<3> A terceira chamada a `next()` vai exibir `'end.'` e continuar até o final do +corpo da função, fazendo com que o objeto gerador levante uma `StopIteration`. + +<4> Para iterar, o mecanismo do `for` faz o equivalente a `g = iter(gen_AB())` +para obter um objeto gerador, e daí `next(g)` a cada iteração. + +<5> O laço exibe `-{rt-arrow}` e o valor devolvido por `next(g)`. Esse resultado +só aparece após a saída das chamadas `print` dentro da função geradora. + +<6> O texto `start` vem de `print('start')` no corpo do gerador. + +<7> `yield 'A'` no corpo do gerador produz o valor `'A'` consumido pelo laço +`for`, que é atribuído à variável `c` e resulta na saída `-{rt-arrow} A`. + +<8> A iteração continua com a segunda chamada a `next(g)`, avançando no corpo do +gerador de `yield 'A'` para `yield 'B'`. O texto `continue` é gerado pelo +segundo `print` no corpo do gerador. + +<9> `yield 'B'` produz o valor 'B' consumido pelo laço `for`, que é atribuído à +variável `c` do laço, que então exibe `-{rt-arrow} B`. + +<10> A iteração continua com uma terceira chamada a `next(it)`, avançando para o +final do corpo da função. O texto `end.` é exibido por causa do terceiro `print` +no corpo do gerador. + +<11> Quando a função geradora chega ao final, o objeto gerador levanta uma +`StopIteration`. O mecanismo do laço `for` captura essa exceção, e o laço +encerra naturalmente. -A segunda versão de `Sentence` é mais concisa que a primeira, mas não é tão preguiçosa quanto poderia ser. Atualmente, a _preguiça_ é considerada uma virtude, pelo menos em linguagens de programação e APIs. Uma implementação preguiçosa adia a produção de valores até o último momento possível. Isso economiza memória e também pode evitar o desperdício de ciclos da CPU. +Espero agora ter deixado claro como `+Sentence.__iter__+` no <> +funciona: `+__iter__+` é uma função geradora que, quando invocada, cria um objeto +gerador que implementa a interface `Iterator`, então a classe `SentenceIterator` +não é mais necessária. -Vamos criar a seguir classes `Sentence` preguiçosas.((("", startref="ITsentence17")))((("", startref="iter17")))((("", startref="sentclass17")))((("", startref="genex17")))((("", startref="yielda17")))((("", startref="Kyielda17"))) +A segunda versão de `Sentence` é mais concisa que a primeira, mas não é tão +preguiçosa quanto poderia ser. Atualmente, a _preguiça_ é considerada uma +virtude, pelo menos em linguagens de programação e APIs. Uma implementação +preguiçosa adia a produção de valores até o último momento possível. Isso +economiza memória e também pode evitar o desperdício de ciclos de CPU. + +A seguir, criaremos classes `Sentence` preguiçosas.((("", +startref="ITsentence17")))((("", startref="iter17")))((("", +startref="sentclass17")))((("", startref="genex17")))((("", +startref="yielda17")))((("", startref="Kyielda17"))) === Sentenças preguiçosas -As((("iterators", "lazy sentences", id="Ilazy17")))((("generators", "lazy generators", id="Glazy17")))((("lazy sentences", id="lazysen17"))) últimas variações de `Sentence` são preguiçosas, se valendo de um função preguiçosa do módulo `re`. +As((("iterators", "lazy sentences", id="Ilazy17")))((("generators", +"lazy generators", id="Glazy17")))((("lazy sentences", id="lazysen17"))) +últimas variações de `Sentence` são preguiçosas, valendo-se de um +função geradora do módulo `re`. + + +==== Sentence versão #4: um gerador preguiçoso +A interface `Iterator` foi projetada para ser preguiçosa: `next(my_iterator)` +produz um item por vez. O oposto de preguiçosa é ávida: avaliação preguiçosa +(_lazy evaluation_) e ávida (_eager evaluation_) são termos técnicos da teoria +das linguagens de programação.footnote:[NT: Em português a +literatura usa também avaliação _estrita_ e _não estrita_. +Optamos pelos termos "preguiçosa" e "ávida", que parecem mais claros.] -==== Sentence versão #4: uma geradora preguiçosa +//// +parei aqui em 23/dez, 19:20 + +TODO: trocar "loop" por "laço" ao longo do livro, começando por +"laço `for`" -> "laço `for`" +"loop infinito" -> "laço infito". -A interface `Iterator` foi projetada para ser preguiçosa: `next(my_iterator)` produz um item por vez. -O oposto de preguiçosa é ávida: avaliação preguiçosa e ávida são termos técnicos da teoria das linguagens de programaçãofootnote:[NT: Os termos em inglês são _lazy_ (preguiçosa) e _eager_ (ávida). Em português essas traduções aparecem, mas a literatura usa também avaliação _estrita_ e avaliação _não estrita_. Optamos pelos termos "preguiçosa" e "ávida", que parecem mais claros.]. +//// Até aqui, nossas implementações de `Sentence` não são preguiçosas, pois o `+__init__+` cria avidamemente uma lista com todas as palavras no texto, vinculando-as ao atributo `self.words`. Isso exige o processamento do texto inteiro, e a lista pode acabar usando tanta memória quanto o próprio texto (provavelmente mais: vai depender de quantos caracteres que não fazem parte de palavras existirem no texto). A maior parte desse trabalho será inútil se o usuário iterar apenas sobre as primeiras palavras. Se você está se perguntado se "Existiria uma forma preguiçosa de fazer isso em Python?", a resposta muitas vezes é "Sim". -A função `re.finditer` é uma versão preguiçosa de `re.findall`. Em vez de uma lista, `re.finditer` devolve uma geradora que produz instâncias de `re.MatchObject` sob demanda. +A função `re.finditer` é uma versão preguiçosa de `re.findall`. Em vez de uma lista, `re.finditer` devolve um gerador que produz instâncias de `re.MatchObject` sob demanda. Se existirem muitos itens, `re.finditer` economiza muita memória. Com ela, nossa terceira versão de `Sentence` agora é preguiçosa: ela só lê a próxima palavra do texto quando necessário. @@ -703,10 +957,10 @@ end. ==== <1> Está é a mesma função `gen_AB` do <>. <2> A compreensão de lista itera avidamente sobre os itens produzidos pelo objeto gerador devolvido por `gen_AB()`: `'A'` e `'B'`. Observe a saída nas linhas seguintes: `start`, `continue`, `end.` -<3> Esse loop `for` itera sobre a lista `res1` criada pela compreensão de lista. +<3> Esse laço `for` itera sobre a lista `res1` criada pela compreensão de lista. <4> A expressão geradora devolve `res2`, um objeto gerador. O gerador não é consumido aqui. -<5> Este gerador obtém itens de `gen_AB` apenas quando o loop `for` itera sobre `res2`. Cada iteração do loop `for` invoca, implicitamente, `next(res2)`, que por sua vez invoca `next()` sobre o objeto gerador devolvido por `gen_AB()`, fazendo este último avançar até o próximo `yield`. -<6> Observe como a saída de `gen_AB()` se intercala com a saída do `print` no loop `for`. +<5> Este gerador obtém itens de `gen_AB` apenas quando o laço `for` itera sobre `res2`. Cada iteração do laço `for` invoca, implicitamente, `next(res2)`, que por sua vez invoca `next()` sobre o objeto gerador devolvido por `gen_AB()`, fazendo este último avançar até o próximo `yield`. +<6> Observe como a saída de `gen_AB()` se intercala com a saída do `print` no laço `for`. Podemos usar uma expressão geradora para reduzir ainda mais o código na classe `Sentence`. Veja o <>. @@ -726,7 +980,7 @@ Expressões geradoras são "açúcar sintático": elas pode sempre ser substitu === Quando usar expressões geradoras -Eu((("generators", "when to use generator expressions")))((("generator expressions (genexps)"))) usei várias expressões geradoras quando implementamos a classe `Vector` no <>. Cada um destes métodos contém uma expressão geradora: `+__eq__+`, `+__hash__+`, `+__abs__+`, `angle`, `angles`, pass:[format], `+__add__+`, e `+__mul__+`. +Eu((("generators", "when to use generator expressions")))((("generator expressions (genexps)"))) usei várias expressões geradoras quando implementamos a classe `Vector` no <> do <>. Cada um destes métodos contém uma expressão geradora: `+__eq__+`, `+__hash__+`, `+__abs__+`, `angle`, `angles`, pass:[format], `+__add__+`, e `+__mul__+`. // has a generator expression. Em todos aqueles métodos, uma compreensão de lista também funcionaria, com um custo adicional de memória para armazenar os valores da lista intermediária. @@ -740,7 +994,7 @@ Minha regra básica para escolher qual sintaxe usar é simples: se a expressão [TIP] .Dica de sintaxe ==== -Quando uma expressão geradora é passada como único argumento a uma função ou a um construtor, não é necessário escrever um conjunto de parênteses para a chamada da função e outro par cercando a expressão geradora. Um único par é suficiente, como na chamada a `Vector` no método `+__mul__+` do <>, reproduzido abaixo: +Quando uma expressão geradora é passada como único argumento a uma função ou a um construtor, não é necessário escrever um conjunto de parênteses para a chamada da função e outro par cercando a expressão geradora. Um único par é suficiente, como na chamada a `Vector` no método `+__mul__+` do <> do <>, reproduzido abaixo: [source, python] ---- @@ -769,24 +1023,24 @@ Adotei as seguintes definições: iterador:: Termo geral para qualquer objeto que implementa um método `+__next__+`. - Iteradores são projetados para produzir dados a serem consumidos pelo código cliente, isto é, o código que controla o iterador através de um loop `for` ou outro mecanismo de iteração, ou chamando `next(it)` explicitamente no iterador—apesar desse uso explícito ser menos comum. + Iteradores são projetados para produzir dados a serem consumidos pelo código cliente, isto é, o código que controla o iterador através de um laço `for` ou outro mecanismo de iteração, ou chamando `next(it)` explicitamente no iterador—apesar desse uso explícito ser menos comum. Na prática, a maioria dos iteradores que usamos no Python são _geradores_. gerador:: Um iterador criado pelo compilador Python. - Para criar um gerador, não implementamos `+__next__+`. + Para criar um gerador, não implementamos uma classe com `+__next__+`. Em vez disso, usamos((("yield keyword")))((("keywords", "yield keyword"))) a palavra reservada `yield` para criar uma _função geradora_, que é uma fábrica de _objetos geradores_. Uma _expressão geradora_ é outra maneira de criar um objeto gerador. - Objetos geradores fornecem `+__next__+`, então são iteradores. + Objetos geradores fornecem `+__next__+`, portanto são iteradores. Desde o Python 3.5, também temos _geradores assíncronos_, declarados com `async def`. Vamos estudá-los no <>. -O https://docs.python.org/pt-br/3/glossary.html[_Glossário de Python_] +O https://fpy.li/99[_Glossário de Python_] introduziu recentemente o termo -https://docs.python.org/pt-br/3/glossary.html#term-generator-iterator[iterador gerador] +https://fpy.li/9a[iterador gerador] para se referir a objetos geradores criados por funções geradoras, enquanto o verbete para -https://docs.python.org/pt-br/3/glossary.html#term-generator-expression[expressão geradora] diz que ela devolve um "iterador". +https://fpy.li/9b[expressão geradora] diz que ela devolve um "iterador". Mas, de acordo com o interpretador Python, @@ -810,14 +1064,23 @@ os objetos devolvidos em ambos os casos são objetos geradores: === Um gerador de progressão aritmética -O((("generators", "arithmetic progression generators", id="Garith17"))) padrão _Iterator_ clássico está todo baseado em uma travessia: navegar por alguma estrutura de dados. Mas uma interface padrão baseada em um método para obter o próximo item em uma série também é útil quando os itens são produzidos sob demanda, ao invés de serem obtidos de uma coleção. Por exemplo, a função embutida `range` gera uma progressão aritmética (PA) de inteiros delimitada. E se precisarmos gerar uma PA com números de qualquer tipo, não apenas inteiros? +O((("generators", "arithmetic progression generators", id="Garith17"))) +padrão _Iterator_ clássico trata de navegar por uma estrutura de dados. +Sua interface padrão é um método que fornece o próximo item de uma série. +Tal interface também é util quando os itens são produzidos sob demanda, +ao invés de serem lidos de uma coleção. +Por exemplo, a função embutida `range` gera uma progressão aritmética (PA) +de inteiros. +E se precisarmos gerar uma PA com números de qualquer tipo, não apenas inteiros? O <> mostra alguns testes no console com uma classe `ArithmeticProgression`, que vermos em breve. A assinatura do construtor no <> é `ArithmeticProgression(begin, step[, end])`. A assinatura completa da função embutida `range` é `range(start, stop[, step])`. -Escolhi implementar uma assinatura diferente porque o `step` é obrigatório, mas `end` é opcional em uma progressão aritmética. -Também mudei os nomes dos argumentos de `start/stop` para `begin/end`, para deixar claro que optei por uma assinatura diferente. -Para cada teste no <>, chamo `list()` com o resultado para inspecionar o valores gerados. +Escolhi implementar uma assinatura diferente porque o `step` é obrigatório, +mas o `end` é opcional. +Também mudei os nomes dos argumentos `start/stop` para `begin/end`, +para deixar claro que optei por uma assinatura diferente. +Para cada teste no <>, chamo `list()` com o resultado para exibir o valores gerados. [[ap_class_demo]] .Demonstração de uma classe `ArithmeticProgression` @@ -828,7 +1091,9 @@ include::../code/17-it-generator/aritprog_v1.py[tags=ARITPROG_CLASS_DEMO] ---- ==== -Observe que o tipo dos números na progressão aritmética resultante segue o tipo de `begin + step`, de acordo com as regras de coerção numérica da aritmética de Python. No <>, você pode ver listas de números `int`, `float`, `Fraction`, e `Decimal`. +Observe que o tipo dos números na progressão aritmética resultante +segue o tipo de `begin + step`, de acordo com as regras de coerção numérica da aritmética de Python. +No <>, você pode ver listas de números `int`, `float`, `Fraction`, e `Decimal`. O <> mostra a implementação da classe `ArithmeticProgression`. [[ex_ap_class]] @@ -839,16 +1104,23 @@ O <> mostra a implementação da classe `ArithmeticProgression`. include::../code/17-it-generator/aritprog_v1.py[tags=ARITPROG_CLASS] ---- ==== + <1> `+__init__+` exige dois argumentos: `begin` e `step`; `end` é opcional, se for `None`, a série será ilimitada. + <2> Obtém o tipo somando `self.begin` e `self.step`. Por exemplo, se um for `int` e o outro `float`, o `result_type` será `float`. + <3> Essa linha cria um `result` com o mesmo valor numérico de `self.begin`, mas coagido para o tipo das somas subsequentes.footnote:[No Python 2, havia uma função embutida `coerce()`, mas ela não existe mais no Python 3. Foi considerada desnecessária, pois as regras de coerção numérica estão implícitas nos métodos dos operadores aritméticos. Então, a melhor forma que pude imaginar para forçar o valor inicial para o mesmo tipo do restante da série foi realizar a adição e usar seu tipo para converter o resultado. Perguntei sobre isso na Python-list e recebi uma excelente https://fpy.li/17-11[resposta de Steven D'Aprano] (EN).] + <4> Para melhorar a legibilidade, o sinalizador `forever` será `True` se o atributo `self.end` for `None`, resultando em uma série ilimitada. -<5> Esse loop roda `forever` ou até o resultado ser igual ou maior que `self.end`. Quando esse loop termina, a função retorna. + +<5> Este laço roda `forever` (para sempre) ou até o resultado ser igual ou maior que `self.end`. Quando este laço termina, a função retorna. + <6> O `result` atual é produzido. -<7> O próximo resultado em potencial é calculado. Ele pode nunca ser produzido, se o loop `while` terminar. + +<7> O próximo resultado em potencial é calculado. Ele pode nunca ser produzido, se o laço `while` terminar. Na última linha do <>, -em vez de somar `self.step` ao `result` anterior a cada passagem do loop, +em vez de somar `self.step` ao `result` anterior a cada volta do laço, optei por ignorar o `result` existente: cada novo `result` é criado somando `self.begin` a `self.step` multiplicado por `index`. Isso evita o efeito cumulativo de erros após a adição sucessiva de números de ponto flutuante. Alguns experimentos simples tornam clara a diferença: @@ -939,7 +1211,7 @@ Entretanto, lembre-se que `itertools.count` soma o `step` repetidamente, então a série de números de ponto flutuante que ela produz não é tão precisa quanto a do <>. O importante no <> é: -ao implementar geradoras, olhe o que já está disponível na biblioteca padrão, caso contrário você tem uma boa chance de reinventar a roda. +ao implementar geradores, olhe o que já está disponível na biblioteca padrão, caso contrário você tem uma boa chance de reinventar a roda. Por isso a próxima seção trata de várias funções geradoras prontas para usar.((("", startref="Garith17"))) @@ -947,35 +1219,49 @@ Por isso a próxima seção trata de várias funções geradoras prontas para us === Funções geradoras na biblioteca padrão A((("generators", "generator functions in Python standard library", -id="Ggenfunc17"))) biblioteca padrão oferece muitas geradoras, desde objetos de +id="Ggenfunc17"))) biblioteca padrão oferece muitos geradores, desde objetos de arquivo de texto fornecendo iteração linha por linha até a incrível função https://fpy.li/17-12[os.walk], que produz nomes de arquivos percorrendo uma árvore de diretórios, reduzindo uma busca recursiva no sistema de arquivos a um simples laço `for`. -A função geradora `os.walk` é impressionante, mas nesta seção quero me concentrar em funções genéricas que recebem iteráveis arbitrários como argumento e devolvem geradores que produzem itens selecionados, calculados ou reordenados. Nas tabelas a seguir, resumi duas dúzias delas, algumas embutidas, outras dos módulos `itertools` e `functools`. Por conveniência, elas estão agrupadas por sua funcionalidade de alto nível, independente de onde são definidas. +A função geradora `os.walk` é impressionante, mas nesta seção quero me +concentrar em funções genéricas que recebem iteráveis arbitrários como argumento +e devolvem geradores que produzem itens selecionados, agregados ou reordenados. -O primeiro grupo contém funções geradoras de filtragem: elas produzem um subconjunto dos itens produzidos pelo iterável de entrada, sem mudar os itens em si. Como `takewhile`, a maioria das funções listadas na <> recebe um `predicate`, uma função booleana de um argumento que será aplicada a cada item no iterável de entrada, para determinar se aquele item será incluído na saída.((("filtering generator functions"))) +((("filtering generator functions")))O primeiro grupo são funções geradoras de +filtragem: elas devolvem um gerador que produz um subconjunto dos itens do +iterável de entrada, sem mudar os itens em si. -[[filter_genfunc_tbl]] -.Funções geradoras de filtragem -[options="header"] -|============================================================================================================================================================================================================================================================= -|Módulo|Função|Descrição -|[.keep-together]#`itertools`#|[.keep-together]#`compress(it, selector_it)`#|Consome dois iteráveis em paralelo; produz itens de `it` sempre que o item correspondente em `selector_it` é verdadeiro -|`itertools`|`dropwhile(predicate, it)`|Consome `it`, pulando itens enquanto `predicate` resultar verdadeiro, e daí produz todos os elementos restantes (nenhuma verificação adicional é realizada) -|(Embutida)|`filter(predicate, it)`|Aplica `predicate` para cada item de `iterable`, produzindo o item se `predicate(item)` for verdadeiro; se `predicate` for `None`, apenas itens verdadeiros serão produzidos -|`itertools`|`filterfalse(predicate, it)`|Igual a `filter`, mas negando a lógica de `predicate`: produz itens sempre que `predicate` resultar falso -|`itertools`|`islice(it, stop) ou islice(it, start, stop, step=1)`|Produz itens de uma fatia de `it`, similar a `s[:stop]` ou `s[start:stop:step]`, exceto por `it` poder ser qualquer iterável e a operação ser preguiçosa -|`itertools`|`takewhile(predicate, it)`|Produz itens enquanto `predicate` resultar verdadeiro, e daí para (nenhuma verificação adicional é realizada). -|============================================================================================================================================================================================================================================================= +O exemplo mais conhecido é a função embutida `filter`: -//// -PROD: In the table above, the word `itertools` appears split in two lines as `iter` and `tools`. Can we make the 1st column wider to prevent that? Thanks! -//// +`filter(predicate, it)`:: + Aplica `predicate` para cada item de `iterable`, produzindo o item se + `predicate(item)` for verdadeiro. Quando `predicate` é `None`, apenas itens verdadeiros serão produzidos. + +Como `filter`, a maioria das funções listadas na <> +recebe uma `predicate` (uma função booleana de um argumento) que será aplicada a +cada item no iterável de entrada, para determinar se aquele item será incluído +na saída. A exceção é `itertools.compress`, que consome dois iteráveis, sendo +que o segundo iterável fornece valores para decidir quais itens do primeiro iterável serão produzidos. +Veja o uso de `compress` no <>. +<<< -A seção de console no <> demonstra o uso de todas as funções na <>. +[[filter_genfunc_tbl]] +.`itertools`: funções geradoras de filtragem +[options="header", cols="5,9"] +|======================= +|Função|Descrição +|`compress(it, selector_it)`|Consome dois iteráveis em paralelo; produz itens de `it` sempre que o item correspondente em `selector_it` é verdadeiro +|`dropwhile(predicate, it)`|Consome `it`, pulando itens enquanto `predicate` resultar verdadeiro, e daí produz todos os elementos restantes (nenhuma verificação adicional é realizada) +|`filterfalse(predicate, it)`|Como a `filter`, mas invertendo a lógica: produz itens quando `predicate` resulta falso +|`islice(it, stop)` + + `islice(it, start, stop, step=1)`|Produz itens de uma fatia de `it`, similar a `s[:stop]` ou `s[start:stop:step]`, mas `it` é qualquer iterável e a operação é preguiçosa +|`takewhile(predicate, it)`|Produz itens enquanto `predicate` resultar verdadeiro, e daí para (nenhuma verificação adicional é realizada). +|======================= + +A seção de console no <> demonstra o uso das funções de filtragem. [[demo_filter_genfunc]] .Exemplos de funções geradoras de filtragem @@ -1005,22 +1291,33 @@ A seção de console no <> demonstra o uso de todas as fun ---- ==== -O((("mappings", "mapping generator functions"))) grupo seguinte contém os geradores de mapeamento: eles produzem itens computados a partir de cada item individual no iterável de entrada--ou iteráveis, nos casos de `map` e `starmap`.footnote:[O termo "mapeamento" aqui não está relacionado a dicionários, mas com a função embutida `map`.] As geradoras na <> produzem um resultado por item dos iteráveis de entrada. Se a entrada vier de mais de um iterável, a saída para assim que o primeiro iterável de entrada for exaurido. +O((("mappings", "mapping generator functions"))) grupo seguinte contém os +geradores de mapeamento. O termo "mapeamento" aqui não está relacionado a +dicionários, mas com a função embutida `map`. Este grupo de funções produzem +itens computados a partir de cada item individual no iterável de entrada—ou +iteráveis, nos casos de `map` e `starmap`. +Se a entrada vier de mais de um iterável, a saída para assim que o primeiro +iterável de entrada for exaurido. [[mapping_genfunc_tbl]] -.Funções geradoras de mapeamento -[options="header"] -|============================================================================================================================================================================================================================================================= -|Módulo|Função|Descrição -|[.keep-together]#`itertools`#|`accumulate(it, [func])`|Produz somas cumulativas; se `func` for fornecida, produz o resultado da aplicação de `func` ao primeiro par de itens, depois ao primeiro resultado e ao próximo item, etc. -|(embutida)|`enumerate(iterable, start=0)`|Produz tuplas de dois itens na forma `(index, item)`, onde `index` é contado a partir de `start`, e `item` é obtido do `iterable` -|(embutida)|`map(func, it1, [it2, …, itN])`|Aplica `func` a cada item de `it`, produzindo o resultado; se forem fornecidos N iteráveis, `func` deve aceitar N argumentos, e os iteráveis serão consumidos em paralelo -|`itertools`|`starmap(func, it)`|Aplica `func` a cada item de `it`, produzindo o resultado; o iterável de entrada deve produzir itens iteráveis `iit`, e `func` é aplicada na forma `func(*iit)` -|============================================================================================================================================================================================================================================================= - -//// -PROD: Again, the word `itertools` appears split in two lines as `iter` and `tools`. Can we make the 1st column wider to prevent that? Thanks! -//// +.Funções geradoras de mapeamento (embutidas) +[options="header", cols="7,10"] +|============================ +|Função|Descrição +|`enumerate(iterable, start=0)`|Produz tuplas de dois itens na forma `(index, item)`, onde `index` é contado a partir de `start`, e `item` é obtido do `iterable` +|`map(func, it1, [it2, …, itN])`|Aplica `func` a cada item de `it`, produzindo o resultado; se forem fornecidos N iteráveis, `func` deve aceitar N argumentos, e os iteráveis serão consumidos em paralelo +|============================ + +O módulo `itertools` oferece mais duas funções geradoras de mapeamento: + +[[mapping_genfunc_itertools_tbl]] +.`itertools`: funções geradoras de mapeamento +[options="header", cols="4,9"] +|========================== +|Função|Descrição +|`starmap(func, it)`|Aplica `func` a cada item de `it`, produzindo o resultado; o iterável de entrada deve produzir itens iteráveis `iit`, e `func` é aplicada na forma `func(*iit)` +|`accumulate(it, [func])`|Produz somas cumulativas; se `func` for fornecida, produz o resultado da aplicação de `func` ao primeiro par de itens, depois ao primeiro resultado e ao próximo item, etc. +|========================== O <> demonstra alguns usos de `itertools.accumulate`. @@ -1030,8 +1327,8 @@ O <> demonstra alguns usos de `itertools.accumulate`. ==== [source, python] ---- ->>> sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1] >>> import itertools +>>> sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1] >>> list(itertools.accumulate(sample)) # <1> [5, 9, 11, 19, 26, 32, 35, 35, 44, 45] >>> list(itertools.accumulate(sample, min)) # <2> @@ -1051,7 +1348,7 @@ O <> demonstra alguns usos de `itertools.accumulate`. <4> Produto acumulado. <5> Fatoriais de `1!` a `10!`. -As funções restantes da <> são demonstradas no <>. +As demais funçnoes de mapeamento são demonstradas no <>. [[demo_mapping_genfunc]] .Exemplos de funções geradoras de mapeamento @@ -1084,21 +1381,34 @@ As funções restantes da <> são demonstradas no < Repete cada letra na palavra de acordo com a posição da letra na palavra, começando por `1`. <6> Média corrente. -A seguir temos o grupo de geradores de fusão—todos eles produzem itens a partir de múltiplos iteráveis de entrada. `chain` e `chain.from_iterable` consomem os iteráveis de entrada em sequência (um após o outro), enquanto `product`, `zip`, e `zip_longest` consomem os iteráveis de entrada em paralelo. Veja a <>. +A seguir temos o grupo de geradores de mesclagem (_merge_). +Elas mesclam os itens de vários iteráveis de entrada. -[[merging_genfunc_tbl]] -.Funções geradoras que fundem os iteráveis de entrada -[options="header"] -|============================================================================================================================================================================================================================================================= -|Módulo|Função|Descrição -|`itertools`|`chain(it1, …, itN)`|Produz todos os itens de `it1`, a seguir de `it2`, etc., continuamente. -|`itertools`|`chain.from_iterable(it)`|Produz todos os itens de cada iterável produzido por `it`, um após o outro, continuamente; `it` é um iterável cujos itens também são iteráveis, uma lista de tuplas, por exemplo -|`itertools`|`product(it1, …, itN, repeat=1)`|Produto cartesiano: produz tuplas de N elementos criadas combinando itens de cada iterável de entrada, como loops `for` aninhados produziriam; `repeat` permite que os iteráveis de entrada sejam consumidos mais de uma vez -|(embutida)|`zip(it1, …, itN, strict=False)`|Produz tuplas de N elementos criadas a partir de itens obtidos dos iteráveis em paralelo, terminando silenciosamente quando o menor iterável é exaurido, a menos que `strict=True` for passadofootnote:[O argumento apenas nomeado `strict` é novo, surgiu no Python 3.10. Quando `strict=True`, um `ValueError` é gerado se qualquer iterável tiver um tamanho diferente. O default é `False`, para manter a compatibilidade retroativa.] -|`itertools`|`zip_longest(it1, …, itN, fillvalue=None)`|Produz tuplas de N elementos criadas a partir de itens obtidos dos iteráveis em paralelo, terminando apenas quando o último iterável for exaurido, preenchendo os itens ausentes com o `fillvalue` -|============================================================================================================================================================================================================================================================= +O exemplo mais conhecido é a função embutida `zip`: + +`zip(it1, …, itN, strict=False)`:: + Produz tuplas de N elementos criadas a partir de itens obtidos dos iteráveis em paralelo, terminando silenciosamente quando o menor iterável é exaurido, a menos que `strict=True` for passado (a partir do Python 3.10). Quando `strict=True`, um `ValueError` é gerado se algum iterável esgotar antes dos outros. O default é `False`, para manter a compatibilidade retroativa. + +Na <>, vale notar que `chain` e `chain.from_iterable` +consomem os iteráveis de entrada um após o outro, enquanto `product`, e +`zip_longest` consomem os iteráveis de entrada em paralelo, como faz a função +`zip`. -O <> demonstra o uso das funções geradoras `itertools.chain` e `zip`, e de suas pares. Lembre-se que o nome da função `zip` vem do zíper ou fecho-éclair (nenhuma relação com a compreensão de dados). Tanto `zip` quanto `itertools.zip_longest` foram apresentadas no <>. +[[merging_genfunc_tbl]] +.`itertools`: funções geradoras que mesclam os iteráveis de entrada +[options="header", cols="6,12"] +|=================== +|Função|Descrição +|`chain(it1, …, itN)`|Produz todos os itens de `it1`, a seguir de `it2`, etc., continuamente. +|`chain.from_iterable(it)`|Produz todos os itens de cada iterável produzido por `it`, um após o outro, continuamente; `it` é um iterável cujos itens também são iteráveis, uma lista de tuplas, por exemplo +|`product(it1, …, itN, repeat=1)`|Produto cartesiano: produz tuplas de N elementos criadas combinando itens de cada iterável de entrada, como laços `for` aninhados produziriam; `repeat` permite que os iteráveis de entrada sejam consumidos mais de uma vez +|`zip_longest(it1, …, itN, fillvalue=None)`|Produz tuplas de N elementos criadas a partir de itens obtidos dos iteráveis em paralelo, terminando apenas quando o último iterável for exaurido, preenchendo os itens ausentes com o `fillvalue` +|=================== + +O <> demonstra o uso das funções geradoras +`itertools.chain` e `zip`, e de suas pares. Lembre-se que o nome da função `zip` +vem do zíper ou fecho-éclair (nenhuma relação com a compreensão de dados). Tanto +`zip` quanto `itertools.zip_longest` foram apresentadas no <>. [[demo_merging_genfunc]] @@ -1124,11 +1434,11 @@ O <> demonstra o uso das funções geradoras `itertools.ch <1> `chain` é normalmente invocada com dois ou mais iteráveis. <2> `chain` não faz nada de útil se invocada com um único iterável. <3> Mas `chain.from_iterable` pega cada item do iterável e os encadeia em sequência, desde que cada item seja também iterável. -<4> Qualquer número de iteráveis pode ser consumido em paralelo por `zip`, mas a geradora sempre para assim que o primeiro iterável acaba. No Python ≥ 3.10, se o argumento `strict=True` for passado e um iterável terminar antes dos outros, um `ValueError` é gerado. +<4> Qualquer número de iteráveis pode ser consumido em paralelo por `zip`, mas o gerador sempre para assim que o primeiro iterável acaba. No Python ≥ 3.10, se o argumento `strict=True` for passado e um iterável terminar antes dos outros, um `ValueError` é gerado. <5> `itertools.zip_longest` funciona como `zip`, exceto por consumir todos os iteráveis de entrada, preenchendo as tuplas de saída com `None` onde necessário. <6> O argumento nomeado `fillvalue` especifica um valor de preenchimento customizado. -A geradora `itertools.product` é uma forma preguiçosa para calcular produtos cartesianos, que criamos usando compreensões de lista com mais de uma instrução `for` na <>. Expressões geradoras com múltiplas instruções `for` também podem ser usadas para produzir produtos cartesianos de forma preguiçosa. O <> demonstra [.keep-together]#`itertools.product`.# +O gerador `itertools.product` é uma forma preguiçosa para calcular produtos cartesianos, que criamos usando compreensões de lista com mais de uma instrução `for` na <>. Expressões geradoras com múltiplas instruções `for` também podem ser usadas para produzir produtos cartesianos de forma preguiçosa. O <> demonstra `itertools.product`. [[demo_product_genfunc]] .Exemplo da função geradora `itertools.product` @@ -1178,22 +1488,24 @@ A geradora `itertools.product` é uma forma preguiçosa para calcular produtos c Algumas((("input expanding generator functions"))) funções geradoras expandem a entrada, produzindo mais de um valor por item de entrada. Elas estão listadas na <>. [[expanding_genfunc_tbl]] -.Funções geradoras que expandem cada item de entrada em múltiplos itens de saída -[options="header"] -|============================================================================================================================================================================================================================================================= -|Module|Function|Description -|`itertools`|`combinations(it, out_len)`|Produz combinações de `out_len` itens a partir dos itens produzidos por `it` -|`itertools`|`combinations_with_replacement(it, out_len)`|Produz combinações de `out_len` itens a partir dos itens produzidos por `it`, incluindo combinações com itens repetidos -|`itertools`|`count(start=0, step=1)`|Produz números começando em `start` e adicionando `step` para obter o número seguinte, indefinidamente -|`itertools`|`cycle(it)`|Produz itens de `it`, armazenando uma cópia de cada, e então produz a sequência inteira repetida e indefinidamente -|`itertools`|`pairwise(it)`|Produz pares sobrepostos sucessivos, obtidos do iterável de entradafootnote:[`itertools.pairwise` foi introduzido no Python 3.10.] -|`itertools`|`permutations(it, out_len=None)`|Produz permutações de `out_len` itens a partir dos itens produzidos por `it`; por default, `out_len` é `len(list(it))` -|`itertools`|`repeat(item, [times])`|Produz um dado item repetidamente e, a menos que um número de `times` (_vezes_) seja passado, indefinidamente -|============================================================================================================================================================================================================================================================= +.`itertools`: funções geradoras que expandem cada item de entrada em múltiplos itens de saída +[options="header", cols="6, 9"] +|==================== +|Função|Descrição +|`combinations(it, out_len)`|Produz combinações de `out_len` itens a partir dos itens produzidos por `it` +|`combinations_with_replacement( + +it, out_len)`|Produz combinações de `out_len` itens a partir dos itens produzidos por `it`, incluindo combinações com itens repetidos +|`count(start=0, step=1)`|Produz números começando em `start` e adicionando `step` para obter o número seguinte, indefinidamente +|`cycle(it)`|Produz itens de `it`, armazenando uma cópia de cada, e então produz a sequência inteira repetida e indefinidamente +|`pairwise(it)`|Produz pares sobrepostos sucessivos, obtidos do iterável de entradafootnote:[`itertools.pairwise` foi introduzido no Python 3.10.] +|`permutations(it, out_len=None)`|Produz permutações de `out_len` itens a partir dos itens produzidos por `it`; por default, `out_len` é `len(list(it))` +|`repeat(item, [times])`|Produz um dado item repetidamente e, a menos que um número de `times` (_vezes_) seja passado, indefinidamente +|==================== As funções `count` e `repeat` de `itertools` devolvem geradores que conjuram itens do nada: -nenhum deles recebe um iterável como parâmetro. -Vimos `itertools.count` na <>. +elas não consomem itens de um itereavel. +Já vimos `itertools.count` na <>. + O gerador `cycle` faz uma cópia do iterável de entrada e produz seus itens repetidamente. O <> ilustra o uso de `count`, `cycle`, `pairwise` e `repeat`. @@ -1225,18 +1537,18 @@ O <> ilustra o uso de `count`, `cycle`, `pairwise` e [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50] ---- ==== -<1> Cria `ct`, uma geradora `count`. +<1> Cria `ct`, um gerador `count`. <2> Obtém o primeiro item de `ct`. <3> Não posso criar uma `list` a partir de `ct`, pois `ct` nunca para. Então pego os próximos três itens. -<4> Posso criar uma `list` de uma geradora `count` se ela for limitada por `islice` ou `takewhile`. -<5> Cria uma geradora `cycle` a partir de `'ABC'`, e obtém seu primeiro item, `'A'`. +<4> Posso criar uma `list` de um gerador `count` se ele for limitado por `islice` ou `takewhile`. +<5> Cria um gerador `cycle` a partir de `'ABC'`, e obtém seu primeiro item, `'A'`. <6> Uma `list` só pode ser criada se limitada por `islice`; os próximos sete itens são obtidos aqui. <7> Para cada item na entrada, `pairwise` produz uma tupla de dois elementos com aquele item e o próximo—se existir um próximo item. Disponível no Python ≥ 3.10. -<8> Cria uma geradora `repeat` que vai produzir o número `7` para sempre. -<9> Uma geradora `repeat` pode ser limitada passando o argumento `times`: aqui o número `8` será produzido `4` vezes. -<10> Um uso comum de `repeat`: fornecer um argumento fixo em `map`; aqui ela fornece o multiplicador `5`. +<8> Cria um gerador `repeat` que vai produzir o número `7` para sempre. +<9> Um gerador `repeat` pode ser limitado passando o argumento `times`: aqui o número `8` será produzido `4` vezes. +<10> Um uso comum de `repeat`: fornecer um argumento fixo em `map`; aqui ele fornece o multiplicador `5`. -A funções geradoras `combinations`, `combinations_with_replacement` e `permutations`--juntamente com `product`—são chamadas _geradoras combinatórias_ na https://docs.python.org/pt-br/3/library/itertools.html[página de documentação do `itertools`]. +A funções geradoras `combinations`, `combinations_with_replacement` e `permutations`--juntamente com `product`—são chamadas _geradoras combinatórias_ na https://fpy.li/9c[página de documentação do `itertools`]. Também há um relação muito próxima entre `itertools.product` e o restante das funções _combinatórias_, como mostra o <>. [[demo_conbinatoric_genfunc]] @@ -1260,17 +1572,30 @@ Também há um relação muito próxima entre `itertools.product` e o restante d <3> Todas as permutações com `len()==2` a partir dos itens em `'ABC'`; a ordem dos itens nas tuplas geradas é relevante. <4> Produto cartesiano de `'ABC'` e `'ABC'` (esse é o efeito de `repeat=2`). -O último grupo de funções geradoras que vamos examinar nessa seção foram projetados para produzir todos os itens dos iteráveis de entrada, mas rearranjados de alguma forma. Aqui estão duas funções que devolvem múltiplos geradores: `itertools.groupby` e `itertools.tee`. A outra geradora nesse grupo, a função embutida `reversed`, é a única geradora tratada nesse capítulo que não aceita qualquer iterável como entrada, apenas sequências. Faz sentido: como `reversed` vai produzir os itens do último para o primeiro, só funciona com uma sequência de tamanho conhecido. Mas ela evita o custo de criar uma cópia invertida da sequência produzindo cada item quando necessário. Coloquei a função `itertools.product` junto com as geradoras de _fusão_, na <>, porque todas aquelas consomem mais de um iterável, enquanto todas as geradoras na <> aceitam no máximo um iterável como entrada. +O último grupo de funções geradoras que vamos examinar nessa seção foram projetados para produzir todos os itens dos iteráveis de entrada, mas rearranjados de alguma forma. Aqui estão duas funções que devolvem múltiplos geradores: `itertools.groupby` e `itertools.tee`. + +A função embutida `reversed` é o unico gerador tratado neste capítulo que não +aceita qualquer iterável como entrada, apenas sequências. Faz sentido: como +`reversed` vai produzir os itens do último para o primeiro, só funciona com uma +sequência, porque seu tamanho é conhecido então é possível acessar o último item +diretamente. +Ao produzir cada item sob demanda, `reversed` evita o custo de criar uma cópia +invertida da sequência. +Coloquei a função +`itertools.product` junto com os geradores de mesclagem, na +<>, porque todas aquelas consomem mais de um iterável, +enquanto os geradores na <> aceitam no máximo um +iterável como entrada. [[expanding_genfunc_tbl2]] .Funções geradoras de rearranjo -[options="header"] -|============================================================================================================================================================================================================================================================= +[options="header", cols="2,4,7"] +|===================== |Módulo|Função|Descrição |`itertools`|`groupby(it, key=None)`|Produz tuplas de 2 elementos na forma `(key, group)`, onde `key` é o critério de agrupamento e `group` é um gerador que produz os itens no grupo |(embutida)|`reversed(seq)`|Produz os itens de `seq` na ordem inversa, do último para o primeiro; `seq` deve ser uma sequência ou implementar o método especial `+__reversed__+` |`itertools`|`tee(it, n=2)`|Produz uma tupla de _n_ geradores, cada um produzindo os itens do iterável de entrada de forma independente -|============================================================================================================================================================================================================================================================= +|===================== //// PROD: Again, the word `itertools` appears split in two lines as `iter` and `tools`. Can we make the 1st column wider to prevent that? Thanks! @@ -1319,10 +1644,10 @@ G -> ['G', 'G', 'G'] ---- ==== <1> `groupby` produz tuplas de `(key, group_generator)`. -<2> Tratar geradoras `groupby` envolve iteração aninhada: neste caso, o loop `for` externo e o construtor de `list` interno. +<2> Tratar geradores `groupby` envolve iteração aninhada: neste caso, o laço `for` externo e o construtor de `list` interno. <3> Ordena `animals` por tamanho. -<4> Novamente, um loop sobre o par `key` e `group`, para exibir `key` e expandir o `group` em uma `list`. -<5> Aqui a geradora `reverse` itera sobre `animals` da direita para a esquerda. +<4> Novamente, um laço sobre o par `key` e `group`, para exibir `key` e expandir o `group` em uma `list`. +<5> Aqui o gerador `reverse` itera sobre `animals` da direita para a esquerda. A última das funções geradoras nesse grupo é `iterator.tee`, que apresenta um comportamento singular: ela produz múltiplos geradores a partir de um único iterável de entrada, cada um deles produzindo todos os itens daquele iterável. Esse geradores podem ser consumidos de forma independente, como mostra o <>. @@ -1358,26 +1683,36 @@ Vamos agora revisar outro grupo de funções da biblioteca padrão que lidam com Todas((("generators", "iterable reducing functions", id="Greduc17")))((("iterables", "iterable reducing functions", id="Ireduc17")))((("reducing functions", id="redfunc17"))) as funções na <> recebem um iterável e devolvem um resultado único. Elas são conhecidas como funções de "redução", "dobra" (_folding_) ou "acumulação". -Podemos implementar cada uma das funções embutidas listadas a seguir com `functools.reduce`, mas elas existem embutidas por resolverem algums casos de uso comuns de forma mais fácil. -Já vimos uma explicação mais aprofundada sobre `functools.reduce` na <>. -Nos casos de `all` e `any`, há uma importante otimização não suportada por `functools.reduce`: `all` e `any` conseguem criar um curto-circuito—isto é, elas param de consumir o iterador assim que o resultado esteja determinado. +A função de redução mais flexível é `functools.reduce`: + +`functools.reduce(func, it, [initial])`:: + Devolve o resultado da aplicação de `func` ao primeiro par de itens, + depois aplica `func` ao resultado anterior e o terceiro item, e assim por diante. + Se `initial` for passado, esse argumento formará o par inicial com o primeiro item do iterável `it`. + +Podemos implementar cada uma das funções embutidas listadas a seguir com `functools.reduce`, +mas elas estão embutidas no Python por resolverem mais facilmente algums casos de uso comuns de `functools.reduce`. +Vimos uma explicação mais aprofundada sobre `functools.reduce` na <>. + +Nos casos de `all` e `any`, há uma importante otimização não suportada por `functools.reduce`: +`all` e `any` conseguem criar um curto-circuito—isto é, +elas param de consumir o iterador assim que o resultado é determinado. Veja o último teste com `any` no <>. [[tbl_iter_reducing]] .Funções embutidas que leem iteráveis e devolvem um único valor -[options="header"] -|============================================================================================================================================================================================================================================================= -|Módulo|Função|Descrição -|(embutida)|`all(it)`|Devolve `True` se todos os itens em `it` forem verdadeiros, `False` em caso contrário; `all([])` devolve `True` -|(embutida)|`any(it)`|Devolve `True` se qualquer item em `it` for verdadeiro, `False` em caso contrário; `any([])` devolve `False` -|(embutida)|`max(it, [key=,] [default=])`|Devolve o valor máximo entre os itens de `it`;footnote:[Pode também ser invocado na forma +[options="header", cols="2,5"] +|=============================================== +|Função|Descrição +|`all(it)`|Devolve `True` se todos os itens em `it` forem verdadeiros, `False` em caso contrário; `all([])` devolve `True` +|`any(it)`|Devolve `True` se qualquer item em `it` for verdadeiro, `False` em caso contrário; `any([])` devolve `False` +|`max(it, [key=,] [default=])`|Devolve o valor máximo entre os itens de `it`;footnote:[Pode também ser invocado na forma `+max(arg1, arg2, …, [key=?])+`, devolvendo então o valor máximo entre os argumentos passados.] `key` é uma função de ordenação, como em `sorted`; `default` é devolvido se o iterável estiver vazio -|(embutida)|`min(it, [key=,] [default=])`|Devolve o valor mínimo entre os itens de `it`.footnote:[Pode também ser invocado na forma +|`min(it, [key=,] [default=])`|Devolve o valor mínimo entre os itens de `it`.footnote:[Pode também ser invocado na forma `+min(arg1, arg2, …, [key=?])+`, devolvendo então o valor mínimo entre os argumentos passados.] `key` é uma função de ordenação, como em `sorted`; `default` é devolvido se o iterável estiver vazio -|`functools`|`reduce(func, it, [initial])`|Devolve o resultado da aplicação de `func` consecutivamente ao primeiro par de itens, depois deste último resultado e o terceiro item, e assim por diante; se `initial` for passado, esse argumento formará o par inicial com o primeiro item -|(embutida)|`sum(it, start=0)`|A soma de todos os itens em `it`, acrescida do valor opcional `start` (para uma precisão melhor na adição de números de ponto flutuante, use `math.fsum`) -|============================================================================================================================================================================================================================================================= +|`sum(it, start=0)`|A soma de todos os itens em `it`, acrescida do valor opcional `start` (para uma precisão melhor na adição de números de ponto flutuante, use `math.fsum`) +|=============================================== O <> exemplifica a operação de `all` e de `any`. @@ -1434,7 +1769,7 @@ A sintaxe `yield from` fornece uma nova forma de combinar geradores. É nosso pr A sintaxe da expressão `yield from`((("generators", "subgenerators with yield from expression", id="Gsubyield17")))((("yield from expression", id="yieldfrom17"))) foi introduzida no Python 3.3, para permitir que um gerador delegue tarefas a um subgerador. -Antes da introdução de `yield from`, usávamos um loop `for` quando um gerador precisava produzir valores de outro gerador: +Antes da introdução de `yield from`, usávamos um laço `for` quando um gerador precisava produzir valores de outro gerador: [source, python] ---- @@ -1483,10 +1818,10 @@ Podemos obter o mesmo resultado usando `yield from`, como se vê no <>, o((("delegating generators")))((("subgenerators")))((("client codes"))) loop `for` é o _código cliente_, +No <>, o((("delegating generators")))((("subgenerators")))((("client codes"))) laço `for` é o _código cliente_, `gen` é o _gerador delegante_ e `sub_gen` é o _subgerador_. Observe que `yield from` suspende `gen`, e `sub_gen` toma o controle até se exaurir. -Os valores produzidos por `sub_gen` passam através de `gen` diretamente para o loop `for` cliente. +Os valores produzidos por `sub_gen` passam através de `gen` diretamente para o laço `for` cliente. Enquanto isso, `gen` está suspenso e não pode ver os valores que passam por ele. `gen` continua apenas quando `sub_gen` termina. @@ -1525,9 +1860,9 @@ Agora que já vimos o básico sobre `yield from`, vamos estudar alguns exemplos [[reinventing_chain_sec]] ==== Reinventando chain -Vimos((("chain generator"))) na <> que `itertools` fornece uma geradora `chain`, que produz itens a partir de vários iteráveis, +Vimos((("chain generator"))) na <> que `itertools` fornece um gerador `chain`, que produz itens a partir de vários iteráveis, iterando sobre o primeiro, depois sobre o segundo, e assim por diante, até o último. -Abaixo está uma implementação caseira de `chain`, com loops `for` aninhados, em Python:footnote:[`chain` e a maioria das funções de `itertools` são escritas em C.] +Abaixo está uma implementação caseira de `chain`, com laços `for` aninhados, em Python:footnote:[`chain` e a maioria das funções de `itertools` são escritas em C.] [source, python] ---- @@ -1542,8 +1877,8 @@ Abaixo está uma implementação caseira de `chain`, com loops `for` aninhados, ['A', 'B', 'C', 0, 1, 2] ---- -A geradora `chain`, no código acima, está delegando para cada iterável `it`, controlando cada `it` no loop `for` interno. -Aquele loop interno pode ser substituído por uma expressão `yield from`, como mostra a seção de console a seguir: +O gerador `chain`, no código acima, está delegando para cada iterável `it`, controlando cada `it` no laço `for` interno. +Aquele laço interno pode ser substituído por uma expressão `yield from`, como mostra a seção de console a seguir: [source, python] ---- @@ -1565,12 +1900,12 @@ Vamos então desenvolver um exemplo mais interessante. Nessa((("tree structures, traversing", id="treetravers17"))) seção, veremos `yield from` em um script para percorrer uma estrutura de árvore. Vou desenvolvê-lo bem devagar. -A estrutura de árvore nesse exemplo é a https://docs.python.org/pt-br/3/library/exceptions.html#exception-hierarchy[hierarquia das exceções] de Python. +A estrutura de árvore nesse exemplo é a https://fpy.li/9d[hierarquia das exceções] de Python. Mas o padrão pode ser adaptado para exibir uma árvore de diretórios ou qualquer outra estrutura de árvore. Começando de `BaseException` no nível zero, a hierarquia de exceções tem cinco níveis de profundidade no Python 3.10. Nosso primeiro pequeno passo será exibir o nível zero. -Dada uma classe raiz, a geradora `tree` no <> produz o nome dessa classe e para. +Dada uma classe raiz, o gerador `tree` no <> produz o nome dessa classe e para. [[ex_tree_step0]] .tree/step0/tree.py: produz o nome da classe raiz e para @@ -1589,7 +1924,7 @@ BaseException ---- O próximo pequeno passo nos leva ao nível 1. -A geradora `tree` irá produzir o nome da classe raiz e os nomes de cada subclasse direta. +O gerador `tree` produzirá o nome da classe raiz e os nomes de cada subclasse direta. Os nomes das subclasses são indentados para explicitar a hierarquia. Esta é a saída que queremos: @@ -1618,8 +1953,8 @@ include::../code/17-it-generator/tree/step1/tree.py[] <3> Produz o nome da subclasse e o nível (`1`). <4> Cria a string de indentação de `4` espaços vezes o `level`. No nível zero, isso será uma string vazia. -No <>, refatorei `tree` para separar o caso especial da classes raiz de suas subclasses, que agora são processadas na geradora `sub_tree`. -Em `yield from`, a geradora `tree` é suspensa, e `sub_tree` passa a produzir valores. +No <>, refatorei `tree` para separar o caso especial da classes raiz de suas subclasses, que agora são processadas no gerador `sub_tree`. +Em `yield from`, o gerador `tree` é suspenso, e `sub_tree` passa a produzir valores. [[ex_tree_step2]] .tree/step2/tree.py: `tree` produz o nome da classe raiz, e entao delega para `sub_tree` @@ -1630,12 +1965,12 @@ include::../code/17-it-generator/tree/step2/tree.py[] ---- ==== <1> Delega para `sub_tree`, para produzir os nomes das subclasses. -<2> Produz o nome de cada subclasse e o nível (`1`). Por causa do `yield from sub_tree(cls)` dentro de `tree`, esses valores escapam completamente à geradora `tree` ... +<2> Produz o nome de cada subclasse e o nível (`1`). Por causa do `yield from sub_tree(cls)` dentro de `tree`, esses valores escapam completamente ao gerador `tree` ... <3> ... e são recebidos aqui diretamente. Seguindo com nosso método de pequenos passos, vou escrever o código mais simples que consigo imaginar para chegar ao nível 2. -Para percorrer uma árvore https://pt.wikipedia.org/wiki/Busca_em_profundidade[primeiro em produndidade (_depth-first_)], após produzir cada nó do nível 1, quero produzir os filhotes daquele nó no nível 2 antes de voltar ao nível 1. -Um loop `for` aninhado cuida disso, como no <>. +Para percorrer uma árvore https://fpy.li/9e[primeiro em produndidade (_depth-first_)], após produzir cada nó do nível 1, quero produzir os filhotes daquele nó no nível 2 antes de voltar ao nível 1. +Um laço `for` aninhado cuida disso, como no <>. [[ex_tree_step3]] .tree/step3/tree.py: `sub_tree` percorre os níveis 1 e 2, primeiro em profundidade @@ -1678,11 +2013,11 @@ BaseException ---- Você pode já ter percebido para onde isso segue, mas vou insistir mais uma vez nos pequenos passos: -vamos atingir o nível 3, acrescentando ainda outro loop `for` aninhado. -Não há qualquer alteração no restante do programa, então o <> mostra apenas a geradora `sub_tree`. +vamos atingir o nível 3, acrescentando ainda outro laço `for` aninhado. +Não há qualquer alteração no restante do programa, então o <> mostra apenas o gerador `sub_tree`. [[ex_tree_step4]] -.A geradora `sub_tree` de _tree/step4/tree.py_ +.O gerador `sub_tree` de _tree/step4/tree.py_ ==== [source, python] ---- @@ -1691,10 +2026,10 @@ include::../code/17-it-generator/tree/step4/tree.py[tags=SUB_TREE] ==== Há um padrão claro no <>. -Entramos em um loop `for` para obter as subclasses do nível _N_. -A cada passagem do loop, produzimos uma subclasse do nível _N_, e então iniciamos outro loop `for` para visitar o nível __N__+1. +Entramos em um laço `for` para obter as subclasses do nível _N_. +A cada volta do laço, produzimos uma subclasse do nível _N_, e então iniciamos outro laço `for` para visitar o nível __N__+1. -Na <>, vimos como é possível substituir um loop `for` aninhado controlando uma geradora com `yield from` sobre a mesma geradora. +Na <>, vimos como é possível substituir um laço `for` aninhado controlando um gerador com `yield from` sobre o mesmo gerador. Podemos aplicar aquela ideia aqui, se fizermos `sub_tree` aceitar um parâmetro `level`, usando `yield from` recursivamente e passando a subclasse atual como nova classe raiz com o número do nível seguinte. Veja o <>. @@ -1717,8 +2052,8 @@ para evitar uma recursão infinita. Um caso base é um ramo condicional que retorna sem fazer uma chamada recursiva. O caso base é frequentemente implementado com uma instrução `if`. No <>, `sub_tree` não tem um `if`, -mas há uma condicional implícita no loop `for`: -Se `cls.__subclasses__()` devolver uma lista vazia, o corpo do loop não é executado, +mas há uma condicional implícita no laço `for`: +Se `cls.__subclasses__()` devolver uma lista vazia, o corpo do laço não é executado, e assim a chamada recursiva não ocorre. O caso base ocorre quando a classe `cls` não tem subclasses. Nesse caso, `sub_tree` não produz nada, apenas retorna. @@ -1726,10 +2061,10 @@ Nesse caso, `sub_tree` não produz nada, apenas retorna. O <> funciona como planejado, mas podemos fazê-la mais concisa recordando do padrão que observamos quando alcançamos o nível 3 (no <>): -produzimos uma subclasse de nível _N_, e então iniciamos um loop `for` +produzimos uma subclasse de nível _N_, e então iniciamos um laço `for` aninhado para visitar o nível __N__+1. -No <>, substituímos o loop aninhado por `yield from`. -Agora podemos fundir `tree` e `sub_tree` em uma única geradora. +No <>, substituímos o laço aninhado por `yield from`. +Agora podemos fundir `tree` e `sub_tree` em um unico gerador. O <> é o último passo deste exemplo. [[ex_tree_step6]] @@ -1741,8 +2076,8 @@ include::../code/17-it-generator/tree/step6/tree.py[] ---- ==== -No início da <>, vimos como `yield from` conecta a subgeradora diretamente ao código cliente, escapando da geradora delegante. -Aquela conexão se torna realmente importante quando geradoras são usadas como corrotinas, e não apenas produzem mas também consomem valores do código cliente, como veremos na <>. +No início da <>, vimos como `yield from` conecta a subgeradora diretamente ao código cliente, escapando do gerador delegante. +Aquela conexão se torna realmente importante quando geradores são usados como corrotinas, e não apenas produzem mas também consomem valores do código cliente, como veremos na <>. Após esse primeiro encontro com `yield from`, vamos olhar as dicas de tipo para iteráveis e iteradores.((("", startref="treetravers17")))((("", startref="yieldfrom17")))((("", startref="Gsubyield17"))) @@ -1766,7 +2101,7 @@ Desde o Python 3.10, `FromTo` deve ter uma dica de tipo de `typing.TypeAlias`, p <2> Anota `changes` para aceitar um `Iterable` de tuplas `FromTo`. Tipos `Iterator` não aparecem com a mesma frequência de tipos `Iterable`, mas eles também são simples de escrever. -O <> mostra a conhecida geradora Fibonacci, anotada. +O <> mostra o famoso gerador de Fibonacci, anotado. [[fibo_gen_annot_ex]] ._fibo_gen.py_: `fibonacci` devolve um gerador de inteiros @@ -1777,12 +2112,12 @@ include::../code/17-it-generator/fibo_gen.py[] ---- ==== -Observe que o tipo `Iterator` é usado para geradoras programadas como funções com `yield`, +Observe que o tipo `Iterator` é usado para geradores programadas como funções com `yield`, bem como para iteradores escritos "a mão", como classes que implementam `+__next__+`. Há também o tipo `collections.abc.Generator` (e o decontinuado `typing.Generator` correspondente) que podemos usar para anotar objetos geradores, -mas ele é verboso e redundane para geradoras usadas como iteradores, +mas ele é verboso e redundane para geradores usados como iteradores, que não recebem valores via `.send()`. O <>, quando checado com o Mypy, revela que o tipo `Iterator` é, na verdade, um caso especial simplificado do tipo `Generator`. @@ -1804,7 +2139,7 @@ include::../code/17-it-generator/iter_gen_type.py[] assim o Mypy não reporta erros na checagem de tiposs no <>. `Iterator[T]` é um atalho para `Generator[T, None, None]`. -Ambas as anotações significam "uma geradora que produz itens do tipo `T`, mas não consome ou devolve valores." +Ambas as anotações significam "um gerador que produz itens do tipo `T`, mas não consome ou devolve valores." Geradoras capazes de consumir e devolver valores são corrotinas, nosso próximo tópico. @@ -1813,13 +2148,13 @@ Geradoras capazes de consumir e devolver valores são corrotinas, nosso próximo [NOTE] ==== -A https://fpy.li/pep342[PEP 342—Coroutines via Enhanced Generators (_Corrotinas via geradoras aprimoradas_)] introduziu `.send()` e outros recursos que tornaram possível usar geradoras como corrotinas. A PEP 342 usa a palavra "corrotina" (_coroutine_) no mesmo sentido que estou usando aqui. -É lamentável que a documentação oficial de Python e da biblioteca padrão agora usem uma terminologia inconsistente para se referir a geradoras usadas como corrotinas, me obrigando a adotar o qualificador "corrotina clássica", para diferenciar estas últimas com os novos objetos "corrotinas nativas". +A https://fpy.li/pep342[PEP 342—Coroutines via Enhanced Generators (_Corrotinas via geradores aprimorados_)] introduziu `.send()` e outros recursos que tornaram possível usar geradores como corrotinas. A PEP 342 usa a palavra "corrotina" (_coroutine_) no mesmo sentido que estou usando aqui. +É lamentável que a documentação oficial de Python e da biblioteca padrão agora usem uma terminologia inconsistente para se referir a geradores usadas como corrotinas, me obrigando a adotar o qualificador "corrotina clássica", para diferenciar estas últimas com os novos objetos "corrotinas nativas". -Após o lançamento de Python 3.5, a tendência é usar "corrotina" como sinônimo de "corrotina nativa". Mas a PEP 342 não está descontinuada, e as corrotinas clássicas ainda funcionam como originalmente projetadas, apesar de não serem mais suportadas por `asyncio`. +Após o lançamento de Python 3.5, a tendência é usar "corrotina" como sinônimo de "corrotina nativa". Mas a PEP 342 não está descontinuada, e as corrotinas clássicas ainda funcionam como originalmente projetadas, apesar de não serem mais suportadas por _asyncio_. ==== -Entender as corrotinas clássicas((("coroutines", "understanding classic"))) no Python é mais confuso porque elas são, na verdade, geradoras usadas de uma forma diferente. +Entender as corrotinas clássicas((("coroutines", "understanding classic"))) no Python é mais confuso porque elas são, na verdade, geradores usadas de uma forma diferente. Vamos então dar um passo atrás e examinar outro recurso de Python que pode ser usado de duas maneiras. Vimos na <> que é possível usar instâncias de `tuple` como registros ou como sequências imutáveis. @@ -1836,11 +2171,11 @@ city: tuple[str, str, int] domains: tuple[str, ...] ---- -Algo similar ocorre com geradoras. +Algo similar ocorre com geradores. Elas normalmente são usadas como iteradores, mas podem também ser usadas como corrotinas. Na verdade, _corrotina_ é uma função geradora, criada com a ((("yield keyword")))((("keywords", "yield keyword"))) palavra-chave `yield` em seu corpo. E um((("coroutine objects"))) _objeto corrotina_ é um objeto gerador, fisicamente. -Apesar de compartilharem a mesma implementação subjacente em C, os casos de uso de geradoras e corrotinas em Python são tão diferentes que há duas formas de escrever dicas de tipo para elas: +Apesar de compartilharem a mesma implementação subjacente em C, os casos de uso de geradores e corrotinas em Python são tão diferentes que há duas formas de escrever dicas de tipo para elas: [source, python] ---- @@ -1858,23 +2193,23 @@ sim_taxi: Generator[Event, float, int] Para aumentar a confusão, os autores do módulo `typing` decidiram nomear aquele tipo `Generator`, quando ele de fato descreve a API de um objeto gerador projetado para ser usado como uma corrotina, -enquanto geradoras são mais frequentemente usadas como iteradores simples. +enquanto geradores são mais frequentemente usados como iteradores. A -https://docs.python.org/pt-br/3/library/typing.html#typing.Generator[documentação do módulo `typing`] (EN) descreve assim os parâmetros de tipo formais de `Generator`: +https://fpy.li/9f[documentação do módulo `typing`] (EN) descreve assim os parâmetros de tipo formais de `Generator`: [source, python] ---- Generator[YieldType, SendType, ReturnType] ---- -O `SendType` só é relevante quando a geradora é usada como uma corrotina. +O `SendType` só é relevante quando o gerador é usada como uma corrotina. Aquele parâmetro de tipo é o tipo de `x` na chamada `gen.send(x)`. -É um erro invocar `.send()` em uma geradora escrita para se comportar como um iterador em vez de uma corrotina. +É um erro invocar `.send()` em um gerador escrito para se comportar como um iterador em vez de uma corrotina. Da mesma forma, `ReturnType` só faz sentido para anotar uma corrotina, pois iteradores não devolvem valores como funções regulares. -A única operação razoável em uma geradora usada como um iterador é -invocar `next(it)` direta ou indiretamente, via loops `for` e +A única operação razoável em um gerador usado como um iterador é +invocar `next(it)` direta ou indiretamente, via laços `for` e outras formas de iteração. O `YieldType` é o tipo do valor devolvido em uma chamada a `next(it)`. @@ -1926,10 +2261,10 @@ O <> mostra como fazer o mesmo com uma corrotina.footnote:[Este include::../code/17-it-generator/coroaverager.py[tags=CORO_AVERAGER] ---- ==== -<1> Essa função devolve uma geradora que produz valores `float`, aceita -valores `float` via `.send()`, e não devolve um valor útil.footnote:[Na verdade, ela nunca retorna, a menos que uma exceção interrompa o loop. O Mypy 0.910 aceita tanto `None` quanto pass:[typing​.NoReturn] como parâmetro de tipo devolvido pela geradora—mas ele também aceita `str` naquela posição, então aparentemente o Mypy não consegue, neste momento, analisar completamente o código da corrotina.] -<2> Esse loop infinito significa que a corrotina continuará produzindo médias enquanto o código cliente enviar valores. -<3> O comando `yield` aqui suspende a corrotina, produz um resultado para o cliente e—mais tarde—recebe um valor enviado pelo código de invocação para a corrotina, iniciando outra iteração do loop infinito. +<1> Essa função devolve um gerador que produz valores `float`, aceita +valores `float` via `.send()`, e não devolve um valor útil.footnote:[Na verdade, ela nunca retorna, a menos que uma exceção interrompa o laço. O Mypy 0.910 aceita tanto `None` quanto pass:[typing​.NoReturn] como parâmetro de tipo devolvido pelo gerador—mas ele também aceita `str` naquela posição, então aparentemente o Mypy não consegue, neste momento, analisar completamente o código da corrotina.] +<2> Esse laço infinito significa que a corrotina continuará produzindo médias enquanto o código cliente enviar valores. +<3> O comando `yield` aqui suspende a corrotina, produz um resultado para o cliente e—mais tarde—recebe um valor enviado pelo código de invocação para a corrotina, iniciando outra iteração do laço infinito. Em uma corrotina, `total` e `count` podem ser variáveis locais: atributos de instância ou uma clausura não são necessários para manter o contexto @@ -1959,12 +2294,12 @@ Invocar `next()` ou `.send(None)` para avançar até o primeiro `yield` é conhe Após cada ativação, a corrotina é suspensa exatamente na((("yield keyword")))((("keywords", "yield keyword"))) palavra-chave `yield`, e espera que um valor seja enviado. A linha `coro_avg.send(10)` fornece aquele valor, ativando a corrotina. A expressão `yield` se resolve para o valor 10, que é atribuído à variável `term`. -O restante do loop atualiza as variáveis `total`, `count`, e `average`. -A próxima iteração no loop `while` produz `average`, e a corrotina é novamente suspensa na palavra-chave `yield`. +O restante do laço atualiza as variáveis `total`, `count`, e `average`. +A próxima volta do laço `while` produz `average`, e a corrotina é novamente suspensa na palavra-chave `yield`. O leitor atento pode estar ansioso para saber como a execução de uma instância de `averager` -(por exemplo, `coro_avg`) pode ser encerrada, pois seu corpo é um loop infinito. -Em geral, não precisamos encerrar uma geradora, pois ela será coletada como lixo assim que não existirem mais referências válidas para ela. +(por exemplo, `coro_avg`) pode ser encerrada, pois seu corpo é um laço infinito. +Em geral, não precisamos encerrar um gerador, pois será coletado como lixo assim que não existirem mais referências válidas para ele. Se for necessário encerrá-la explicitamente, use o método `.close()`, como mostra o <>. [[ex_coroaverager_test_cont]] @@ -1980,10 +2315,10 @@ include::../code/17-it-generator/coroaverager.py[tags=CORO_AVERAGER_TEST_CONT] Se não for tratada na função corrotina, a exceção a encerra. `GeneratorExit` é capturada pelo objeto gerador que encapsula a corrotina—por isso não a vemos. <3> Invocar `.close()` em uma corrotina previamente encerrada não tem efeito. -<4> Tentar usar `.send()` em uma corrotina encerrada gera uma `StopIteration`. +<4> Tentar usar `.send()` em uma corrotina encerrada levanta `StopIteration`. Além do método `.send()`, a -https://fpy.li/pep342[PEP 342—Coroutines via Enhanced Generators (_Corrotinas via geradoras aprimoradas_)] também introduziu uma forma de uma corrotina devolver um valor. +https://fpy.li/pep342[PEP 342—Coroutines via Enhanced Generators (_Corrotinas via geradores aprimorados_)] também introduziu uma forma de uma corrotina devolver um valor. A próxima seção mostra como fazer isso.((("", startref="avg17")))((("", startref="runavg17")))((("", startref="CRavg17"))) [[coro_return_sec]] @@ -2034,7 +2369,7 @@ include::../code/17-it-generator/coroaverager2.py[tags=RETURNING_AVERAGER] ==== <1> Para essa corrotina, o tipo produzido é `None`, porque ela não produz dados. Ela recebe dados do tipo `SendType` e devolve uma tupla `Result` quando termina o processamento. <2> Usar `yield` assim só faz sentido em corrotinas, que são projetadas para consumir dados. Isso produz `None`, mas recebe um `term` de `.send(term)`. -<3> Se `term` é um `Sentinel`, sai do loop. Graças a essa checagem com `isinstance`... +<3> Se `term` é um `Sentinel`, sai do laço. Graças a essa checagem com `isinstance`... <4> ...Mypy me permite somar `term` a `total` sem sinalizar um erro (que eu não poderia somar um `float` a um objeto que pode ser um `float` ou um `Sentinel`). <5> Essa linha só será alcançada de um `Sentinel` for enviado para a corrotina. @@ -2061,17 +2396,21 @@ Vamos então fazê-la funcionar, no <>. include::../code/17-it-generator/coroaverager2.py[tags=RETURNING_AVERAGER_DEMO_2] ---- ==== -<1> Enviar o valor sentinela `STOP` faz a corrotina sair do loop e devolver um `Result`. O objeto gerador que encapsula a corrotina gera então uma `StopIteration`. +<1> Enviar o valor sentinela `STOP` faz a corrotina sair do laço e devolver um `Result`. O objeto gerador que encapsula a corrotina gera então uma `StopIteration`. <2> A instância de `StopIteration` tem um atributo `value` vinculado ao valor do comando `return` que encerrou a corrotina. <3> Acredite se quiser! Essa ideia de "contrabandear" o valor devolvido para fora de uma corrotina dentro de uma exceção `StopIteration` é um truque bizarro. Entretanto, esse truque é parte da -https://fpy.li/pep342[PEP 342—Coroutines via Enhanced Generators (_Corrotinas via geradoras aprimoradas_)] (EN), e está documentada com a https://docs.python.org/pt-br/3/library/exceptions.html#StopIteration[exceção `StopIteration`] e na seção https://docs.python.org/pt-br/3/reference/expressions.html#yield-expressions["Expressões yield"] +https://fpy.li/pep342[_PEP 342—Coroutines via Enhanced Generators_ (Corrotinas via geradores aprimorados)], +e está documentada com a +https://fpy.li/9m[exceção `StopIteration`] +e na seção +https://fpy.li/9n["Expressões yield"] do capítulo 6 de -https://fpy.li/17-24[_A Referência da Linguagem Python_]. +https://fpy.li/9p[A Referência da Linguagem Python]. -Uma geradora delegante pode obter o valor devolvido por uma corrotina diretamente, usando a sintaxe +Um gerador delegante pode obter o valor devolvido por uma corrotina diretamente, usando a sintaxe `yield from`, como demonstrado no <>. [[ex_coro_averager2_demo_3]] @@ -2083,16 +2422,16 @@ include::../code/17-it-generator/coroaverager2.py[tags=RETURNING_AVERAGER_DEMO_3 ---- ==== <1> `res` vai coletar o valor devolvido por `averager2`; o mecanismo de `yield from` recupera o valor devolvido quando trata a exceção `StopIteration`, que marca o encerramento da corrotina. Quando `True`, o parâmetro `verbose` faz a corrotina exibir o valor recebido, tornando sua operação visível. -<2> Preste atenção na saída desta linha quando a geradora for executada. +<2> Preste atenção na saída desta linha quando o gerador for acionado. <3> Devolve o resultado. Isso também estará encapsulado em `StopIteration`. <4> Cria o objeto corrotina delegante. -<5> Esse loop vai controlar a corrotina delegante. +<5> Esse laço vai controlar a corrotina delegante. <6> O primeiro valor enviado é `None`, para preparar a corrotina; o último é a sentinela, para pará-la. <7> Captura `StopIteration` para obter o valor devolvido por `compute`. <8> Após as linhas exibidas por `averager2` e `compute`, recebemos a instância de `Result`. Mesmo com esses exemplos aqui, que não fazem muita coisa, o código é difícil de entender. -Controlar a corrotina com chamadas `.send()` e recuperar os resultados é complicado, exceto com `yield from`—mas só podemos usar essa sintaxe dentro de uma geradora/corrotina, +Controlar a corrotina com chamadas `.send()` e recuperar os resultados é complicado, exceto com `yield from`—mas só podemos usar essa sintaxe dentro de um gerador/corrotina, que no fim precisa ser controlada por algum código não-trivial, como mostra o <>. Os exemplos anteriores mostram que o uso direto de corrotinas é incômodo e confuso. @@ -2102,13 +2441,14 @@ Não vou tratar de `.throw()` nesse livro porque—como `.send()`—ele só é [NOTE] ==== Se tiver interesse em um tratamento mais aprofundado de corrotinas clássicas—incluindo o método `.throw()`—por favor veja -https://fpy.li/oldcoro["Classic Coroutines" (_Corrotinas Clássicas_)] (EN) no site que acompanha o livro, pass:[fluentpython.com]. -Aquele texto inclui pseudo-código similar ao Python detalhando como `yield from` controla geradoras e corrotinas, bem como uma pequena simulação de eventos discretos, demonstrando uma forma de concorrência usando corrotinas sem um framework de programação assíncrona. +https://fpy.li/oldcoro[_Classic Coroutines_] no site que acompanha o livro, +http://fluentpython.com[_fluentpython.com_]. +Aquele texto inclui pseudo-código similar ao Python detalhando como `yield from` controla geradores e corrotinas, bem como uma pequena simulação de eventos discretos, demonstrando uma forma de concorrência usando corrotinas sem um framework de programação assíncrona. ==== Na prática, realizar trabalho produtivo com corrotinas exige o suporte de um framework especializada. -É isso que `asyncio` oferecia para corrotinas clássicas lá atrás, no Python 3.3. -Com o advento das corrotinas nativas no Python 3.5, os mantenedores de Python estão gradualmente eliminando o suporte a corrotinas clássicas no `asyncio`. +É isso que _asyncio_ oferecia para corrotinas clássicas lá atrás, no Python 3.3. +Com o advento das corrotinas nativas no Python 3.5, os mantenedores de Python estão gradualmente eliminando o suporte a corrotinas clássicas no _asyncio_. Mas os mecanismos subjacentes são muito similares. A sintaxe `async def` torna a corrotinas nativas mais fáceis de identificar no código, um grande benefício por si só. @@ -2206,13 +2546,13 @@ No Python, um _Iterator_ clássico, implementado "à mão", como no <documentação do módulo itertools] é excelente, especialmente por todos os exemplos incluídos. Apesar das funções daquele módulo serem implementadas em C, a documentação mostra como algumas delas poderiam ser escritas em Python, frequentemente se valendo de outras funções no módulo. Os exemplos de utilização também são ótimos; por exemplo, há um trecho mostrando como usar a função `accumulate` para amortizar um empréstimo com juros, dada uma lista de pagamentos ao longo do tempo. Há também a seção https://docs.python.org/pt-br/3/library/itertools.html#itertools-recipes["Receitas com itertools"], com funções adicionais de alto desempenho, usando as funções de `itertools` como base. +A documentação do módulo https://fpy.li/9c[_itertools_] é excelente, especialmente por todos os exemplos incluídos. Apesar das funções daquele módulo serem implementadas em C, a documentação mostra como algumas delas poderiam ser escritas em Python, frequentemente se valendo de outras funções no módulo. Os exemplos de utilização também são ótimos; por exemplo, há um trecho mostrando como usar a função `accumulate` para amortizar um empréstimo com juros, dada uma lista de pagamentos ao longo do tempo. Há também a seção https://fpy.li/9h["Receitas com itertools"], com funções adicionais de alto desempenho, usando as funções de `itertools` como base. -Além da bilbioteca padrão de Python, recomendo o pacote https://fpy.li/17-30[More Itertools], que continua a bela tradição do `itertools`, oferecendo geradoras poderosas, acompanhadas de muitos exemplos e várias receitas úteis. +Além da bilbioteca padrão de Python, recomendo o pacote https://fpy.li/17-30[More Itertools], que continua a bela tradição do `itertools`, oferecendo geradores poderosos, acompanhadas de muitos exemplos e várias receitas úteis. "Iterators and Generators" (_Iteradores e Geradoras_), o capítulo 4 de _Python Cookbook_, 3ª ed., de David Beazley e Brian K. Jones (O'Reilly), traz 16 receitas sobre o assunto, de muitos ângulos diferentes, concentradas em aplicações práticas. O capítulo contém algumas receitas esclarecedoras com `yield from`. @@ -2237,8 +2577,8 @@ A sintaxe de `yield from` é explicada, com exemplos, na seção "What’s New i https://fpy.li/oldcoro["Classic Coroutines" (_Corrotinas Clássicas_)] (EN) no pass:[fluentpython.com] explica `yield from` em profundidade, incluindo pseudo-código em Python de sua implementação (em C). -David Beazley é a autoridade final sobre geradoras e corrotinas no Python. -O pass:[Python Cookbook], 3ª ed., +David Beazley é a autoridade máxima sobre geradores e corrotinas clássicas no Python. O +https://fpy.li/pycook3[_Python Cookbook, 3rd. ed._], (O'Reilly), que ele escreveu com Brian Jones, traz inúmeras receitas com corrotinas. Os tutoriais de Beazley sobre esse tópico nas PyCon são famosos por sua profundidade e abrangência. O primeiro foi na PyCon US 2008: @@ -2259,7 +2599,7 @@ Um exemplo interessante de um algoritmo clássico reescrito com corrotinas apare O https://fpy.li/17-40[_Effective Python_, 1ª ed.] (Addison-Wesley), de Brett Slatkin, tem um excelente capítulo curto chamado "Consider Coroutines to Run Many Functions Concurrently" (_Considere as Corrotinas para Executar Muitas Funções de Forma Concorrente_). Esse capítulo não aparece na segunda edição de _Effective Python_, mas ainda está https://fpy.li/17-41[disponível online como um capítulo de amostra] (EN). Slatkin apresenta o melhor exemplo que já vi do controle de corrotinas com `yield from`: -uma implementaçào do https://pt.wikipedia.org/wiki/Jogo_da_vida[Jogo da Vida], de John Conway, no qual corrotinas gerenciam o estado de cada célula conforme o jogo avança. +uma implementaçào do https://fpy.li/9j[Jogo da Vida], de John Conway, no qual corrotinas gerenciam o estado de cada célula conforme o jogo avança. Refatorei o código do exemplo do Jogo da Vida—separando funções e classes que implementam o jogo dos trechos de teste no código original de Slatkin. Também reescrevi os testes como doctests, então você pode ver o resultados de várias corrotinas e classes sem executar o script. The https://fpy.li/17-43[exemplo refatorado] está publicado como um https://fpy.li/17-44[GitHub gist]. @@ -2288,8 +2628,8 @@ Isso é próximo do que temos em Python: um único método `+__next__+`, faz o s [role="soapbox-title"] Geradoras conectáveis -Qualquer((("Soapbox sidebars", "pluggable generators", id="SSplgen17"))) um que gerencie grandes conjuntos de dados encontra muitos usos para geradoras. -Essa é a história da primeira vez que criei uma solução prática baseada em geradoras. +Qualquer((("Soapbox sidebars", "pluggable generators", id="SSplgen17"))) um que gerencie grandes conjuntos de dados encontra muitos usos para geradores. +Essa é a história da primeira vez que criei uma solução prática baseada em geradores. Muitos anos atrás, eu trabalhava na BIREME, uma biblioteca digital operada pela OPAS/OMS (Organização Pan-Americana da Saúde/Organização Mundial da Saúde) em São Paulo, Brasil. Entre os conjuntos de dados bibliográficos criados pela BIREME estão o LILACS (Literatura Latino-Americana e do Caribe em Ciências da Saúde) and SciELO (Scientific Electronic Library Online), dois bancos de dados abrangentes, indexando a literatura de pesquisa em ciências da saúde produzida na região. @@ -2298,9 +2638,9 @@ Uma de minhas tarefas era pesquisar alternativas para uma possível migração d Naquela época escrevi um artigo explicando o modelo de dados semi-estruturado e as diferentes formas de representar dados CDS/ISIS com registros do tipo JSON: https://fpy.li/17-45["From ISIS to CouchDB: Databases and Data Models for Bibliographic Records" (_Do ISIS ao CouchDBL Bancos de Dados e Modelos de Dados para Registros Bibliográficos_)] (EN). -Como parte daquela pesquisa, escrevi um script Python para ler um arquivo CDS/ISIS e escrever um arquivo JSON adequado para importação pelo CouchDB ou pelo MongoDB. Inicialmente, o arquivo lia arquivos no formato ISO-2709, exportados pelo CDS/ISIS. A leitura e a escrita tinham de ser feitas de forma incremental, pois os conjuntos de dados completos eram muito maiores que a memória principal. Isso era bastante fácil: cada iteração do loop `for` principal lia um registro do arquivo _.iso_, o manipulava e escrevia no arquivo de saída _.json_. +Como parte daquela pesquisa, escrevi um script Python para ler um arquivo CDS/ISIS e escrever um arquivo JSON adequado para importação pelo CouchDB ou pelo MongoDB. Inicialmente, o arquivo lia arquivos no formato ISO-2709, exportados pelo CDS/ISIS. A leitura e a escrita tinham de ser feitas de forma incremental, pois os conjuntos de dados completos eram muito maiores que a memória principal. Isso era bastante fácil: cada iteração do laço `for` principal lia um registro do arquivo _.iso_, o manipulava e escrevia no arquivo de saída _.json_. -Entretanto, por razões operacionais, foi considerado necessário que o _isis2json.py_ suportasse outro formato de dados do CDS/ISIS: os arquivos binários _.mst_, usados em produção na BIREME--para evitar uma exportação dispendiosa para ISO-2709. Agora eu tinha um problema: as bibliotecas usadas para ler arquivos ISO-2709 e _.mst_ tinham APIs muito diferentes. E o loop de escrita JSON já era complicado, pois o script aceitava, na linha de comando, muitas opções para reestruturar cada registro de saída. Ler dados usando duas APIs diferentes no mesmo loop `for` onde o JSON era produzido seria muito difícil de manejar. +Entretanto, por razões operacionais, foi considerado necessário que o _isis2json.py_ suportasse outro formato de dados do CDS/ISIS: os arquivos binários _.mst_, usados em produção na BIREME--para evitar uma exportação dispendiosa para ISO-2709. Agora eu tinha um problema: as bibliotecas usadas para ler arquivos ISO-2709 e _.mst_ tinham APIs muito diferentes. E o laço de escrita JSON já era complicado, pois o script aceitava, na linha de comando, muitas opções para reestruturar cada registro de saída. Ler dados usando duas APIs diferentes no mesmo laço `for` onde o JSON era produzido seria muito difícil de manejar. A solução foi isolar a lógica de leitura em um par de funções geradoras: uma para cada formato de entrada suportado. No fim, dividi o script _isis2json.py_ em quatro funções. Você pode ver o código-fonte em Python 2, com suas dependências, no repositório https://fpy.li/17-46[_fluentpython/isis2json_] no GitHub.footnote:[O código está em Python 2 porque uma de suas dependências opcionais é uma biblioteca Java chamada _Bruma_, que podemos importar quando executamos o script com o Jython—que ainda não suporta Python 3.] @@ -2308,20 +2648,20 @@ Aqui está uma visão geral em alto nível de como o script está estruturado: `main`:: A função `main` usa `argparse` para ler opções de linha de comando que configuram a estrutura dos registros de saída. Baseado na extensão do nome do arquivo de entrada, uma função geradora é selecionada para ler os dados e produzir os registros, um por vez. -`iter_iso_records`:: Essa função geradora lê arquivos _.iso_ (que se presume estarem no formato ISO-2709). Ela aceita dois argumento: o nome do arquivo e `isis_json_type`, uma das opções relacionadas à estrutura do registro. Cada iteração de seu loop `for` lê um registro, cria um `dict` vazio, o preenche com dados dos campos, e produz o `dict`. +`iter_iso_records`:: Essa função geradora lê arquivos _.iso_ (que se presume estarem no formato ISO-2709). Ela aceita dois argumento: o nome do arquivo e `isis_json_type`, uma das opções relacionadas à estrutura do registro. Cada iteração de seu laço `for` lê um registro, cria um `dict` vazio, o preenche com dados dos campos, e produz o `dict`. -`iter_mst_records`:: Essa outra função geradora lê arquivos _.mst_.footnote:[A biblioteca usada para ler o complexo arquivo binário _.mst_ é na verdade escrita em Java, então essa funcionalidade só está disponível quando _isis2json.py_ é executado com o interpretador Jython, versão 2.5 ou superior. Para mais detalhes, veja o arquivo https://fpy.li/17-47[_README.rst_] (EN)no repositório. As dependências são importadas dentro das funções geradoras que precisam delas, então o script pode rodar mesmo se apenas uma das bibliotecas externas esteja disponível.] Se você examinar o código-fonte de _isis2json.py_, vai notar que ela não é tão simples quanto `iter_iso_records`, mas sua interface e estrutura geral é a mesma: a função recebe como argumentos um nome de arquivo e um `isis_json_type`, e entra em um loop `for`, que cria e produz por iteração um `dict`, representando um único registro. +`iter_mst_records`:: Essa outra função geradora lê arquivos _.mst_.footnote:[A biblioteca usada para ler o complexo arquivo binário _.mst_ é na verdade escrita em Java, então essa funcionalidade só está disponível quando _isis2json.py_ é executado com o interpretador Jython, versão 2.5 ou superior. Para mais detalhes, veja o arquivo https://fpy.li/17-47[_README.rst_] (EN)no repositório. As dependências são importadas dentro das funções geradoras que precisam delas, então o script pode rodar mesmo se apenas uma das bibliotecas externas esteja disponível.] Se você examinar o código-fonte de _isis2json.py_, vai notar que ela não é tão simples quanto `iter_iso_records`, mas sua interface e estrutura geral é a mesma: a função recebe como argumentos um nome de arquivo e um `isis_json_type`, e entra em um laço `for`, que cria e produz por iteração um `dict`, representando um único registro. -`write_json`:: Essa função executa a escrita efetiva de registros JSON, um por vez. Ela recebe numerosos argumentos, mas o primeiro—++input_gen++—é uma referência para uma função geradora: `iter_iso_records` ou `iter_mst_records`. O loop `for` principal itera sobre os dicionários produzidos pela geradora `input_gen` selecionada, os reestrutura de diferentes formas, determinadas pelas opções de linha de comando, e anexa o registro JSON ao arquivo de saída. +`write_json`:: Essa função executa a escrita efetiva de registros JSON, um por vez. Ela recebe numerosos argumentos, mas o primeiro—++input_gen++—é uma referência para uma função geradora: `iter_iso_records` ou `iter_mst_records`. O laço `for` principal itera sobre os dicionários produzidos pelo gerador `input_gen` escolhido, os reestrutura de diferentes formas, conforme as opções de linha de comando, e anexa o registro JSON ao arquivo de saída. Me aproveitando das funções geradoras, pude dissociar a leitura da escrita. Claro, a maneira mais simples de dissociar as duas operações seria ler todos os registros para a memória e então escrevê-los no disco. Mas essa não era uma opção viável, pelo tamanho dos conjuntos de dados. -Usando geradoras, a leitura e a escrita são intercaladas, então o script pode processar arquivos de qualquer tamanho. +Usando geradores, a leitura e a escrita são intercaladas, então o script pode processar arquivos de qualquer tamanho. Além disso, a lógica especial para ler um registro em formatos de entrada diferentes está isolada da lógica de reestruturação de cada registro para escrita. Agora, se precisarmos que _isis2json.py_ suporte um formato de entrada adicional—digamos, MARCXML, uma DTDfootnote:[NT: sigla de _Document Type Definition_, Definição de Tipo de Documento] usada pela Biblioteca do Congresso norte-americano para representar dados ISO-2709—será fácil acrescentar uma terceira função geradora para implementar a lógica de leitura, sem mudar nada na complexa função `write_json`. -Não é ciência de foguete, mas é um exemplo real onde as geradoras permitiram um solução eficiente e flexível para processar bancos de dados como um fluxo de registros, mantendo o uso de memória baixo e independente do tamanho do conjunto de dados.((("", startref="SSplgen17"))) +Não é ciência de foguete, mas é um exemplo real onde os geradores permitiram um solução eficiente e flexível para processar bancos de dados como um fluxo de registros, mantendo o uso de memória baixo e independente do tamanho do conjunto de dados.((("", startref="SSplgen17"))) **** diff --git a/online/cap18.adoc b/online/cap18.adoc index a64c220..ed05b77 100644 --- a/online/cap18.adoc +++ b/online/cap18.adoc @@ -5,59 +5,101 @@ [quote, Raymond Hettinger, um eloquente evangelista de Python] ____ -Gerenciadores de contexto podem vir a ser quase tão importantes quanto a própria sub-rotina. Só arranhamos a superfície das possibilidades. [...] Basic tem uma instrução `with`, há instruções `with` em várias linguagens. Mas elas não fazem a mesma coisa, todas fazem algo muito raso, economizam consultas a atributos com o operador ponto (`.`), elas não configuram e desfazem ambientes. Não pense que é a mesma coisa só porque o nome é igual. A instrução `with` é mais que isso.footnote:[Palestra de abertura da PyCon US 2013: https://fpy.li/18-1["What Makes Python Awesome" ("_O que torna Python incrível_")]; a parte sobre `with` começa em 23:00 e termina em 26:15.] (EN) + +Gerenciadores de contexto podem vir a ser quase tão importantes quanto a própria +sub-rotina. Só arranhamos a superfície das possibilidades. [...] Basic tem uma +instrução `with`, há instruções `with` em várias linguagens. Mas elas não fazem +a mesma coisa, todas fazem algo muito raso, economizam consultas a atributos com +o operador ponto (`.`), elas não configuram e desfazem ambientes. Não pense que +é a mesma coisa só porque o nome é igual. A instrução `with` é mais que +isso.footnote:[Palestra de abertura da PyCon US 2013: https://fpy.li/18-1["What +Makes Python Awesome" ("_O que torna Python incrível_")]; a parte sobre `with` +começa em 23:00 e termina em 26:15.] + ____ -Este((("with, match, and else blocks", "topics covered"))) capítulo é sobre mecanismos de controle de fluxo não muito comuns em outras linguagens e que, por essa razão, podem ser ignorados ou subutilizados em Python. -São eles: +Este((("with, match, and else blocks", "topics covered"))) capítulo é sobre +mecanismos de controle de fluxo não muito comuns em outras linguagens e que, por +essa razão, podem ser ignorados ou subutilizados em Python. São eles: * A instrução `with` e o protocolo de gerenciamento de contexto -* A instrução `match/case` para _pattern matching_ (casamento de padrões) +* A instrução `match/case` para casamento de padrões (_pattern matching_) * A cláusula `else` nas instruções `for`, `while`, e `try` -A instrução `with` cria um contexto temporário e o destrói com segurança, sob o controle de um objeto gerenciador de contexto. Isso previne erros e reduz código repetitivo, tornando as APIs ao mesmo tempo mais seguras e mais fáceis de usar. Programadores Python estão encontrando muitos usos para blocos `with` além do fechamento automático de arquivos. - -Já estudamos _pattern matching_ em capítulos anteriores, mas aqui veremos como a gramática de uma linguagem de programação pode ser expressa como padrões de sequências. -Por isso `match/case` é uma ferramenta eficiente para criar processadores de linguagem fáceis de entender e de estender. Vamos examinar um interpretador completo para um pequeno (porém funcional) subconjunto da linguagem Scheme. As mesmas ideias poderiam ser aplicadas no desenvolvimento de uma linguagem de templates ou uma DSL (_Domain-Specific Language_, literalmente Linguagem de Domínio Específico) para codificar regras de negócio em um sistema maior. - -A cláusula `else` não é grande coisa, mas ajuda a transmitir a intenção por trás do código quando usada corretamente junto com `for`, `while` e `try`. +A instrução `with` cria um contexto temporário e o desfaz com segurança, sob o +controle de um objeto gerenciador de contexto. Isso previne erros e reduz código +repetitivo, tornando as APIs ao mesmo tempo mais seguras e mais fáceis de usar. +Programadores Python estão encontrando muitos usos para blocos `with` além do +fechamento automático de arquivos. + +Já estudamos casamento de padrões em capítulos anteriores, mas aqui veremos como a +gramática de uma linguagem de programação pode ser expressa como padrões de +sequências. +Por isso `match/case` é uma ferramenta eficiente para criar processadores de +linguagem fáceis de entender e de estender. Vamos examinar um interpretador +completo de subconjunto pequeno mas funcional da linguagem Scheme. As +mesmas ideias poderiam ser aplicadas no desenvolvimento de uma linguagem de +templates ou uma DSL (_Domain-Specific Language_, Linguagem de +Domínio Específico) para representar regras de negócio em um sistema maior. + +A cláusula `else` não é grande coisa, mas ajuda a transmitir a intenção por trás +do código quando usada corretamente junto com `for`, `while` e `try`. === Novidades neste capítulo -A <> é nova. +A <> é nova: um exemplo maior de casamento de padrões. -Também((("with, match, and else blocks", "significant changes to"))) atualizei a <> para incluir alguns recursos do módulo `contextlib` adicionados desde o Python 3.6, -e os novos gerenciadores de contexto "parentizados", introduzidos no Python 3.10. +Também((("with, match, and else blocks", "significant changes to"))) atualizei a +<> para incluir alguns recursos do módulo `contextlib` +adicionados desde o Python 3.6, e os novos gerenciadores de contexto +agrupados entre parênteses, desde o Python 3.10. Vamos começar com a poderosa instrução `with`. [[context_managers_sec]] -=== Gerenciadores de contexto e a instrução with +=== Instrução `with` e gerenciadores de contexto -Objetos((("context managers", "purpose of")))((("with, match, and else blocks", "context managers and with blocks", id="wmebcontextm18"))) gerenciadores de contexto -existem para controlar uma instrução `with`, da mesma forma que iteradores existem para controlar uma instrução `for`. +Objetos((("context managers", "purpose of")))((("with, match, and else blocks", +"context managers and with blocks", id="wmebcontextm18"))) gerenciadores de +contexto servem para controlar uma instrução `with`, da mesma forma que +iteradores servem para controlar uma instrução `for`. -A((("with, match, and else blocks", "purpose of with statements"))) instrução `with` -foi projetada para simplificar alguns usos comuns de `try/finally`, -que garantem que alguma operação seja realizada após um bloco de código, -mesmo que o bloco termine com um `return`, uma exceção, ou uma chamada `sys.exit()`. -O código no bloco `finally` normalmente libera um recurso crítico ou restaura um estado anterior que havia sido temporariamente modificado. +A((("with, match, and else blocks", "purpose of with statements"))) instrução +`with` foi projetada para simplificar alguns usos comuns de `try/finally`, que +garantem que alguma operação seja realizada após um bloco de código, mesmo que o +bloco termine com um `return`, uma exceção, ou uma chamada `sys.exit()`. O +código no bloco `finally` normalmente libera um recurso crítico ou restaura um +estado anterior que havia sido temporariamente modificado. A((("context managers", "creative uses for"))) comunidade Python está encontrando novos usos criativos para gerenciadores de contexto. Alguns exemplos, da biblioteca padrão, são: -* Gerenciar transações no módulo `sqlite3` — veja https://docs.python.org/3/library/sqlite3.html#using-the-connection-as-a-context-manager["Usando a conexão como gerenciador de contexto"]. -* Manipular travas, condições e semáforos de forma segura—como descrito na https://docs.python.org/pt-br/3/library/threading.html#using-locks-conditions-and-semaphores-in-the-with-statement[documentação do módulo `threading`] (EN). -* Configurar ambientes customizados para operações aritméticas com objetos `Decimal`—veja a https://docs.python.org/pt-br/3/library/decimal.html#decimal.localcontext[documentação de `decimal.localcontext`] (EN). -* Remendar (_patch_) objetos para testes—veja a https://docs.python.org/pt-br/3/library/unittest.mock.html#patch[função `unittest.mock.patch`] (EN). +* Gerenciar transações no módulo `sqlite3`—veja +https://fpy.li/a3[«Usando a conexão como gerenciador de contexto»]. + +* Manipular travas, condições e +semáforos de forma segura—como descrito na +https://fpy.li/9q[«documentação do módulo `threading`»]. -A((("context managers", "methods included in interface")))((("__enter__")))((("__exit__"))) interface gerenciador de contexto consiste dos métodos `+__enter__+` and `+__exit__+`. -No topo do `with`, Python chama o método `+__enter__+` do objeto gerenciador de contexto. Quando o bloco `with` encerra ou termina por qualquer razão, Python chama -`+__exit__+` no objeto gerenciador de contexto. +* Configurar ambientes customizados para operações +aritméticas com objetos `Decimal`—veja a +https://fpy.li/9r[«documentação de `decimal.localcontext`»]. -O((("context managers", "demonstrations of", id="CMdemo18"))) exemplo mais comum é se assegurar que um objeto arquivo seja fechado. O <> é uma demonstração detalhada do uso do `with` para fechar um arquivo. +* Remendar (_patch_) objetos para testes—veja a função +https://fpy.li/9s[«`unittest.mock.patch`»]. + +A((("context managers", +"methods included in interface")))((("__enter__")))((("__exit__"))) +interface gerenciador de contexto consiste dos métodos `+__enter__+` and +`+__exit__+`. No topo do `with`, Python chama o método `+__enter__+` do objeto +gerenciador de contexto. Quando o bloco `with` encerra ou termina por qualquer +razão, Python chama `+__exit__+` no objeto gerenciador de contexto. + +O((("context managers", "demonstrations of", id="CMdemo18"))) exemplo mais comum +é se assegurar que um objeto arquivo seja fechado. O <> é uma +demonstração detalhada do uso do `with` para fechar um arquivo. [[with_file_demo]] .Demonstração do uso de um objeto arquivo como gerenciador de contexto @@ -79,23 +121,44 @@ Traceback (most recent call last): ValueError: I/O operation on closed file. ---- ==== -<1> `fp` está vinculado ao arquivo de texto aberto, pois o método `+__enter__+` do arquivo devolve `self`. + +<1> `fp` está vinculado ao arquivo de texto aberto, pois o método `+__enter__+` +do arquivo devolve `self`. + <2> Lê `60` caracteres Unicode de `fp`. -<3> A variável `fp` ainda está disponível—blocos `with` não definem um novo escopo, como fazem as funções. -<4> Podemos ler os atributos do objeto `fp`. -<5> Mas não podemos ler mais texto de `fp` pois, no final do bloco `with`, o método `+TextIOWrapper.__exit__+` foi chamado, e isso fechou o arquivo. -A primeira explicação no <> transmite uma informação sutil porém crucial: -o objeto gerenciador de contexto é o resultado da avaliação da expressão após o `with`, mas o valor vinculado à variável alvo (na cláusula `as`) é o resultado devolvido pelo método `+__enter__+` do objeto gerenciador de contexto. +<3> A variável `fp` continua disponível após o bloco; +uma instrução `with` não define um novo +escopo, como faz a instrução `def`. -E acontece que a função `open()` devolve uma instância de `TextIOWrapper`, e o método `+__enter__+` dessa classe devolve `self`. -Mas em uma classe diferente, o método `+__enter__+` também pode devolver algum outro objeto em vez do gerenciador de contexto. +<4> Podemos acessar atributos do objeto `fp`. -Quando o fluxo de controle sai do bloco `with` de qualquer forma, o método `+__exit__+` é invocado no objeto gerenciador de contexto, e não no que quer que `+__enter__+` tenha devolvido. +<5> Mas não podemos mais ler texto de `fp` pois, no final do bloco `with`, o +método `+TextIOWrapper.__exit__+` foi invocado, e este método fecha o arquivo. -A cláusula `as` da instrução `with` é opcional. No caso de `open`, sempre precisamos obter uma referência para o arquivo, para podermos chamar seus métodos. Mas alguns gerenciadores de contexto devolvem `None`, pois não têm nenhum objeto útil para entregar ao usuário. +A nota `①` no <> é sutil mas muito importante: +o objeto gerenciador de contexto é o resultado da avaliação da +expressão após o `with`, mas o valor vinculado à variável alvo (na cláusula +`as`) é o resultado devolvido pelo método `+__enter__+` do objeto gerenciador de +contexto. -O <> mostra o funcionamento de um gerenciador de contexto perfeitamente frívolo, projetado para ressaltar a diferença entre o gerenciador de contexto e o objeto devolvido por seu método `+__enter__+`. +Acontece que a função `open()` devolve uma instância de `TextIOWrapper`, +e o método `+__enter__+` dessa classe devolve `self`. +Mas em uma classe diferente, o método `+__enter__+` pode +devolver algum outro objeto em vez do próprio gerenciador de contexto. + +Quando o fluxo de controle sai do bloco `with` de qualquer forma, o método +`+__exit__+` é invocado no objeto gerenciador de contexto, e não no que quer que +`+__enter__+` tenha devolvido. + +A cláusula `as` da instrução `with` é opcional. No caso de `open`, sempre +precisamos obter uma referência para o arquivo, para podermos invocar seus +métodos. Mas alguns gerenciadores de contexto devolvem `None`, pois não têm +um objeto útil para entregar ao usuário. + +O <> mostra o funcionamento de um gerenciador de contexto +perfeitamente frívolo, projetado para ressaltar a diferença entre o gerenciador +de contexto e o objeto devolvido por seu método `+__enter__+`. [[looking_glass_demo_1]] .Testando a classe gerenciadora de contexto `LookingGlass` @@ -105,10 +168,16 @@ O <> mostra o funcionamento de um gerenciador de contexto include::../code/18-with-match/mirror.py[tags=MIRROR_DEMO_1] ---- ==== -<1> O gerenciador de contexto é uma instância de `LookingGlass`; Python chama `+__enter__+` no gerenciador de contexto e o resultado é vinculado a `what`. -<2> Exibe uma `str`, depois o valor da variável alvo `what`. -A saída de cada `print` será invertida. -<3> Agora o bloco `with` terminou. Podemos ver que o valor devolvido por `+__enter__+`, armazenado em `what`, é a string `'JABBERWOCKY'`. + +<1> O gerenciador de contexto é uma instância de `LookingGlass`; Python chama +`+__enter__+` no gerenciador de contexto e o resultado é vinculado a `what`. + +<2> Exibe uma `str`, depois o valor da variável alvo `what`. A saída de cada +`print` é invertida. + +<3> Agora o bloco `with` terminou. Podemos ver que o valor devolvido por +`+__enter__+`, armazenado em `what`, é a string `'JABBERWOCKY'`. + <4> A saída do programa não está mais invertida. @@ -122,32 +191,62 @@ O <> mostra a implementação de `LookingGlass`. include::../code/18-with-match/mirror.py[tags=MIRROR_EX] ---- ==== + <1> Python invoca `+__enter__+` sem argumentos além de `self`. + <2> Armazena o método `sys.stdout.write` original, para podermos restaurá-lo mais tarde. + <3> Faz um _monkey-patch_ em `sys.stdout.write`, substituindo-o com nosso próprio método. + <4> Devolve a string `'JABBERWOCKY'`, apenas para termos algo para colocar na variável alvo `what`. + <5> Nosso substituto de `sys.stdout.write` inverte o argumento `text` e chama a implementação original. -<6> Se tudo correu bem, Python chama `+__exit__+` com `None, None, None`; se ocorreu uma exceção, os três argumentos recebem dados da exceção, como descrito a seguir, logo após esse exemplo. + +<6> Se tudo correu bem, Python chama `+__exit__+` com `None, None, None`; se +ocorreu uma exceção, os três argumentos recebem dados da exceção, +como descrito logo após este exemplo. + <7> Restaura o método original em `sys.stdout.write`. + <8> Se a exceção não é `None` e seu tipo é `ZeroDivisionError`, exibe uma mensagem... + <9> ...e devolve `True`, para informar o interpretador que a exceção foi tratada. -<10> Se `+__exit__+` devolve `None` ou qualquer valor _falso_, qualquer exceção levantada dentro do bloco `with` será propagada. + +<10> Se `+__exit__+` devolve `None` ou qualquer valor _falso_, qualquer exceção +levantada dentro do bloco `with` será propagada. [TIP] ==== -Quando aplicações reais tomam o controle da saída padrão, elas frequentemente desejam substituir `sys.stdout` com outro objeto similar a um arquivo por algum tempo, depois voltar ao original. O gerenciador de contexto https://fpy.li/18-6[`contextlib.redirect_stdout`] faz exatamente isso: passe a ele seu objeto similar a um arquivo que substituirá `sys.stdout`. + +Quando aplicações reais tomam o controle da saída padrão, elas frequentemente +desejam substituir `sys.stdout` com outro objeto similar a um arquivo por algum +tempo, depois voltar ao original. O gerenciador de contexto +https://fpy.li/18-6[`contextlib.redirect_stdout`] faz exatamente isso: passe a +ele seu objeto similar a um arquivo que substituirá `sys.stdout`. + ==== -O interpretador chama o método `+__enter__+` sem qualquer argumento—além do `self` implícito. Os três argumentos passados a `+__exit__+` são: +O interpretador chama o método `+__enter__+` sem qualquer argumento—além do +`self` implícito. Os três argumentos passados a `+__exit__+` são: `exc_type`:: A classe da exceção (por exemplo, `ZeroDivisionError`). -`exc_value`:: A instância da exceção. Algumas vezes, parâmetros passados para o construtor da exceção—tal como a mensagem de erro—podem ser encontrados em `exc_value.args`. +`exc_value`:: A instância da exceção. Algumas vezes, parâmetros passados para o +construtor da exceção—tal como a mensagem de erro—podem ser encontrados em +`exc_value.args`. -`traceback`:: Um objeto `traceback`.footnote:[Os três argumentos recebidos por `self` são exatamente o que você obtém se chama https://fpy.li/18-7[`sys.exc_info()`] no bloco `finally` de uma instrução `try/finally`. Isso faz sentido, considerando que a instrução `with` tem por objetivo substituir a maioria dos usos de `try/finally`, e chamar `sys.exc_info()` é muitas vezes necessário para determinar que ação de limpeza é necessária.] +`traceback`:: Um objeto `traceback`.footnote:[Os três argumentos recebidos por +`self` são exatamente o que você obtém se chama +https://fpy.li/18-7[`sys.exc_info()`] no bloco `finally` de uma instrução +`try/finally`. Isso faz sentido, considerando que a instrução `with` tem por +objetivo substituir a maioria dos usos de `try/finally`, e invocar +`sys.exc_info()` é muitas vezes necessário para determinar que ação de limpeza é +necessária.] -Para uma visão detalhada de como funciona um gerenciador de contexto, vejamos o <>, onde `LookingGlass` é usado fora de um bloco `with`, de forma que podemos chamar manualmente seus métodos - `+__enter__+` e `+__exit__+`. +Para uma visão detalhada de como funciona um gerenciador de contexto, vejamos o +<>, onde `LookingGlass` é usado fora de um bloco `with`, +de forma que podemos invocar manualmente seus métodos `+__enter__+` e +`+__exit__+`. [[looking_glass_demo_2]] .Exercitando o `LookingGlass` sem um bloco `with` @@ -158,19 +257,28 @@ include::../code/18-with-match/mirror.py[tags=MIRROR_DEMO_2] ---- ==== <1> Instancia e inspeciona a instância de `manager`. + <2> Chama o método `+__enter__+` do manager e guarda o resultado em `monster`. -<3> `monster` é a string `'JABBERWOCKY'`. O identificador `True` aparece invertido, porque toda a saída via `stdout` passa pelo método `write`, que modificamos em `+__enter__+`. -<4> Chama `+manager.__exit__+` para restaurar o `stdout.write` original.((("", startref="CMdemo18"))) + +<3> `monster` é a string `'JABBERWOCKY'`. O identificador `True` aparece +invertido, porque toda a saída via `stdout` passa pelo método `write`, que +modificamos em `+__enter__+`. + +<4> Chama `+manager.__exit__+` para restaurar o `stdout.write` original. +((("",startref="CMdemo18"))) .Gerenciadores de contexto entre parênteses [TIP] ==== + Python((("context managers", "parenthesized in Python 3.10"))) 3.10 adotou https://fpy.li/pep617[um novo parser] (analisador sintático), -mais poderoso que o antigo https://fpy.li/18-8[parser LL(1)]. +mais poderoso que o antigo +https://fpy.li/18-8[parser LL(1)]. Isso permitiu introduzir novas sintaxes que não eram viáveis anteriormente. -Uma melhoria na sintaxe foi permitir gerenciadores de contexto agrupados entre parênteses, assim: +Uma melhoria na sintaxe foi permitir gerenciadores de contexto agrupados +entre parênteses, assim: [source, python] ---- @@ -182,54 +290,98 @@ with ( ... ---- -Antes do 3.10, as linhas acima teriam que ser escritas como blocos `with` aninhados. +Antes do 3.10, as linhas acima teriam que ser escritas como blocos `with` +aninhados. + ==== -A biblioteca padrão inclui o pacote `contextlib`, com funções, classe e decoradores muito convenientes para desenvolver, combinar e usar gerenciadores de contexto. +A biblioteca padrão inclui o pacote `contextlib`, com funções, classes e +decoradores convenientes para desenvolver, combinar e usar gerenciadores +de contexto. [[context_utilities_sec]] -==== Utilitários do contextlib +==== Utilitários da `contextlib` -Antes((("context managers", "contextlib utilities"))) de desenvolver suas próprias classes gerenciadoras de contexto, dê uma olhada em -https://docs.python.org/pt-br/3/library/contextlib.html[`contextlib`—"Utilities for ++with++-statement contexts" ("Utilitários para contextos da instrução ++with++)], na documentação de Python. -Pode ser que você esteja prestes a escrever algo que já existe, ou talvez exista uma classe ou algum invocável que tornará seu trabalho mais fácil. +Antes((("context managers", "contextlib utilities"))) de desenvolver suas +próprias classes gerenciadoras de contexto, dê uma olhada em +https://fpy.li/9t[`contextlib`] +(Utilitários para contextos da instrução `with`), +na documentação de Python. +Pode ser que você esteja prestes a escrever algo que já existe, ou talvez exista +uma classe ou algum invocável que tornará seu trabalho mais fácil. -Além do gerenciador de contexto `redirect_stdout` mencionado logo após o <>, o `redirect_stderr` foi acrescentado no Python 3.5—ele faz o mesmo que seu par mais antigo, mas com as saídas direcionadas para `stderr`. +Além do gerenciador de contexto `redirect_stdout` mencionado logo após o +<>, o `redirect_stderr` foi acrescentado no Python 3.5—ele faz +o mesmo que seu par mais antigo, mas com as saídas direcionadas para `stderr`. O pacote `contextlib` também inclui: -`closing`:: Uma função para criar gerenciadores de contexto a partir de objetos que forneçam um método `close()` mas não implementam a interface `+__enter__/__exit__+`. +`closing`:: Uma função para criar gerenciadores de contexto a partir de objetos +que forneçam um método `close()` mas não implementam a interface +`+__enter__/__exit__+`. -`suppress`:: Um gerenciador de contexto para ignorar temporariamente exceções passadas como parâmetros. +`suppress`:: Um gerenciador de contexto para ignorar temporariamente exceções +passadas como parâmetros. -`nullcontext`:: Um gerenciador de contexto que não faz nada, para simplificar a lógica condicional em torno de objetos que podem não implementar um gerenciador de contexto adequado. -Ele serve como um substituto quando o código condicional antes do bloco `with` pode ou não fornecer um gerenciador de contexto para a instrução `with`. Adicionado no Python 3.7. +`nullcontext`:: Um gerenciador de contexto que não faz nada, para simplificar a +lógica condicional em torno de objetos que podem não implementar um gerenciador +de contexto adequado. Ele serve como um substituto quando o código condicional +antes do bloco `with` pode ou não fornecer um gerenciador de contexto para a +instrução `with`. Adicionado no Python 3.7. -O módulo `contextlib` fornece classes e um decorador que são mais largamente aplicáveis que os decoradores mencionados acima: +O módulo `contextlib` fornece classes e um decorador que são mais largamente +aplicáveis que os decoradores mencionados acima: -`@contextmanager`:: Um decorador que permite construir um gerenciador de contexto a partir de um simples função geradora, em vez de criar uma classe e implementar a interface. Veja a <>. +`@contextmanager`:: Um decorador que permite construir um gerenciador de +contexto a partir de um simples função geradora, em vez de criar uma classe e +implementar a interface. Veja a <>. -`AbstractContextManager`:: Uma ABC que formaliza a interface gerenciador de contexto, e torna um pouco mais fácil criar classes gerenciadoras de contexto, através de subclasses—adicionada no Python 3.6. +`AbstractContextManager`:: Uma ABC que formaliza a interface gerenciador de +contexto, e torna um pouco mais fácil criar classes gerenciadoras de contexto, +através de subclasses—adicionada no Python 3.6. -`ContextDecorator`:: Uma classe base para definir gerenciadores de contexto baseados em classes que podem também ser usadas como decoradores de função, rodando a função inteira dentro de um contexto gerenciado. +`ContextDecorator`:: Uma classe base para definir gerenciadores de contexto +baseados em classes que podem também ser usadas como decoradores de função, +rodando a função inteira dentro de um contexto gerenciado. -`ExitStack`:: Um gerenciador de contexto que permite entrar em um número variável de gerenciadores de contexto. Quando o bloco ++with++ termina, ++ExitStack++ chama os métodos `+__exit__+` dos gerenciadores de contexto empilhados na ordem LIFO (Last In, First Out, _Último a Entrar, Primeiro a Sair_). Use essa classe quando você não sabe de antemão em quantos gerenciadores de contexto será necessário entrar no bloco `with`; por exemplo, ao abrir ao mesmo tempo todos os arquivos de uma lista arbitrária de arquivos. +`ExitStack`:: Um gerenciador de contexto que permite entrar em um número +variável de gerenciadores de contexto. Quando o bloco ++with++ termina, +++ExitStack++ chama os métodos `+__exit__+` dos gerenciadores de contexto +empilhados na ordem LIFO (Last In, First Out, _Último a Entrar, Primeiro a +Sair_). Use essa classe quando você não sabe de antemão em quantos gerenciadores +de contexto será necessário entrar no bloco `with`; por exemplo, ao abrir ao +mesmo tempo todos os arquivos de uma lista arbitrária de arquivos. -Com Python 3.7, `contextlib` acrescentou `AbstractAsyncContextManager`, `@asynccontextmanager`, e `AsyncExitStack`. -Eles são similares aos utilitários equivalentes sem a parte `async` no nome, mas projetados para uso com a nova instrução `async with`, tratado no <>. +Com Python 3.7, `contextlib` acrescentou `AbstractAsyncContextManager`, +`@asynccontextmanager`, e `AsyncExitStack`. Eles são similares aos utilitários +equivalentes sem a parte `async` no nome, mas projetados para uso com a nova +instrução `async with`, tratada no <>. -Desses todos, o utilitário mais amplamente usado é o decorador `@contextmanager`, então ele merece mais atenção. Esse decorador também é interessante por mostrar um uso não relacionado a iteração para a instrução `yield`. +Entre estas ferramentas, a mais fácil de usar é o decorador +`@contextmanager`, então ele merece mais atenção. Este decorador também é +interessante por mostrar um uso da instrução `yield` não relacionado a iteração. [[using_cm_decorator_sec]] ==== Usando o @contextmanager -O((("@contextmanager decorator", id="atcontextm18")))((("context managers", "@contextmanager decorator", id="CMatcontextm18"))) decorador `@contextmanager` é uma ferramenta elegante e prática, que une três recursos distintos de Python: um decorador de função, um gerador, e a instrução `with`. +O((("@contextmanager decorator", id="atcontextm18")))((("context managers", +"@contextmanager decorator", id="CMatcontextm18"))) decorador `@contextmanager` +é uma ferramenta elegante e prática, que une três recursos distintos de Python: +um decorador de função, um gerador, e a instrução `with`. -Usar o `@contextmanager` reduz o código repetitivo na criação de um gerenciador de contexto: em vez de escrever toda uma classe com métodos `+__enter__/__exit__+`, você só precisa implementar um gerador com uma única instrução `yield`, que deve produzir o que o método `+__enter__+` deveria devolver. +Usar o `@contextmanager` reduz o código repetitivo na criação de um gerenciador +de contexto: em vez de escrever toda uma classe com métodos +`+__enter__/__exit__+`, você só precisa implementar um gerador com uma única +instrução `yield`, que deve produzir o que o método `+__enter__+` deveria +devolver. -Em um gerador decorado com `@contextmanager`, o `yield` divide o corpo da função em duas partes: tudo que vem antes do `yield` será executado no início do bloco `with`, quando o interpretador chama `+__enter__+`; o código após o `yield` será executado quando `+__exit__+` é chamado, no final do bloco. +Em um gerador decorado com `@contextmanager`, o `yield` divide o corpo da função +em duas partes: tudo que vem antes do `yield` será executado no início do bloco +`with`, quando o interpretador chama `+__enter__+`; o código após o `yield` será +executado quando `+__exit__+` é invocado, no final do bloco. -O <> substitui a classe `LookingGlass` do <> por uma função geradora. +O <> substitui a classe `LookingGlass` do +<> por uma função geradora. [[looking_glass_gen_ex]] .mirror_gen.py: um gerenciador de contexto implementado com um gerador @@ -241,10 +393,14 @@ include::../code/18-with-match/mirror_gen.py[tags=MIRROR_GEN_EX] ==== <1> Aplica o decorador `contextmanager`. <2> Preserva o método `sys.stdout.write` original. -<3> `reverse_write` pode chamar `original_write` mais tarde, pois ele está disponível em sua clausura (closure). +<3> `reverse_write` pode invocar `original_write` mais tarde, +pois ele está disponível em sua clausura (closure). <4> Substitui `sys.stdout.write` por `reverse_write`. -<5> Produz o valor que será vinculado à variável alvo na cláusula `as` da instrução `with`. O gerador se detem nesse ponto, enquanto o corpo do `with` é executado. -<6> Quando o fluxo de controle sai do bloco `with`, a execução continua após o `yield`; neste ponto o `sys.stdout.write` original é restaurado. +<5> Produz o valor que será vinculado à variável alvo na cláusula `as` da +instrução `with`. O gerador se detem nesse ponto, enquanto o corpo do `with` é +executado. +<6> Quando o fluxo de controle sai do bloco `with`, a execução continua após o +`yield`; neste ponto o `sys.stdout.write` original é restaurado. O <> mostra a função `looking_glass` em operação. @@ -256,27 +412,41 @@ O <> mostra a função `looking_glass` em operação. include::../code/18-with-match/mirror_gen.py[tags=MIRROR_GEN_DEMO_1] ---- ==== -<1> A única diferença do <> é o nome do gerenciador de contexto:`looking_glass` em vez de `LookingGlass`. +<1> A única diferença do <> é o nome do gerenciador de +contexto:`looking_glass` em vez de `LookingGlass`. -O decorador `contextlib.contextmanager` envolve a função em uma classe que implementa os métodos `+__enter__+` e `+__exit__+`.footnote:[A classe real se chama `_GeneratorContextManager`. Se você quiser saber exatamente como ela funciona, leia seu https://fpy.li/18-10[código-fonte] na __Lib/contextlib.py__ de Python 3.10.] +O decorador `contextlib.contextmanager` envolve a função em uma classe que +implementa os métodos `+__enter__+` e `+__exit__+`.footnote:[A classe real se +chama `_GeneratorContextManager`. Se você quiser saber exatamente como ela +funciona, leia seu https://fpy.li/18-10[código-fonte] na __Lib/contextlib.py__ +de Python 3.10.] O método `+__enter__+` daquela classe: -. Chama a função geradora para obter um objeto gerador—vamos chamá-lo de `gen`. -. Chama `next(gen)` para acionar com ele a palavra reservada `yield`. -. Devolve o valor produzido por `next(gen)`, para permitir que o usuário o vincule a uma variável usando o formato `with/as`. +. Invoca a função geradora para obter um objeto gerador—vamos chamá-lo de `gen`. +. Invoca `next(gen)` para executar o gerador até o `yield`. +. Devolve o valor produzido por `next(gen)`, para permitir que o usuário o +vincule a uma variável usando o cláusula `as` da instrução `with`. Quando o bloco `with` termina, o método `+__exit__+`: -. Verifica se uma exceção foi passada como `exc_type`; em caso afirmativo, `gen.throw(exception)` é invocado, fazendo com que a exceção seja levantada na linha `yield`, dentro do corpo da função geradora. -. Caso contrário, `next(gen)` é chamado, retomando a execução do corpo da função geradora após o `yield`. +. Verifica se uma exceção foi passada no argumento `exc_type`; em caso afirmativo, +`gen.throw(exception)` é invocado, fazendo com que a exceção seja levantada +na posição do `yield`, dentro do corpo da função geradora. + +. Caso contrário, `next(gen)` é invocado, retomando a execução do corpo da função +geradora após o `yield`. O <> tem um defeito: Se uma exceção for levantada no corpo do bloco `with`, -o interpretador Python vai capturá-la e levantá-la novamente na expressão `yield` dentro de `looking_glass`. -Mas não há tratamento de erro ali, então o gerador `looking_glass` vai terminar sem nunca restaurar o método `sys.stdout.write` original, deixando o sistema em um estado inconsistente. +o interpretador Python vai capturá-la e levantá-la novamente na expressão +`yield` dentro de `looking_glass`. Mas não há tratamento de erro ali, então o +gerador `looking_glass` vai terminar sem nunca restaurar o método +`sys.stdout.write` original, deixando o sistema em um estado inconsistente. -O <> acrescenta o tratamento especial da exceção `ZeroDivisionError`, tornando esse gerenciador de contexto funcionalmente equivalente ao <>, baseado em uma classe. +O <> acrescenta o tratamento especial da exceção +`ZeroDivisionError`, tornando esse gerenciador de contexto funcionalmente +equivalente ao <>, baseado em uma classe. [[looking_glass_gen_exc_ex]] .mirror_gen_exc.py: gerenciador de contexto baseado em um gerador implementando tratamento de erro—com o mesmo comportamento externo de <> @@ -291,21 +461,35 @@ include::../code/18-with-match/mirror_gen_exc.py[tags=MIRROR_GEN_EXC] <3> Desfaz o _monkey-patching_ de `sys.stdout.write`. <4> Mostra a mensagem de erro, se ela foi determinada. -Lembre-se que o método `+__exit__+` diz ao interpretador que ele tratou a exceção ao devolver um valor _verdadeiro_; nesse caso, o interpretador suprime a exceção. +Lembre-se que o método `+__exit__+` diz ao interpretador que ele tratou a +exceção ao devolver um valor _verdadeiro_; nesse caso, o interpretador suprime a +exceção. -Por outro lado, se `+__exit__+` não devolver explicitamente um valor, o interpretador recebe o habitual `None`, e propaga a exceção. -Com o `@contextmanager`, o comportamento default é invertido: o método `+__exit__+` fornecido pelo decorador assume que qualquer exceção enviada para o gerador está tratada e deve ser suprimida. +Por outro lado, se `+__exit__+` não devolver explicitamente um valor, o +interpretador recebe o habitual `None`, e propaga a exceção. Com o +`@contextmanager`, o comportamento default é invertido: o método `+__exit__+` +fornecido pelo decorador assume que qualquer exceção enviada para o gerador está +tratada e deve ser suprimida. [TIP] ==== -Ter um `try/finally` (ou um bloco `with`) em torno do `yield` é o preço inescapável do uso de `@contextmanager`, -porque você nunca sabe o que os usuários do seu gerenciador de contexto vão fazer dentro do bloco `with`.footnote:[Essa dica é uma citação literal de um comentário de Leonardo Rochael, um do revisores técnicos desse livro. Muito bem dito, Leo!] + +Ter um `try/finally` (ou um bloco `with`) em torno do `yield` é o preço +inescapável do uso de `@contextmanager`, porque você nunca sabe o que os +usuários do seu gerenciador de contexto vão fazer dentro do bloco +`with`.footnote:[Essa dica é uma citação literal de um comentário de Leonardo +Rochael, um do revisores técnicos desse livro. Muito bem dito, Leo!] + ==== -Um recurso pouco conhecido do `@contextmanager` é que os geradores decorados com ele podem ser usados eles mesmos como decoradores.footnote:["Pouco conhecido" porque pelo menos eu e os outros revisores técnicos não sabíamos disso até Caleb Hattingh nos contar. Obrigado, Caleb!] -Isso ocorre porque `@contextmanager` é implementado com a classe `contextlib.ContextDecorator`. +Um recurso pouco conhecido do `@contextmanager` é que os geradores decorados com +ele também podem ser usados como decoradores.footnote:["Pouco conhecido" +porque pelo menos eu e os outros revisores técnicos não sabíamos disso até Caleb +Hattingh nos contar. Obrigado, Caleb!] Isso ocorre porque `@contextmanager` é +implementado com a classe `contextlib.ContextDecorator`. -O <> mostra o gerenciador de contexto `looking_glass` do <> sendo usado como um decorador. +O <> mostra o gerenciador de contexto +`looking_glass` do <> sendo usado como um decorador. [[looking_glass_gen_deco_demo]] .O gerenciador de contexto `looking_glass` também funciona como um decorador. @@ -318,9 +502,15 @@ include::../code/18-with-match/mirror_gen.py[tags=MIRROR_GEN_DECO] <1> `looking_glass` faz seu trabalho antes e depois do corpo de `verse` rodar. <2> Isso confirma que o `sys.write` original foi restaurado. -Compare o <> com o <>, onde `looking_glass` é usado como um gerenciador de contexto. +Compare o <> com o <>, onde +`looking_glass` é usado como um gerenciador de contexto. -Um interessante exemplo real do uso do `++@contextmanager++` fora da biblioteca padrão é a https://fpy.li/18-11[reescrita de arquivo no mesmo lugar usando um gerenciador de contexto] de Martijn Pieters. O <> mostra como ele é usado. +Um exemplo real interessante de `++@contextmanager++` fora da biblioteca +padrão é descrito em +https://fpy.li/18-11[_Easy in-place file rewriting_] +(Reescrita de arquivo no mesmo lugar] +de Martijn Pieters. O <> mostra como usar +função `inplace` apresentada naquele artigo. [[inplace_ex]] .Um gerenciador de contexto para reescrever arquivos no lugar @@ -339,34 +529,71 @@ with inplace(csvfilename, 'r', newline='') as (infh, outfh): ---- ==== -A função `inplace` é um gerenciador de contexto que fornece a você dois identificadores—no exemplo, `infh` e `outfh`—para o mesmo arquivo, permitindo que seu código leia e escreva ali ao mesmo tempo. Isso é mais fácil de usar que a https://docs.python.org/pt-br/3/library/fileinput.html#fileinput.input[função `fileinput.input`] (EN) da biblioteca padrão (que, por sinal, também fornece um gerenciador de contexto). +A função `inplace` constrói um gerenciador de contexto que fornece a você duas +referências para o mesmo arquivo (`infh` e `outfh` no exemplo), permitindo +que seu código leia e escreva nele ao mesmo tempo. Isso é mais fácil de usar que +a https://fpy.li/9v[função `fileinput.input`] da biblioteca padrão (que, por +sinal, também fornece um gerenciador de contexto). -Se você quiser estudar o código-fonte do `inplace` de Martijn (listado no https://fpy.li/18-11[post]) (EN), encontre a((("yield keyword")))((("keywords", "yield keyword"))) palavra reservada `yield`: tudo antes dela lida com configurar o contexto, que implica criar um arquivo de backup, então abrir e produzir referências para os identificadores de arquivo de leitura e escrita que serão devolvidos pela chamada a `+__enter__+`. O processamento do `+__exit__+` após o `yield` fecha os identificadores do arquivo e, se algo deu errado, restaura o arquivo do backup. +Se você quiser estudar o código-fonte do `inplace` de Martijn (listado no +https://fpy.li/18-11[«post»]), encontre a((("yield keyword")))((("keywords", +"yield keyword"))) palavra reservada `yield`: tudo antes dela lida com +configurar o contexto, que implica criar um arquivo de backup, então abrir e +produzir referências para os objetos de leitura e escrita que +serão devolvidos pela chamada a `+__enter__+`. O processamento do `+__exit__+` +após o `yield` fecha os objetos vinculados ao arquivo e, se algo deu errado, +restaura o arquivo do backup. -Isso conclui nossa revisão da instrução `with` e dos gerenciadores de contexto. Vamos agora olhar o `match/case`, no contexto de um exemplo completo.((("", startref="wmebcontextm18")))((("", startref="CMatcontextm18")))((("", startref="atcontextm18"))) +Isso conclui nossa revisão da instrução `with` e dos gerenciadores de contexto. +Vamos agora olhar o `match/case`, no contexto de um exemplo completo.((("", +startref="wmebcontextm18")))((("", startref="CMatcontextm18")))((("", +startref="atcontextm18"))) [[pattern_matching_case_study_sec]] === Pattern matching no lis.py: um estudo de caso -Na((("lis.py interpreter", "topics covered")))((("with, match, and else blocks", "pattern matching in lis.py", id="WMEBlispy18")))((("pattern matching", "in lis.py interpreter", secondary-sortas="lis.py", id="PMlispy18"))) <>, vimos exemplos de sequências de padrões extraídos da função `evaluate` do interpretador _lis.py_ de Peter Norvig, portado para Python 3.10. -Nessa seção quero dar um visão geral do funcionamento do _lis.py_, e também explorar todas as cláusulas `case` de `evaluate`, explicando não apenas os padrões mas também o que o interpretador faz em cada `case`. +Na((("lis.py interpreter", "topics covered")))((("with, match, and else blocks", +"pattern matching in lis.py", id="WMEBlispy18")))((("pattern matching", "in +lis.py interpreter", secondary-sortas="lis.py", id="PMlispy18"))) +<>, vimos exemplos de sequências de padrões +extraídos da função `evaluate` do interpretador _lis.py_ de Peter Norvig, +portado para Python 3.10. Nessa seção quero dar um visão geral do funcionamento +do _lis.py_, e também explorar todas as cláusulas `case` de `evaluate`, +explicando não apenas os padrões mas também o que o interpretador faz em cada +`case`. -Além de mostrar mais _pattern matching_, escrevi essa seção por três razões: +Além de mostrar mais casamento de padrões, escrevi essa seção por três razões: . O _lis.py_ de Norvig é um lindo exemplo de código Python idiomático. . A simplicidade do Scheme é uma aula magna de design de linguagens. -. Aprender como um interpretador funciona me deu um entendimento mais profundo sobre Python e sobre linguagens de programação em geral—interpretadas ou compiladas. +. Aprender como um interpretador funciona me deu um entendimento mais profundo +sobre Python e sobre linguagens de programação em geral—interpretadas ou +compiladas. -Antes de olhar o código Python, vamos ver um pouquinho de Scheme, para você poder entender este estudo de caso—pensando em quem nunca viu Scheme e Lisp antes. +Antes de olhar o código Python, vamos ver um pouquinho de Scheme, para você +poder entender este estudo de caso—pensando em quem nunca viu Scheme e Lisp +antes. ==== A sintaxe do Scheme -No((("lis.py interpreter", "Scheme syntax", id="LPIschemesyn18")))((("Scheme language", id="scheme18"))) Scheme não há distinção entre expressões e instruções, como temos em Python. Também não existem operadores infixos. Todas as expressões usam a notação prefixa, como `(+ x 13)` em vez de `x + 13`. -A mesma notação prefixa é usada para chamadas de função—por exemplo, `(gcd x 13)`—e formas especiais—por exemplo, `(define x 13)`, que em Python escreveríamos como uma declaração de atribuição `x = 13`. - -A((("S-expression"))) notação usada no Scheme e na maioria dos dialetos de Lisp é conhecida como _S-expression_ (_Expressão-S_).footnote:[As pessoas reclamam sobre o excesso de parênteses no Lisp, mas uma indentação bem pensada e um bom editor praticamente resolvem essa questão. O maior problema de legibilidade é o uso da mesma notação `(f ...)` para chamadas de função e formas especiais como `(define ...)`, `(if ...)` e `(quote ...)`, que de forma alguma se comportam como chamadas de função] +No((("lis.py interpreter", "Scheme syntax", id="LPIschemesyn18")))((("Scheme +language", id="scheme18"))) Scheme não há diferença sintática entre expressões e +instruções (_statements_), como temos em Python. Também não existem operadores infixos. Todas +as expressões usam a notação prefixa, como `(+ x 13)` em vez de `x + 13`. A +mesma notação prefixa é usada para chamadas de função—por exemplo, `(gcd x +13)`—e formas especiais—por exemplo, `(define x 13)`, que em Python +escreveríamos como uma instrução de atribuição: `x = 13`. + +A((("S-expression"))) notação usada no Scheme e na maioria dos dialetos de Lisp +é conhecida como _S-expression_ (expressão-S).footnote:[As pessoas reclamam +sobre o excesso de parênteses no Lisp, mas um bom +editor e a indentação consistente do código praticamente resolvem essa questão. +O maior problema de legibilidade é o +uso da mesma notação `(f ...)` para invocar funções e aplicar formas especiais como +`(define ...)`, `(if ...)` e `(quote ...)`, que de têm comportamentos muito +diferentes de qualquer função.] O <> mostra um exemplo simples em Scheme. @@ -390,8 +617,8 @@ O <> mostra um exemplo simples em Scheme. O <> mostra três expressões em Scheme: duas definições de função—`mod` e `gcd`—e uma chamada a `display`, que vai devolver 9, o resultado de `(gcd 18 45)`. -O <> é o mesmo código em Python (menor que a explicação em português do -https://pt.wikipedia.org/wiki/Algoritmo_de_Euclides[_algoritmo recursivo de Euclides_]). +O <> é o mesmo código em Python (mais curto que a explicação em português do +https://fpy.li/9w[«algoritmo recursivo de Euclides»]). [[ex_gcd_python]] .Igual ao <>, mas escrito em Python @@ -411,23 +638,31 @@ print(gcd(18, 45)) ---- ==== -Em Python idiomático, eu usaria o operador `%` em vez de reinventar `mod`, e seria mais eficiente usar um loop `while` em vez de recursão. Mas queria mostrar duas definições de função, e fazer os exemplos o mais similares possível, para ajudar você a ler o código Scheme. +Em Python idiomático, eu usaria o operador `%` em vez de reinventar `mod`, e +seria mais eficiente usar um laço `while` em vez de recursão. Minha intenção foi mostrar +duas definições de funções, e fazer os exemplos o mais similares possível, para +ajudar você a ler o código Scheme. -O Scheme não tem instruções iterativas de controle de fluxo como `while` ou `for`. -A iteração é feita com recursão. -Observe que não há atribuições nos exemplos em Python e Scheme. O uso extensivo de recursão e o uso mínimo de atribuição são marcas registradas do estilo funcional de programação.footnote:[Para tornar a iteração por recursão prática e eficiente, o Scheme e outras linguagens funcionais implementam _chamadas de cauda apropriadas (ou otimizadas)_. Para ler mais sobre isso, veja o <>.] +O Scheme não tem instruções de laço como `while` ou `for`. +Toda iteração é feita com recursão. +Observe que não há atribuições nos exemplos em Python e Scheme. O uso intensivo +de recursão e o uso mínimo de atribuição são marcas registradas do estilo +funcional de programação.footnote:[Para que a iteração por recursão seja prática e +eficiente, o Scheme e outras linguagens funcionais otimizam certas chamadas +recursivas. Para ler mais sobre isso, veja o +<>.] Agora vamos revisar o código da versão Python 3.10 do _lis.py_. -O código-fonte completo, com testes, está -no diretório +O código-fonte completo, com testes, está no diretório https://fpy.li/18-15[_18-with-match/lispy/py3.10/_], -do repositório https://fpy.li/code[_fluentpython/example-code-2e_] no Github.((("", startref="scheme18")))((("", startref="LPIschemesyn18"))) +do repositório https://fpy.li/code[_fluentpython/example-code-2e_].((("", startref="scheme18")))((("", startref="LPIschemesyn18"))) ==== Importações e tipos -O <> mostra((("lis.py interpreter", "imports and types"))) as primeiras linhas do _lis.py_. -O uso do `TypeAlias` e do operador de união de tipos `|` exige Python 3.10. +O <> mostra((("lis.py interpreter", "imports and types"))) as +primeiras linhas do _lis.py_. O uso do `TypeAlias` e do operador de união de +tipos `|` exige Python 3.10. [[lis_top_ex]] .lis.py: início do arquivo @@ -440,20 +675,29 @@ include::../code/18-with-match/lispy/py3.10/lis.py[tags=IMPORTS] Os tipos definidos são: -`Symbol`:: Só um alias para `str`. -Em _lis.py_, `Symbol` é usado para identificadores; -não há um tipo de dados string, com operações como fatiamento (_slicing_), divisão (_splitting_), etc.footnote:[Mas o segundo interpretador de Norvig, https://fpy.li/18-16[_lispy.py_], suporta strings como um tipo de dado, e também traz recursos avançados como macros sintáticas, continuações, e chamadas de cauda otimizadas. Entretanto, o _lispy.py_ é quase três vezes maior que o _lis.py_—é mais difícil de entender.] +`Symbol`:: Só um alias para `str`. Em _lis.py_, `Symbol` é usado para +identificadores; não há um tipo de dados string, com operações como fatiamento +(_slicing_), partição (_splitting_), etc.footnote:[Mas o segundo interpretador de +Norvig, https://fpy.li/18-16[_lispy.py_], suporta strings como um tipo de dado, +e também traz recursos avançados como macros sintáticas, continuações, e +chamadas de cauda otimizadas. Entretanto, o _lispy.py_ é quase três vezes maior +que o _lis.py_—é mais difícil de entender.] -`Atom`:: Um elemento sintático simples, tal como um número ou um `Symbol`—ao contrário de uma estrutura complexa, composta por vários elementos distintos, como uma lista. +`Atom`:: Um elemento sintático simples, tal como um número ou um `Symbol`—ao +contrário de uma estrutura composta, formada por vários elementos distintos, +como uma lista. -`Expression`:: Os componentes básicos de programas Scheme são expressões feitas de átomos e listas, possivelmente aninhados. +`Expression`:: Os componentes básicos de programas Scheme são expressões feitas +de átomos e listas, possivelmente aninhados. [[lispy_parser_sec]] ==== O parser -O parser (_analisador sintático_)((("lis.py interpreter", "parser", id="lispyparser18"))) de Norvig tem 36 linhas de código que -exibem o poder de Python aplicado ao tratamento da sintaxe recursiva simples das expressões-S—sem strings, -comentários, macros e outros recursos que tornam a análise sintática do Scheme padrão mais complicada (<>). +O parser (_analisador sintático_)((("lis.py interpreter", "parser", +id="lispyparser18"))) de Norvig tem 36 linhas de código que exibem o poder de +Python aplicado ao tratamento da sintaxe recursiva simples das expressões-S—sem +strings, comentários, macros e outros recursos que tornam a análise sintática do +Scheme padrão mais complicada (<>). [[lis_parser_ex]] .lis.py: as principais funções do analisador @@ -465,17 +709,18 @@ include::../code/18-with-match/lispy/py3.10/lis.py[lines=27..36] ---- ==== -A principal função desse grupo é `parse`, que recebe uma expressão-S em forma de `str` e devolve um objeto `Expression`, como definido no <>: -um `Atom` ou uma `list` que pode conter mais átomos e listas aninhadas. +A principal função deste grupo é `parse`, que recebe uma expressão-S em forma de +`str` e devolve um objeto `Expression`, como definido no <>: um +`Atom` ou uma `list` que pode conter mais átomos e listas aninhadas. -Norvig usa um truque elegante em `tokenize`: -ele acrescenta espaços antes e depois de cada parênteses na entrada, e então a recorta, -resultando em uma lista de símbolos sintáticos (_tokens_) com `'('` e `')'` como símbolos separados -Esse atalho funciona porque não há um tipo string no pequeno Scheme de _lis.py_, então todo `'('` ou `')'` é um delimitador de expressão. -O código recursivo do analisador está em `read_from_tokens`, -uma função de 14 linhas que você pode ler no repositório -https://fpy.li/18-17[_fluentpython/example-code-2e_]. -Vou pular isso, pois quero me concentrar em outras partes do interpretador. +Norvig usa um truque elegante em `tokenize`: ele acrescenta espaços antes e +depois de cada parênteses na entrada, e então a particiona com `split`, resultando em uma lista +de _tokens_ (símbolos sintáticos) com `'('` e `')'` como itens separados. Este +atalho funciona porque não há um tipo string no pequeno Scheme de _lis.py_, +então todo `'('` ou `')'` é um delimitador de expressão. O código recursivo do +analisador está em `read_from_tokens`, uma função de 14 linhas que você pode ler +no repositório https://fpy.li/18-17[_fluentpython/example-code-2e_]. Vou pular +isso, pois quero me concentrar em outras partes do interpretador. Aqui estão alguns doctests estraídos do https://fpy.li/18-18[_lispy/py3.10/examples_test.py_]: @@ -484,25 +729,45 @@ Aqui estão alguns doctests estraídos do https://fpy.li/18-18[_lispy/py3.10/exa include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=PARSE] ---- -As regras de avaliação para esse subconjunto do Scheme são simples: +As regras de avaliação deste subconjunto do Scheme são simples: + +. Um símbolo sintático que se pareça com um número é tratado como um `float` ou +um `int`. -. Um símbolo sintático que se pareça com um número é tratado como um `float` ou um `int`. -. Todo o resto que não seja um `'('` ou um `')'` é considerado um `Symbol`—uma `str`, a ser usado como um identificador. Isso inclui texto no código-fonte como `+`, `set!`, e `make-counter`, que são identificadores válidos em Scheme, mas não em Python. -. Expressões dentro de `'('` e `')'` são avaliadas recursivamente como listas contendo átomos ou listas aninhadas que podem conter átomos ou mais listas aninhadas. +. Todo o resto que não seja um `'('` ou um `')'` é considerado um `Symbol`—uma +`str`, a ser usado como um identificador. Isso inclui texto no código-fonte como +`{plus}`, `set!`, e `make-counter`, que são três identificadores válidos em Scheme, +mas não em Python. -Usando a terminologia do interpretador Python, a saída de `parse` é uma AST (_Abstract Syntax Tree_—Árvore Sintática Abstrata): -uma representação conveniente de um programa Scheme como listas aninhadas formando uma estrutura similar a uma árvore, onde a lista mais externa é o tronco, listas internas são os galhos, e os átomos são as folhas (<>).((("", startref="lispyparser18"))) +. Expressões dentro de `'('` e `')'` são avaliadas recursivamente como listas +contendo átomos ou listas aninhadas que podem conter átomos ou mais listas +aninhadas. + +Usando a terminologia do interpretador Python, a saída de `parse` é uma AST +(_Abstract Syntax Tree_—Árvore Sintática Abstrata): uma representação +conveniente de um programa Scheme como listas aninhadas formando uma estrutura +similar a uma árvore, onde a lista mais externa é o tronco, listas internas são +os galhos, e os átomos são as folhas (<>).((("", +startref="lispyparser18"))) [role="width-80"] [[ast_fig]] .Uma expressão `lambda` de Scheme, representada como código-fonte (sintaxe concreta de expressões-S), como uma árvore, e como uma sequência de objetos Python (sintaxe abstrata). -image::../images/flpy_1801.png["Código Scheme, im diagram de árvore e objetos Python"] +image::../images/flpy_1801.png[align="center",pdfwidth=10cm] + +Agora veremos como é construído o ambiente (_environment_) que fornece +as definições usadas pelos programas dos usuários, +semelhante ao módulo `builtins` do Python. [[lispy_environ_sec]] ==== O ambiente -A((("lis.py interpreter", "Environment class", id="lispyenv18"))) classe `Environment` estende `collections.ChainMap`, acrescentando o método `change`, para atualizar um valor dentro de um dos dicts encadeados que as instâncias de `ChainMap` mantém em uma lista de mapeamentos: o atributo `self.maps`. -O método `change` é necessário para suportar a forma `(set! …)` do Scheme, descrita mais tarde; veja o <>. +A((("lis.py interpreter", "Environment class", id="lispyenv18"))) classe +`Environment` estende `collections.ChainMap`, acrescentando o método `change`, +para atualizar um valor dentro de um dos dicts encadeados que as instâncias de +`ChainMap` mantém em uma lista de mapeamentos no atributo `self.maps`. O método +`change` é necessário para suportar a instrução `(set! …)` do Scheme, descrita mais +tarde. [[environment_class_ex]] @@ -514,7 +779,12 @@ include::../code/18-with-match/lispy/py3.10/lis.py[tags=ENV_CLASS] ---- ==== -Observe que o método `change` só atualiza chaves existentes.footnote:[O comentário `++# type: ignore[index]++` está ali por causa do issue https://fpy.li/18-19[#6042] no _typeshed_, que segue sem resolução quando esse capítulo está sendo revisado. `ChainMap` é anotado como `MutableMapping`, mas a dica de tipo no atributo `maps` diz que ele é uma lista de `Mapping`, indiretamente tornando todo o `ChainMap` imutável até onde o Mypy entende.] +Observe que o método `change` só atualiza chaves existentes.footnote:[O +comentário `++# type: ignore[index]++` está ali por causa do issue +https://fpy.li/18-19[#6042] no _typeshed_, que segue sem resolução quando esse +capítulo está sendo revisado. `ChainMap` é anotado como `MutableMapping`, mas a +dica de tipo no atributo `maps` diz que ele é uma lista de `Mapping`, +indiretamente tornando todo o `ChainMap` imutável até onde o Mypy entende.] Tentar mudar uma chave não encontrada causa um `KeyError`. Esse doctest mostra como `Environment` funciona: @@ -523,11 +793,20 @@ Esse doctest mostra como `Environment` funciona: ---- include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=ENVIRONMENT] ---- -<1> Ao ler os valores, `Environment` funciona como `ChainMap`: as chaves são procuradas nos mapeamentos aninhados da esquerda para a direita. Por isso o valor de `a` no `outer_env` é encoberto pelo valor em `inner_env`. -<2> Atribuir com `[]` sobrescreve ou insere novos itens, mas sempre no primeiro mapeamento, `inner_env` nesse exemplo. -<3> `env.change('b', 333)` busca a chave `b` e atribui a ela um novo valor no mesmo lugar, no `outer_env` -A seguir temos a função `standard_env()`, que constrói e devolve um `Environment` carregado com funções pré-definidas, similar ao módulo `+__builtins__+` de Python, que está sempre disponível (<>). +<1> Ao ler os valores, `Environment` funciona como `ChainMap`: as chaves são +procuradas nos mapeamentos aninhados da esquerda para a direita. Por isso o +valor de `a` no `outer_env` é encoberto pelo valor em `inner_env`. + +<2> Atribuir com `[]` sobrescreve ou insere novos itens, mas sempre no primeiro +mapeamento, `inner_env` nesse exemplo. + +<3> `env.change('b', 333)` busca a chave `b` e atribui a ela um novo valor no +mesmo lugar, no `outer_env` + +A seguir temos a função `standard_env()`, que constrói e devolve um +`Environment` carregado com funções pré-definidas, similar ao módulo +`+__builtins__+` de Python, que está sempre disponível (<>). [[lis_std_env_ex]] .lis.py: `standard_env()` constrói e devolve o ambiente global @@ -535,26 +814,37 @@ A seguir temos a função `standard_env()`, que constrói e devolve um `Environm [source, python] ---- include::../code/18-with-match/lispy/py3.10/lis.py[lines=77..85] - # omitted here: more operator definitions + # omitidas: várias definições de funções include::../code/18-with-match/lispy/py3.10/lis.py[lines=92..97] - # omitted here: more function definitions + # omitidas: várias definições de funções include::../code/18-with-match/lispy/py3.10/lis.py[lines=111..116] ---- ==== Resumindo, o mapeamento `env` é carregado com: -* Todas as funções do módulo `math` de Python -* Operadores selecionados do módulo `op` de Python -* Funções simples porém poderosas construídas com o `lambda` de Python -* Estruturas e entidades embutidas de Python, ou renomeadas, como `callable` para `procedure?`, ou mapeadas diretamente, como `round` +* Todas as funções do módulo `math` de Python; +* Operadores selecionados do módulo `op` de Python; +* Funções simples porém poderosas construídas com o `lambda` de Python; +* Estruturas e entidades embutidas de Python, algumas renomeadas, como `callable` +para `procedure?`, ou mapeadas diretamente, como `round`. ==== O REPL -O((("lis.py interpreter", "REPL (read-eval-print-loop)"))) REPL (read-eval-print-loop, _loop-lê-calcula-imprime_ ) de Norvig é fácil de entender mas não é amigável ao usuário (veja o <>). -Se nenhum argumento de linha de comando é passado a _lis.py_, -a função `repl()` é invocada por `main()`—definida no final do módulo. -No prompt de `lis.py>`, devemos digitar expressões corretas e completas; se esquecemos de fechar um só parênteses, _lis.py_ se encerra.footnote:[Enquanto estudava o _lis.py_ e o _lispy.py_ de Norvig, comecei uma versão chamada https://fpy.li/18-20[_mylis_], que acrescenta alguns recursos, incluindo um REPL que aceita expressões-S parciais e espera a continuação, como o REPL de Python sabe que não terminamos e apresenta um prompt secundário (`\...`) até entrarmos uma expressão ou instrução completa, que possa ser analisada e avaliada. O _mylis_ também trata alguns erros de forma graciosa, mas ele ainda é fácil de quebrar. Não é nem de longe tão robusto quanto o REPL de Python.] +O código do ((("lis.py interpreter", "REPL (read-eval-print-loop)"))) REPL +(read-eval-print-loop, _laço-lê-calcula-imprime_ ) de Norvig é fácil de entender +mas não é amigável para usuário (veja o <>). Se nenhum argumento de +linha de comando é passado a _lis.py_, a função `repl()` é invocada por +`main()`—definida no final do módulo. No prompt de `lis.py>`, devemos digitar +expressões corretas e completas; se esquecemos de fechar um só parênteses, +_lis.py_ se encerra.footnote:[Enquanto estudava o _lis.py_ e o _lispy.py_ de +Norvig, comecei uma versão chamada https://fpy.li/18-20[_mylis_], que acrescenta +alguns recursos, incluindo um REPL que aceita expressões-S parciais e espera a +continuação, como o REPL do Python qie sabe quando não terminamos uma instrução e apresenta um prompt +secundário (`\...`) até entrarmos uma instrução completa, que possa +ser analisada e avaliada. O _mylis_ também trata alguns erros de forma graciosa, +mas ele ainda é fácil de quebrar. Não é nem de longe tão robusto quanto o REPL +do Python.] [role="pagebreak-before less_space"] [[ex_lispy_repl]] @@ -570,25 +860,29 @@ Segue uma breve explicação sobre essas duas funções: `repl(prompt: str = 'lis.py> ') -> NoReturn`:: Chama `standard_env()` para provisionar as funções embutidas para o ambiente global, - então entra em um loop infinito, lendo e avaliando cada linha de entrada, + então entra em um laço infinito, lendo e avaliando cada linha de entrada, calculando-a no ambiente global, e exibindo o resultado—a menos que seja `None`. O `global_env` pode ser modificado por `evaluate`. Por exemplo, quando o usuário define uma nova variável global ou uma função nomeada, - ela é armazenada no primeiro mapeamento do ambiente—o `dict` vazio na chamada ao construtor de `Environment` na primeira linha de `repl`. + ela é armazenada no primeiro mapeamento do ambiente—o `dict` vazio + na chamada ao construtor de `Environment` na primeira linha de `repl`. `lispstr(exp: object) -> str`:: A função inversa de `parse`: - dado um objeto Python representando uma expressão, - `lispstr` devolve o código-fonte para ela. - Por exemplo, dado `['+', 2, 3]`, o resultado é `'(+ 2 3)'`. + dado um objeto Python representando a AST de uma expressão, + `lispstr` devolve o código-fonte correspondete. + Por exemplo, dado `['{plus}', 2, 3]`, o resultado é `'({plus} 2 3)'`. ==== O avaliador de expressões -Agora((("lis.py interpreter", "evaluate function", id="lispyeval18"))) podemos apreciar a beleza do avaliador de expressões de Norvig—tornado um pouco mais bonito com `match/case`. -A função `evaluate` no <> recebe uma `Expression` (construída por `parse`) e um `Environment`. +Agora((("lis.py interpreter", "evaluate function", id="lispyeval18"))) podemos +apreciar a beleza do avaliador de expressões de Norvig—ainda mais elegante +com `match/case`. A função `evaluate` no <> recebe uma +`Expression` (construída por `parse`) e um `Environment`. -O corpo de `evaluate` é composto por uma única instrução `match` com uma expressão `exp` como sujeito. -Os padrões de `case` expressam a sintaxe e a semântica do Scheme com uma clareza impressionante. +O corpo de `evaluate` é composto por uma única instrução `match` com uma +expressão `exp` como sujeito. Os padrões de `case` expressam a sintaxe e a +semântica do Scheme com uma clareza impressionante. [[ex_evaluate_match]] .`evaluate` recebe uma expressão e calcula seu valor @@ -600,9 +894,9 @@ include::../code/18-with-match/lispy/py3.10/lis.py[tags=EVALUATE] ==== Vamos estudar cada cláusula `case` e o que cada uma faz. -Em algumas ocasiões eu acrescentei comentários, mostrando uma expressão-S que casaria com padrão quando transformado em uma lista de Python. -Os doctests extraídos de -https://fpy.li/18-21[_examples_test.py_] demonstram cada `case`. +Em algumas ocasiões eu acrescentei comentários, mostrando uma expressão-S que +casaria com padrão quando transformado em uma lista de Python. Os doctests +extraídos de https://fpy.li/18-21[_examples_test.py_] demonstram cada `case`. [[eval_atom_sec]] @@ -650,7 +944,8 @@ include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_SYMBOL] ===== (quote …) -A forma especial `quote` trata átomos e listas como dados em vez de expressões a serem avaliadas. +A forma especial `quote` trata átomos e listas como dados em vez de expressões +a serem avaliadas. [source, python] ---- @@ -676,27 +971,34 @@ Sem `quote`, cada expressão no teste geraria um erro: [role="pagebreak-before less_space"] * `no-such-name` seria buscado no ambiente, gerando um `KeyError` -* `(99 bottles of beer)` não pode ser avaliado, pois o número 99 não é um `Symbol` nomeando uma forma especial, um operador ou uma função +* `(99 bottles of beer)` não pode ser avaliado, pois o número 99 não é +um `Symbol` nomeando uma forma especial, um operador ou uma função * `(/ 10 0)` geraria um `ZeroDivisionError` .Por que linguagens têm palavras reservadas? **** + Apesar((("reserved keywords")))((("keywords", "reserved keywords"))) de ser simples, `quote` não pode ser implementada como uma função em Scheme. Seu poder especial é impedir que o interpretador avalie `(f 10)` na expressão `(quote (f 10))`: o resultado é apenas uma lista com um `Symbol` e um `int`. Por outro lado, em uma chamada de função como `(abs (f 10))`, o interpretador primeiro calcula o resultado de `(f 10)` antes de invocar `abs`. -Por isso `quote` é uma palavra reservada: ela precisa ser tratada como uma forma especial. +Por isso `quote` é uma palavra reservada: +ela precisa ser tratada de uma forma especial. De modo geral, palavras reservadas são necessárias para: -* Introduzir regras especiais de avaliação, como `quote` e `lambda`—que não avaliam nenhuma de suas sub-expressões -* Mudar o fluxo de controle, como em `if` e chamadas de função—que também tem regras especiais de avaliação +* Introduzir regras especiais de avaliação, +como `quote` e `lambda`—que não avaliam nenhuma de suas sub-expressões +* Mudar o fluxo de controle, como em `if` e chamadas de função—que +também tem regras especiais de avaliação * Para gerenciar o ambiente, como em `define` e `set` -Por isso também Python, e linguagens de programação em geral, precisam de palavras reservadas. -Pense em `def`, `if`, `yield`, `import`, `del`, e o que elas fazem em Python. +Por isso também Python, e linguagens de programação em geral, +precisam de palavras reservadas. +Pense em `def`, `if`, `yield`, `import`, `del`, +e o que elas fazem em Python. **** @@ -713,7 +1015,8 @@ Pense em `def`, `if`, `yield`, `import`, `del`, e o que elas fazem em Python. ---- Padrão::: - Lista começando com `'if'` seguida de três expressões: `test`, `consequence`, e `alternative`. + Lista começando com `'if'` seguida de três expressões: + `test`, `consequence`, e `alternative`. Ação::: Avalia `test`: @@ -727,14 +1030,16 @@ Exemplos::: include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_IF] ---- -Os ramos `consequence` e `alternative` devem ser expressões simples. -Se mais de uma expressão for necessária em um ramo, você pode combiná-las com `(begin exp1 exp2…)`, fornecida como uma função em _lis.py_—veja o <>. +Os ramos `consequence` e `alternative` devem ser expressões simples. Se mais de +uma expressão for necessária em um ramo, você pode combiná-las com `(begin exp1 +exp2…)`, fornecida como uma função em _lis.py_—veja o <>. ===== (lambda …) A forma `lambda` do Scheme define funções anônimas. Ela não sofre das limitações da `lambda` de Python: -qualquer função que pode ser escrita em Scheme pode ser escrita usando a sintaxe `(lambda …)`. +qualquer função que pode ser escrita em Scheme pode +ser escrita usando a sintaxe `(lambda …)`. [source, python] ---- @@ -746,10 +1051,12 @@ qualquer função que pode ser escrita em Scheme pode ser escrita usando a sinta Padrão::: Lista começando com `'lambda'`, seguida de: * Lista de zero ou mais nomes de parâmetros - * Uma ou mais expressões coletadas em `body` (a expressão guarda assegura que `body` não é vazio). + * Uma ou mais expressões coletadas em `body` + (a expressão guarda `if body` garante que `body` não pode ser vazio). Ação::: - Cria e devolve uma nova instância de `Procedure` com os nomes de parâmetros, a lista de expressões como o corpo da função, e o ambiente atual. + Cria e devolve uma nova instância de `Procedure` com os nomes de parâmetros, + a lista de expressões como o corpo da função, e o ambiente atual. Exemplo::: + @@ -758,16 +1065,16 @@ Exemplo::: include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_LAMBDA] ---- -A classe `Procedure` implementa o conceito de uma closure (_clausura_): +A classe `Procedure` implementa o uma clausura (_closure_): um objeto invocável contendo nomes de parâmetros, um corpo de função, -e uma referência ao ambiente no qual a `Procedure` está sendo instanciada. +e uma referência ao ambiente no qual a `Procedure` está sendo declarada. Vamos estudar o código de `Procedure` daqui a pouco. [role="pagebreak-before less_space"] ===== (define …) -A palavra reservada `define` é usada de duas formas sintáticas diferentes. +A palavra reservada `define` é usada em duas formas sintáticas diferentes. A mais simples é: [source, python] @@ -781,7 +1088,8 @@ Padrão::: Lista começando com `'define'`, seguido de um `Symbol` e uma expressão. Ação::: - Avalia a expressão e coloca o valor resultante em `env`, usando `name` como chave. + Avalia a expressão e coloca o valor resultante em `env`, + usando `name` como chave. Exemplo::: + @@ -790,9 +1098,11 @@ Exemplo::: include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_DEFINE] ---- -O doctest para esse `case` cria um `global_env`, para podermos verificar que `evaluate` coloca `answer` dentro daquele `Environment`. +O doctest para esse `case` cria um `global_env`, para podermos verificar que +`evaluate` coloca `answer` dentro daquele `Environment`. -Podemos usar primeira forma de `define` para criar variáveis ou para vincular nomes a funções anônimas, usando `(lambda …)` como o `value_exp`. +Podemos usar primeira forma de `define` para criar variáveis ou para vincular +nomes a funções anônimas, usando `(lambda …)` como o `value_exp`. A segunda forma de `define` é um atalho para definir funções nomeadas. @@ -807,14 +1117,18 @@ A segunda forma de `define` é um atalho para definir funções nomeadas. Padrão::: Lista começando com `'define'`, seguida de: - * Uma lista começando com um `Symbol(name)`, seguida de zero ou mais itens agrupados em uma lista chamada `parms`. - * Uma ou mais expressões agrupadas em `body` (a expressão guarda garante que `body` não esteja vazio) + * Uma lista começando com um `Symbol(name)`, + seguida de zero ou mais itens agrupados em uma lista chamada `parms`. + * Uma ou mais expressões agrupadas em `body` + (a expressão guarda garante que `body` não esteja vazio) Ação::: - * Cria uma nova instância de `Procedure` com os nomes dos parâmetros, a lista de expressões como o corpo, e o ambiente atual. + * Cria uma nova instância de `Procedure` com os nomes dos parâmetros, + o corpo como uma lista de expressões, e o ambiente atual. * Insere a `Procedure` em `env`, usando `name` como chave. -O doctest no <> define e coloca no `global_env` uma função chamada `%`, que calcula uma porcentagem. +O doctest no <> define e coloca no `global_env` +uma função chamada `%`, que calcula uma porcentagem. [[test_case_defun]] .Definindo uma função chamada `%`, que calcula uma porcentagem @@ -825,20 +1139,27 @@ include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_DEFUN] ---- ==== -// O doctet novamente cria um `global_env`. +Após invocar `evaluate`, verificamos que `%` está vinculada a uma `Procedure` que +recebe dois argumentos numéricos e devolve uma porcentagem. -Após chamar `evaluate`, verificamos que `%` está vinculada a uma `Procedure` que recebe dois argumentos numéricos e devolve uma porcentagem. - -O padrão para o segundo `define` não obriga os itens em `parms` a serem todos instâncias de `Symbol`. -Eu teria que verificar isso antes de criar a `Procedure`, mas não o fiz—para manter o código aqui tão fácil de acompanhar quanto o de Norvig. +O padrão para o segundo `define` não obriga os itens em `parms` a serem todos +instâncias de `Symbol`. Eu teria que verificar isso antes de criar a +`Procedure`, mas não o fiz—para manter o código aqui tão fácil de acompanhar +quanto o de Norvig. ===== (set! …) -A forma `set!` muda o valor de uma variável previamente definida.footnote:[A atribuição é um dos primeiros recursos ensinados em muitos tutoriais de programacão, mas `set!` só aparece na página 220 do mais conhecido livro de Scheme, -https://fpy.li/18-22[_Structure and Interpretation of Computer Programs_ (_A Estrutura e a Interpretação de Programas de Computador_), 2nd ed.,] de Abelson et al. (MIT Press), também conhecido como SICP ou "Wizard Book" (_Livro do Mago_). -Programas em estilo funcional podem nos levar muito longe sem as mudanças de estado típicas da programação imperativa e da programação orientada a objetos.] +A forma `set!` muda o valor de uma variável previamente definida.footnote:[A +atribuição é um dos primeiros recursos ensinados em muitos tutoriais de +programacão, mas `set!` só aparece na página 220 do mais conhecido livro de +Scheme, https://fpy.li/18-22[_Structure and Interpretation of Computer Programs_ +(_A Estrutura e a Interpretação de Programas de Computador_), 2nd ed.,] de +Abelson et al. (MIT Press), também conhecido como SICP ou _Wizard Book_ (Livro +do Mago). Programas em estilo funcional podem nos levar muito longe sem as +mudanças de estado típicas da programação imperativa e da programação orientada +a objetos.] [source, python] ---- @@ -860,17 +1181,26 @@ esse interpretador poderia usar apenas o `ChainMap` de Python para implementar ` sem precisar da nossa classe `Environment`. [role="pagebreak-before less_space"] -.O nonlocal de Python e o set! do Scheme tratam da mesma questão +.O `nonlocal` de Python e o `set!` do Scheme tratam da mesma questão **** -O((("nonlocal keyword")))((("keywords", "nonlocal keyword"))) uso da forma `set!` está relacionado ao uso da palavra reservada `nonlocal` em Python: -declarar `nonlocal x` permite a `x = 10` atualizar uma variável `x` anteriormente definida fora do escopo local. -Sem a declaração `nonlocal x`, `x = 10` vai sempre criar uma variável local em Python, como vimos na <>. -De forma similar, `(set! x 10)` atualiza um `x` anteriormente definido que pode estar fora do ambiente local da função. -Por outro lado, a variável `x` em `(define x 10)` é sempre uma variável local, criada ou atualizada no ambiente local. +O((("nonlocal keyword")))((("keywords", "nonlocal keyword"))) uso da forma +`set!` está relacionado ao uso da instrução `nonlocal` em Python: +declarar `nonlocal x` permite a `x = 10` atualizar uma variável `x` +anteriormente definida fora do escopo local. Sem a declaração `nonlocal x`, `x = +10` vai sempre criar uma variável local em Python, como vimos na +<>. + +De forma similar, `(set! x 10)` atualiza um `x` anteriormente definido que pode +estar fora do ambiente local da função. Por outro lado, a variável `x` em +`(define x 10)` sará sempre uma variável local, criada ou atualizada no ambiente +local. -Ambos, `nonlocal` e `(set! …)`, são necessários para atualizar o estados do programas mantidos em variáveis dentro de uma clausura (_closure_). O <> demonstrou o uso de `nonlocal` para implementar uma função que calcula uma média contínua, mantendo itens `count` e `total` em uma clausura. -Aqui está a mesma ideia, escrita no subconjunto de Scheme de _lis.py_: +Ambos, `nonlocal` e `(set! …)`, são necessários para atualizar o estado do +programa mantido em variáveis dentro de uma clausura. O +<> demonstrou o uso de `nonlocal` para implementar uma função +que calcula uma média contínua, mantendo itens `count` e `total` em uma +clausura. Aqui está a mesma ideia, escrita no subconjunto de Scheme de _lis.py_: [source, scheme] ---- @@ -888,7 +1218,8 @@ Aqui está a mesma ideia, escrita no subconjunto de Scheme de _lis.py_: (avg 11) # <3> (avg 15) # <4> ---- -<1> Cria uma nova clausura com a função interna definida por `lambda` e as variáveis `count` e `total`, inicialziadas com 0; vincula a clausura a `avg`. +<1> Cria uma nova clausura com a função interna definida por `lambda` +e as variáveis `count` e `total`, inicializadas com 0; vincula a clausura a `avg`. <2> Devolve 10.0. <3> Devolve 10.5. <4> Devolve 12.0. @@ -917,16 +1248,18 @@ Padrão::: Lista com um ou mais itens. A expressão guarda garante que `func_exp` não é um de -`['quote', 'if', 'define', 'lambda', 'set!']`—listados logo antes de `evaluate` no <>. +`['quote', 'if', 'define', 'lambda', 'set!']`—listados +logo antes de `evaluate` no <>. O padrão casa com qualquer lista com uma ou mais expressões, -vinculando a primeira expressão a `func_exp` e o restante a `args` como uma lista, que pode ser vazia. +vinculando a primeira expressão a `func_exp` e +o restante a `args` como uma lista, que pode ser vazia. -- Ação::: - * Avaliar `func_exp` para obter uma `proc` da função. + * Avaliar `func_exp` para obter um `Procedure` que representa a função. * Avaliar cada item em `args` para criar uma lista de valores dos argumentos. - * Chamar `proc` com os valores como argumentos separados, devolvendo o resultado. + * Invocar `proc` com os valores como argumentos separados, devolvendo o resultado. Exemplo::: + @@ -941,16 +1274,16 @@ Os argumentos passados a `%` são expressões aritméticas, para enfatizar que eles são avaliados antes da função ser chamada. A expressão guarda nesse `case` é necessária porque `[func_exp, *args]` -casa com qualquer sequência sujeito com um ou mais itens. +casa com qualquer sequência sujeito que tenha um ou mais itens. Entretanto, se `func_exp` é uma palavra reservada e o sujeito não casou com nenhum dos `case` anteriores, -então isso é de fato um erro de sintaxe. +então isso é um erro de sintaxe, como `(define x)`. ===== Capturar erros de sintaxe Se o sujeito `exp` não casa com nenhum dos `case` anteriores, -o `case` "pega tudo" gera um `SyntaxError`: +o `case` "coringa" gera um `SyntaxError`: [source, python] ---- @@ -965,32 +1298,39 @@ Aqui está um exemplo de um `(lambda …)` malformado, identificado como um `Syn include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_SYNTAX_ERROR] ---- -Se o `case` para chamada de função não tivesse aquela expressão guarda rejeitando palavras reservadas, a expressão `(lambda is not like this)` teria sido tratada como uma chamada de função, -que geraria um `KeyError`, pois `'lambda'` não é parte do ambiente—da mesma forma que `lambda` em Python não é uma função embutida.((("", startref="lispyeval18"))) +Se o `case` para chamada de função não tivesse aquela expressão guarda +rejeitando palavras reservadas, a expressão `(lambda is not like this)` teria +sido tratada como uma chamada de função, que geraria um `KeyError`, pois +`'lambda'` faz parte do ambiente—assim como `lambda` em Python não é +o nome de uma função embutida.((("", startref="lispyeval18"))) ==== Procedure: uma classe que implementa uma clausura -A((("decorators and closures", "closures in lis.py")))((("lis.py interpreter", "Procedure class", id="lispyproced18"))) classe `Procedure` poderia muito bem se chamar `Closure`, porque é isso que ela representa: -uma definição de função junto com um ambiente. -A definição de função inclui o nome dos parâmetros e as expressões que compõe o corpo da função. -O((("free variables"))) ambiente é usado quando a função é chamada, para fornecer os valores das _variáveis livres_: variáveis que aparecem no corpo da função mas não são parâmetros, variáveis locais ou variáveis globais. -Vimos os conceitos de _clausura_ e de _variáveis livres_ na <>. +A((("decorators and closures", "closures in lis.py")))((("lis.py interpreter", +"Procedure class", id="lispyproced18"))) classe `Procedure` poderia muito bem se +chamar `Closure`, porque é isso que ela representa: uma definição de função +no contexto de um ambiente. A definição de função inclui o nome dos parâmetros e as +expressões que compõe o corpo da função. O((("free variables"))) ambiente é +usado quando a função é chamada, para fornecer os valores das _variáveis +livres_: variáveis que aparecem no corpo da função mas não são parâmetros, +variáveis locais ou variáveis globais. Vimos os conceitos de _clausura_ e de +_variáveis livres_ na <>. -Aprendemos como usar clausuras em Python, -mas agora podemos mergulhar mais fundo e ver como uma clausura é implementada em _lis.py_: +Aprendemos como usar clausuras em Python, mas agora podemos mergulhar mais fundo +e ver como uma clausura é implementada em _lis.py_: [source, python] ---- include::../code/18-with-match/lispy/py3.10/lis.py[tags=PROCEDURE] ---- -<1> Chamada quando uma função é definida pelas formas `lambda` ou `define`. +<1> `+__init__+` é invocado quando uma função é definida pelas instruções `lambda` ou `define`. <2> Salva os nomes dos parâmetros, as expressões no corpo e o ambiente, para uso posterior. - -<3> Chamada por `proc(*values)` na última linha da cláusula `case [func_exp, *args]`. +<3> `+__call__+` é invocado por `proc(*values)` na última linha da cláusula `case [func_exp, *args]`. <4> Cria `local_env`, mapeando `self.parms` como nomes de variáveis locais e os `args` passados como valores. -<5> Cria um novo `env` combinado, colocando `local_env` primeiro e então `self.env`—o ambiente que foi salvo quando a função foi definida. +<5> Cria um novo `env` combinado, colocando `local_env` primeiro e então +`self.env`—o ambiente que foi salvo quando a função foi definida. <6> Itera sobre cada expressão em `self.body`, avaliando-as no `env` combinado. <7> Devolve o resultado da última expressão avaliada. @@ -1002,15 +1342,17 @@ Meus objetivos aqui eram compartilhar com vocês a beleza do pequeno interpretad explicar melhor como as clausuras funcionam, e mostrar como `match/case` foi uma ótima adição ao Python. -Para fechar essa seção estendida sobre _pattern matching_, -vamos formalizar o conceito de um OR-pattern (_padrão-OU_).((("", startref="lispyproced18"))) +Para fechar essa seção estendida sobre casamento de padrões, +vamos formalizar o conceito de um padrão-OU (_OR-pattern_).((("", startref="lispyproced18"))) ==== Using padrões-OU -Uma((("lis.py interpreter", "OR-patterns")))((("OR-patterns")))((("ǀ (pipe) operator")))((("pipe (ǀ) operator"))) série de padrões separados por `|` formam um -https://fpy.li/18-25[_OR-pattern_] (EN): -ele tem êxito se qualquer dos sub-padrões tiver êxito. -O padrão em <> é um OR-pattern: +Uma((("lis.py interpreter", "OR-patterns")))((("OR-patterns")))((("ǀ (pipe) operator")))((("pipe (ǀ) operator"))) +série de padrões separados por `|` formam um +padrão-OU (documentado em +https://fpy.li/18-25[_OR-patterns_]): +ele casa se qualquer dos sub-padrões casar. +Este é um padrão-OU que já vimos na <>: [source, python] ---- @@ -1018,25 +1360,30 @@ O padrão em <> é um OR-pattern: return x ---- -Todos os sub-padrões em um OR-pattern devem usar as mesmas variáveis. -Essa restrição é necessária para garantir que -as variáveis estejam disponíveis para a expressão de guarda e para o corpo do `case`, -independente de qual sub-padrão tenha sido bem sucedido. +Todos os sub-padrões em um padrão-OU devem usar as mesmas variáveis. +Esta restrição é necessária para garantir que +as variáveis estejam disponíveis na expressão de guarda e no corpo do `case`, +independente de qual sub-padrão tenha casado. [WARNING] ==== No contexto de uma cláusula `case`, o operador `|` tem um significado especial. Ele não aciona o método especial `+__or__+`, que manipula expressões como `a | b` em outros contextos, -onde ele é sobrecarregado para realizar operações como união de conjuntos ou disjunção binária com inteiros (o "ou binário"), dependendo dos operandos. +onde ele é sobrecarregado para realizar operações como união de conjuntos ou +disjunção binária com inteiros (o "ou binário"), dependendo dos operandos. ==== -Um OR-pattern não está limitado a aparecer no nível superior de um padrão. -`|` pode também ser usado em sub-padrões. -Por exemplo, se quiséssemos que o _lis.py_ aceitasse a letra grega λ (lambda)footnote:[O nome Unicode oficial para λ (U+03BB) é GREEK SMALL LETTER LAMDA. Isso não é um erro ortográfico: o caractere é chamado "lamda" sem o "b" no banco de dados do Unicode. De acordo com o artigo https://fpy.li/18-26["Lambda"] (EN) da Wikipedia em inglês, -o Unicode Consortium adotou essa forma em função de -"preferências expressas pela Autoridade Nacional Grega."] -além da palavra reservada `lambda`, poderíamos reescrever o padrão assim: +Um padrão-OU não está limitado a aparecer no nível superior de um padrão. +O símbolo `|` pode também ser usado em sub-padrões. +Por exemplo, se quiséssemos que o _lis.py_ +aceitasse a letra grega λ (lambda)footnote:[O nome para λ +(U+03BB) no Unicode é GREEK SMALL LETTER LAMDA. Isso não é um erro ortográfico: o caractere +é chamado "lamda" sem o "b" no banco de dados do Unicode. De acordo com o artigo +https://fpy.li/18-26["Lambda"] da Wikipedia em inglês, o Unicode Consortium +adotou essa ortografia em função de "preferências expressas pela Autoridade Nacional +Grega."] além da palavra reservada `lambda`, poderíamos reescrever o padrão +assim: [source, python] ---- @@ -1046,48 +1393,60 @@ além da palavra reservada `lambda`, poderíamos reescrever o padrão assim: ---- Agora podemos passar para o terceiro e último assunto deste capítulo: -lugares incomuns onde a cláusula `else` pode aparecer no Python.((("", startref="WMEBlispy18")))((("", startref="PMlispy18"))) +lugares incomuns onde a cláusula `else` pode aparecer no Python.((("", +startref="WMEBlispy18")))((("", startref="PMlispy18"))) -=== Faça isso, então aquilo: os blocos else além do if +=== Faça isso, então aquilo: blocos `else` além do `if` -Isso((("with, match, and else blocks", "else clause", id="WMEBelse18")))((("else blocks", id="else18"))) não é segredo, -mas é um recurso pouco conhecido em Python: -a cláusula `else` pode ser usada não apenas com instruções `if`, mas também com as instruções `for`, `while`, e `try`. +Não((("with, match, and else blocks", "else clause", id="WMEBelse18")))((("else +blocks", id="else18"))) é segredo, mas é um recurso pouco conhecido em +Python: a cláusula `else` pode ser usada não apenas com instruções `if`, mas +também com as instruções `for`, `while`, e `try`. -A semântica para `for/else`, `while/else`, e `try/else` é semelhante, mas é muito diferente do `if/else`. -No início, a palavra `else` na verdade atrapalhou meu entendimento desses recursos, mas no fim acabei me acostumando. +A semântica para `for/else`, `while/else`, e `try/else` é semelhante, mas é +muito diferente do `if/else`. No início, a palavra `else` atrapalhou +meu entendimento destes cláusulas, mas no fim acabei me acostumando. Aqui estão as regras: -`for`:: O bloco `else` será executado apenas se e quando o loop `for` rodar até o fim (isto é, não rodará se o `for` for interrompido com um `break`). +`for`:: O bloco `else` será executado somente se e quando o laço `for` rodar até +o iterável terminar (isto é, não rodará se o `for` for interrompido com um `break`). -`while`:: O bloco `else` será executado apenas se e quando o loop `while` terminar pela condição se tornar _falsa_ (novamente, não rodará se o `while` for interrompido por um `break`) +`while`:: O bloco `else` será executado somente se e quando o laço `while` +terminar pela condição se tornar _falsa_ (novamente, não rodará se o `while` for +interrompido por um `break`) -`try`:: O bloco `else` será executado apenas se nenhuma exceção for gerada no bloco `try`. A https://docs.python.org/pt-br/3/reference/compound_stmts.html[documentação oficial] também afirma: "Exceções na cláusula `else` não são tratadas pela cláusula `except` precedente." +`try`:: O bloco `else` será executado somente se nenhuma exceção for gerada no +bloco `try`. A https://fpy.li/9x[«documentação oficial»] também afirma: "Exceções +na cláusula `else` não são tratadas pela cláusula `except` precedente." -Em todos os casos, a cláusula `else` também será ignorada se uma exceção ou uma instrução `return`, `break` ou `continue` fizer com que o fluxo de controle saia do bloco principal da instrução composta. +Em todos os casos, a cláusula `else` também será ignorada se uma exceção ou uma +instrução `return`, `break` ou `continue` fizer com que o fluxo de controle saia do bloco principal da instrução composta. No caso do `try`, esta é a diferença importante entre `else` e `finally`: o bloco `finally` será executado sempre, ocorrendo ou não uma exceção, e até mesmo se o fluxo de execução sair do bloco `try` por uma instrução como `return`. [NOTE] ==== -Não tenho nada contra o funcionamento dessas cláusulas `else`, +Não tenho nada contra o funcionamento destas cláusulas `else`, mas do ponto de vista do design da linguagem, -a palavra `else` foi uma escolha infeliz; +a palavra `else` foi uma escolha infeliz, porque `else` implica em uma alternativa excludente, -como em "Execute esse loop, caso contrário faça aquilo." -Mas a semântica do `else` em loops é o oposto: "Execute esse loop, então faça aquilo." +como em "Execute esse laço, caso contrário faça aquilo." +Mas o significado do `else` em laços é o oposto: "Execute esse laço, então faça aquilo." Isso sugere que `then` ("então") seria uma escolha melhor. Também faria sentido no contexto de um `try`: "Tente isso, então faça aquilo." -Entretanto, acrescentar uma nova palavra reservada é uma ruptura séria em uma linguagem—uma decisão muito difícil. +Entretanto, acrescentar uma nova palavra reservada é uma ruptura +séria em uma linguagem—uma decisão difícil. Guido sempre foi econômico com palavras reservadas. ==== -Usar `else` com essas instruções muitas vezes torna o código mais fácil de ler e evita o transtorno de configurar flags de controle ou acrescentar instruções `if` extras ao código. +Usar `else` com essas instruções muitas vezes torna o código mais fácil de ler e +evita o transtorno de criar variáveis de controle ou codar instruções +`if` adicionais. -O uso de `else` em loops em geral segue o padrão desse trecho: +O uso de `else` em laços em geral segue o padrão desse trecho: [source, python] ---- @@ -1098,8 +1457,9 @@ else: raise ValueError('No banana flavor found!') ---- -No caso de blocos `try/except`, o `else` pode parecer redundante à primeira vista. -Afinal, a `after_call()` no trecho a seguir só será executado se a `dangerous_call()` não gerar uma exceção, correto? +No caso de blocos `try/except`, o `else` pode parecer redundante à primeira +vista. Afinal, a `after_call()` no trecho a seguir só será executado se a +`dangerous_call()` não gerar uma exceção, correto? [source, python] ---- @@ -1110,8 +1470,9 @@ except OSError: log('OSError...') ---- -Entretanto, isso coloca a `after_call()` dentro do bloco `try` sem um bom motivo. -Por clareza e correção, o corpo de um bloco `try` deveria conter apenas instruções que podem gerar as exceções esperadas. Isso é melhor: +Entretanto, isso coloca a `after_call()` dentro do bloco `try` sem um bom +motivo. Por clareza e correção, o corpo de um bloco `try` deveria conter apenas +instruções que podem gerar as exceções esperadas. Assim fica melhor: [source, python] ---- @@ -1123,124 +1484,237 @@ else: after_call() ---- -Agora fica claro que o bloco `try` está de guarda contra possíveis erros na `dangerous_call()`, e não em `after_call()`. -Também fica explícito que `after_call()` só será executada se nenhuma exceção for gerada no bloco `try`. +Agora fica claro que o bloco `try` está de guarda contra possíveis erros na +`dangerous_call()`, e não em `after_call()`. Também fica explícito que +`after_call()` só será executada se nenhuma exceção for gerada no bloco `try`. -Em Python, `try/except` é frequentemene usado para controle de fluxo, não apenas para tratamento de erro. Há inclusive um acrônimo/slogan para isso, documentado no https://docs.python.org/pt-br/3/glossary.html#term-eafp[glossário oficial de Python]: +Em Python, `try/except` é frequentemene usado para controle de fluxo, não apenas +para tratamento de erro. Há inclusive uma sigla/slogan para isso, documentado +no https://fpy.li/9y[«glossário oficial»] do Python: [quote] ____ -EAFP:: Iniciais da expressão em inglês “easier to ask for forgiveness than permission” que significa “é mais fácil pedir perdão que permissão”. Este estilo de codificação comum em Python assume a existência de chaves ou atributos válidos e captura exceções caso essa premissa se prove falsa. Este estilo limpo e rápido se caracteriza pela presença de várias instruções `try` e `except`. A técnica diverge do estilo LBYL, comum em outras linguagens como C, por exemplo. + +EAFP:: Iniciais da expressão em inglês “easier to ask for forgiveness than +permission” que significa “é mais fácil pedir perdão que permissão”. Este estilo +de codificação comum em Python assume a existência de chaves ou atributos +válidos e captura exceções caso essa premissa se prove falsa. Este estilo limpo +e rápido se caracteriza pela presença de várias instruções `try` e `except`. A +técnica diverge do estilo LBYL, comum em outras linguagens como C, por exemplo. + ____ O glossário então define LBYL: [quote] ____ -LBYL:: Iniciais da expressão em inglês “look before you leap”, que significa algo como “olhe antes de pisar”[NT: ou "olhe antes de pular"]. Este estilo de codificação testa as pré-condições explicitamente antes de fazer chamadas ou buscas. Este estilo contrasta com a abordagem EAFP e é caracterizada pela presença de muitas instruções `if`. -Em um ambiente multithread, a abordagem LBYL pode arriscar a introdução de uma condição de corrida entre “o olhar” e “o pisar”. Por exemplo, o código `if key in mapping: return mapping[key]` pode falhar se outra thread remover `key` do `mapping` após o teste, mas antes da olhada. Esse problema pode ser resolvido com bloqueios [travas] ou usando a abordagem EAFP. + +LBYL:: Iniciais da expressão em inglês “look before you leap”, que significa +algo como “olhe antes de pisar”. Este estilo de +codificação testa as pré-condições explicitamente antes de fazer chamadas ou +buscas. Este estilo contrasta com a abordagem EAFP e é caracterizada pela +presença de muitas instruções `if`. Em um ambiente multithread, a abordagem LBYL +pode arriscar a introdução de uma condição de corrida entre “o olhar” e “o +pisar”. Por exemplo, o código `if key in mapping: return mapping[key]` pode +falhar se outra thread remover `key` do `mapping` após o teste (olhar), mas antes +de acessar a chave (pisar). Este problema pode ser resolvido com bloqueios [travas] ou usando a +abordagem EAFP. ____ -Dado o estilo EAFP, faz mais sentido conhecer e usar os blocos `else` corretamente nas instruções `try/except`. +Dado o estilo EAFP, faz mais sentido conhecer e usar os blocos `else` +corretamente nas instruções `try/except`. [NOTE] ==== -Quando a [inclusão da] instrução `match` foi discutida, algumas pessoas (eu incluído) acharam que ela também devia ter uma cláusula `else`. -No fim ficou decidido que isso não era necessário, pois `case _:` tem o mesmo efeito.footnote:[Acompanhando a discussão na lista python-dev, achei que uma razão para a rejeição do `else` foi a falta de consenso sobre como indentá-lo dentro do `match`: o `else` deveria ser indentedo no mesmo nível do `match` ou no mesmo nível do `case`?] + +Quando a instrução `match` foi proposta, algumas pessoas (inclusive eu) +acharam que ela também devia ter uma cláusula `else`. No fim ficou +decidido que isso não era necessário, pois `case _:` tem o mesmo +efeito.footnote:[Acompanhando a discussão na lista python-dev, achei que um +motivo para a rejeição do `else` foi a falta de consenso sobre como indentá-lo +dentro do `match`: o `else` deveria ser indentedo no mesmo nível do `match` ou +no mesmo nível do `case`?] + ==== Agora vamos resumir o capítulo((("", startref="else18")))((("", startref="WMEBelse18"))). === Resumo do capítulo -Este((("with, match, and else blocks", "overview of"))) capítulo começou com gerenciadores de contexto e o significado da instrução `with`, indo rapidamente além de uso comum (o fechamento automático de arquivos abertos). Implementamos um gerenciador de contexto customizado: a classe `LookingGlass`, usando os métodos -`+__enter__/__exit__+`, e vimos como tratar exceções no método `+__exit__+`. Uma ideia fundamental apontada por Raymond Hettinger, na palestra de abertura da Pycon US 2013, é que `with` não serve apenas para gerenciamento de recursos; ele é uma ferramenta para fatorar código comum de configuração e de finalização, ou qualquer par de operações que precisem ser executadas antes e depois de outro procedimento.footnote:[Veja https://fpy.li/18-29[slide 21 em "Python is Awesome" ("_Python é Incrível_")] (EN).] - -Revisamos funções no módulo `contextlib` da biblioteca padrão. Uma delas, o decorador `@contextmanager`, permite implementar um gerenciador de contexto usando apenas um mero gerador com um ++yield++—uma solução menos trabalhosa que criar uma classe com pelo menos dois métodos. Reimplementamos a ++LookingGlass++ como uma função geradora `looking_glass`, e discutimos como fazer tratamento de exceções usando o `@contextmanager`. - -Então estudamos o elegante interpretador Scheme de Peter Norvig, o _lis.py_, escrito em Python idiomático e refatorado para usar `match/case` em `evaluate`—a função central de qualquer interpretador. -Entender o funcionamenteo de `evaluate` exigiu revisar um pouco de Scheme, um parser para expressões-S, um REPL simples e a construção de escopos aninhados através de `Environment`, uma subclasse de `collection.ChainMap`. -No fim, _lys.py_ se tornou um instrumento para explorarmos mais que _pattern matching_. Ele mostra como diferentes partes de um interpretador trabalham juntas, jogando luz sobre recursos fundamentais do próprio Python: porque palavras reservadas são necessárias, como as regras de escopo funcionam, e como clausuras são criadas e usadas. +Este((("with, match, and else blocks", "overview of"))) capítulo começou com +o significado da instrução `with` e os gerenciadores de contexto, indo rapidamente +além de uso mais comum, fechar arquivos automaticamente. Implementamos +um gerenciador de contexto customizado: a classe `LookingGlass`, usando os +métodos `+__enter__/__exit__+`, e vimos como tratar exceções no método +`+__exit__+`. Uma ideia fundamental apontada por Raymond Hettinger, na palestra +de abertura da Pycon US 2013, é que `with` não serve apenas para gerenciamento +de recursos; ele é uma ferramenta para fatorar código comum de configuração e de +finalização, ou qualquer par de operações que precisem ser executadas antes e +depois de outro procedimento.footnote:[Veja o https://fpy.li/18-29[slide 21 em +_Python is Awesome_ (Python é Incrível)].] + +Revisamos funções no módulo `contextlib` da biblioteca padrão. Uma delas, o +decorador `@contextmanager`, permite implementar um gerenciador de contexto +usando apenas um mero gerador com um `+yield+`—uma solução menos trabalhosa que +criar uma classe com pelo menos dois métodos. Reimplementamos a `LookingGlass` +como uma função geradora `looking_glass`, e discutimos como fazer tratamento de +exceções usando o `@contextmanager`. + +Então estudamos o elegante interpretador Scheme de Peter Norvig, o _lis.py_, +escrito em Python idiomático e refatorado para usar `match/case` em `evaluate`—a +função central de qualquer interpretador. Entender o funcionamenteo de +`evaluate` exigiu revisar um pouco de Scheme, um parser para expressões-S, um +REPL simples e a construção de escopos aninhados através de `Environment`, uma +subclasse de `collection.ChainMap`. No fim, _lys.py_ se tornou um instrumento +para explorarmos mais que casamento de padrões. Ele mostra como diferentes partes +de um interpretador trabalham juntas, jogando luz sobre recursos fundamentais do +próprio Python: porque palavras reservadas são necessárias, como as regras de +escopo funcionam, e como clausuras são criadas e usadas. [[further_reading_context_sec]] === Para saber mais -O https://docs.python.org/pt-br/3/reference/compound_stmts.html[Capítulo 8, "Instruções Compostas,"] em((("with, match, and else blocks", "further reading on"))) _A Referência da Linguagem Python_ diz praticamente tudo que há para dizer sobre cláusulas `else` em instruções `if`, `for`, `while` e `try`. Sobre o uso pythônico de `try/except`, com ou sem `else`, Raymond Hettinger deu uma resposta brilhante para a pergunta https://fpy.li/18-31["Is it a good practice to use try-except-else in Python?" (_É uma boa prática usar try-except-else em Python?_)] (EN) no StackOverflow. O pass:[Python in a Nutshell], 3rd ed., by Martelli et al., tem um capítulo sobre exceções com uma excelente discussão sobre o estilo EAFP, atribuindo à pioneira da computação Grace Hopper a criação da frase "É mais fácil pedir perdão que pedir permissão." - - -O capítulo 4 de _A Biblioteca Padrão de Python_, "Tipos Embutidos", tem uma seção dedicada a https://docs.python.org/pt-br/3/library/stdtypes.html#typecontextmanager["Tipos de Gerenciador de Contexto"]. Os métodos especiais `+__enter__/__exit__+` também estão documentados em _A Referência da Linguagem Python_, em https://docs.python.org/pt-br/3/reference/datamodel.html#with-statement-context-managers["Gerenciadores de Contexto da Instrução with"].footnote:[NT:No momento em que essa tradução é feita, o título dessa seção na documentação diz "Com gerenciadores de contexto de instruções", uma frase que sequer faz sentido. Foi aberto um issue sobre isso.] Os gerenciadores de contexto foram introduzidos na https://fpy.li/pep343[PEP 343—The "with" Statement] (EN). - -Raymond Hettinger apontou a instrução `with` como um "recurso maravilhoso da linguagem" em sua https://fpy.li/18-29[palestra de abertura da PyCon US 2013] (EN). Ele também mostrou alguns usos interessantes de gerenciadores de contexto em sua apresentação https://fpy.li/18-35["Transforming Code into Beautiful, Idiomatic Python" (_"Transformando Código em Lindo Python Idiomático"_)] (EN), na mesma conferência. - -O post de Jeff Preshing em seu blog, https://fpy.li/18-36["The Python 'with' Statement by Example" "_A Instrução 'with' de Python através de Exemplos_"](EN) é interessante pelos exemplos de uso de gerenciadores de contexto com a biblioteca gráfica `pycairo`. - -A classe `contextlib.ExitStack` foi baseada em uma ideia original de Nikolaus Rath, que escreveu um post curto explicando porque ela é útil: -https://fpy.li/18-37["On the Beauty of Python's ExitStack" "_Sobre a Beleza do ExitStack de Python"_]. No texto, Rath propõe que `ExitStack` é similar, mas mais flexível que a instrução `defer` em Go—que acho uma das melhores ideias naquela linguagem. - -Beazley and Jones desenvolveram gerenciadores de contexto para propósitos muito diferentes em seu livro, pass:[Python Cookbook,] (EN) 3rd ed. A "Recipe 8.3. Making Objects Support the Context-Management Protocol" (_Receita 8.3. Fazendo Objetos Suportarem o Protocolo Gerenciador de Contexto_) implementa uma classe `LazyConnection`, cujas instâncias são gerenciadores de contexto que abrem e fecham conexões de rede automaticamente, em blocos `with`. A "Recipe 9.22. Defining Context Managers the Easy Way" (_Receita 9.22. O Jeito Fácil de Definir Gerenciadores de Contexto_) introduz um gerenciador de contexto para código de cronometragem, e outro para realizar mudanças transacionais em um objeto `list`: dentro do bloco `with` é criada um cópia funcional da instância de `list`, e todas as mudanças são aplicadas àquela cópia funcional. Apenas quando o bloco `with` termina sem uma exceção a cópia funcional substitui a original. Simples e genial. +O https://fpy.li/9x[«Capítulo 8, Instruções Compostas»] na((("with, match, and +else blocks", "further reading on"))) _Referência da Linguagem Python_ diz +praticamente tudo que há para dizer sobre cláusulas `else` em instruções `if`, +`for`, `while` e `try`. Sobre o uso pythônico de `try/except`, com ou sem +`else`, Raymond Hettinger deu uma resposta brilhante para a pergunta +https://fpy.li/18-31[_Is it a good practice to use try-except-else in Python?_ +(É uma boa prática usar try-except-else em Python?)] no StackOverflow. O +https://fpy.li/pynut3[_Python in aNutshell, 3rd ed._], de Martelli et.al., tem um capítulo sobre exceções +com uma excelente discussão sobre o estilo EAFP, atribuindo à pioneira da +computação Grace Hopper a frase "É mais fácil pedir perdão que pedir +permissão." + +O capítulo 4 de _A Biblioteca Padrão de Python_, "Tipos Embutidos", tem uma +seção dedicada a https://fpy.li/9z[«Tipos de Gerenciador de Contexto»]. Os +métodos especiais `+__enter__/__exit__+` também estão documentados em _A +Referência da Linguagem Python_, em https://fpy.li/a4[«Gerenciadores de Contexto +da Instrução with»]. Os gerenciadores de contexto foram propostos na +https://fpy.li/pep343[_PEP 343—The "with" Statement_]. + +Raymond Hettinger apontou a instrução `with` como um "recurso maravilhoso da +linguagem" em sua https://fpy.li/18-29[«palestra de abertura»] da PyCon US 2013. +Ele também mostrou alguns usos interessantes de gerenciadores de contexto em sua +apresentação https://fpy.li/18-35[_Transforming Code into Beautiful, Idiomatic +Python+] (Transformando Código em Python Lindo e Idiomático), na mesma +conferência. + +O post de Jeff Preshing em seu blog, +https://fpy.li/18-36[_The Python 'with' Statement by Example_] +(A Instrução 'with' de Python através de exemplos) +é interessante pelos exemplos de uso de gerenciadores de contexto com a +biblioteca gráfica `pycairo`. + +A classe `contextlib.ExitStack` foi baseada em uma ideia original de Nikolaus +Rath, que escreveu um post curto explicando porque ela é útil: +https://fpy.li/18-37[_On the Beauty of Python's ExitStack_] +(Sobre a Beleza do ExitStack de Python]. No texto, Rath argumenta que `ExitStack` é similar, +mas mais flexível que a instrução `defer` em Go—que acho uma das melhores ideias +naquela linguagem. + +Beazley and Jones desenvolveram gerenciadores de contexto para propósitos muito +diferentes em seu livro, +https://fpy.li/pycook3[_Python Cookbook, 3rd. ed._]. A _Recipe 8.3. +Making Objects Support the Context-Management Protocol_ (Fazendo +Objetos Suportarem o Protocolo Gerenciador de Contexto) implementa uma classe +`LazyConnection`, cujas instâncias são gerenciadores de contexto que abrem e +fecham conexões de rede automaticamente, em blocos `with`. A +_Recipe 9.22. Defining Context Managers the Easy Way_ +(O Jeito Fácil de Definir Gerenciadores de Contexto) +apresenta um gerenciador de contexto para código de +cronometragem, e outro para realizar mudanças transacionais em um objeto `list`: +dentro do bloco `with` é criada um cópia de trabalho da instância de `list`, e +todas as mudanças são aplicadas àquela cópia de trabalho. Apenas quando o bloco +`with` termina sem uma exceção a cópia de trabalho substitui a original. Simples e +genial. Peter Norvig descreve seu pequeno interpretador Scheme nos posts -pass:["(How to Write a (Lisp) Interpreter (in Python))" "(_Como Escrever um Interpretador (Lisp) (em Python))_"] (EN) e -pass:["(An ((Even Better) Lisp) Interpreter (in Python))" "_(Um Interpretador (Lisp (Ainda Melhor)) (em Python))_"] (EN). -O código-fonte de _lis.py_ e _lispy.py_ está no repositório https://fpy.li/18-40[_norvig/pytudes_]. -Meu repositório, https://fpy.li/18-41[_fluentpython/lispy_], inclui a versão _mylis_ do _lis.py_, atualizado para Python 3.10, com um REPL melhor, integraçào com a linha de comando, exemplos, mais testes e referências para aprender mais sobre Scheme. -O melhor ambiente e dialeto de Scheme para aprender e experimentar é o https://fpy.li/18-42[Racket]. - -//// -PROD: The title of Norvig's second post -"(An ((Even Better) Lisp) Interpreter (in Python))" -appears without the first "(" and the trailing "))" in the rendered PDF, -and it is also generating an index entry. -Can you please find a way to make it appear right, and not generate an index entry? Thanks! -//// +https://fpy.li/18-38[_(How to Write a (Lisp) Interpreter (in Python))_] +(Como Escrever um Interpretador (Lisp) (em Python)) e +https://fpy.li/18-39[_(An ((Even Better) Lisp) Interpreter (in Python))_] +(Um Interpretador (Lisp (Ainda Melhor)) (em Python)). +O código-fonte de _lis.py_ e _lispy.py_ está no repositório +https://fpy.li/18-40[_norvig/pytudes_]. Meu repositório, +https://fpy.li/18-41[_fluentpython/lispy_], inclui a versão _mylis_ do _lis.py_, +atualizado para Python 3.10, com um REPL melhor, integraçào com a linha de +comando, exemplos, mais testes e referências para aprender mais sobre Scheme. +O melhor ambiente e dialeto de Scheme para aprender e experimentar é o +https://fpy.li/18-42[_Racket_]. [[soapbox_with_match]] .Ponto de vista **** -[role="soapbox-title"] -Fatorando o pão -Em ((("Soapbox sidebars", "with statements")))((("with, match, and else blocks", "Soapbox discussion", id="WMEBsoap18"))) sua palestra de abertura na PyCon US 2013, https://fpy.li/18-1["What Makes Python Awesome" ("_O que torna Python incrível_")], -Raymond Hettinger diz que quando viu a proposta da instrução `with`, pensou que era "um pouquinho misteriosa." Inicialmente tive uma reação similar. As PEPs são muitas vezes difíceis de ler, e a PEP 343 é típica nesse sentido. +*Fatorando o pão* + +Em ((("Soapbox sidebars", "with statements")))((("with, match, and else blocks", +"Soapbox discussion", id="WMEBsoap18"))) sua palestra de abertura na PyCon US +2013, https://fpy.li/18-1["What Makes Python Awesome" ("_O que torna Python +incrível_")], Raymond Hettinger diz que quando viu a proposta da instrução +`with`, pensou que era "um pouquinho misteriosa." Inicialmente tive uma reação +similar. As PEPs são muitas vezes difíceis de ler, e a PEP 343 é típica nesse +sentido. -Mas aí--nos contou Hettinger--ele teve uma ideia: as sub-rotinas são a invenção mais importante na história das linguagens de computador. Se você tem sequências de operações, como A;B;C e P;B;Q, você pode fatorar B em uma sub-rotina. É como fatorar o recheio de um sanduíche: usar atum com tipos de diferentes de pão. Mas e se você quiser fatorar o pão, para fazer sanduíches com pão de trigo integral usando recheios diferentes a cada vez? É isso que a instrução `with` oferece. Ela é o complemento da sub-rotina. Hettinger continuou: +Mas aí--nos contou Hettinger--ele teve uma ideia: as sub-rotinas são a invenção +mais importante na história das linguagens de computador. Se você tem sequências +de operações, como A;B;C e P;B;Q, você pode fatorar B em uma sub-rotina. É como +fatorar o recheio de um sanduíche: usar atum com tipos de diferentes de pão. Mas +e se você quiser fatorar o pão, para fazer sanduíches com pão de trigo integral +usando recheios diferentes a cada vez? É isso que a instrução `with` oferece. +Ela é o complemento da sub-rotina. Hettinger continuou: [quote] ____ + A instrução `with` é algo muito importante. -Encorajo vocês a irem lá e olharem para a ponta desse iceberg, e daí cavarem mais fundo. +Encorajo vocês a irem lá e olharem para a ponta desse iceberg, +e daí cavarem mais fundo. Provavelmente é possível fazer coisas muito profundas com a instrução `with`. Seus melhores usos ainda estão por ser descobertos. -Espero que, se vocês fizerem bom uso dela, ela será copiada para outras linguagens, e todas as linguagens futuras vão incluí-la. -Vocês podem ser parte da descoberta de algo quase tão profundo quanto a invenção da própria sub-rotina. +Espero que, se vocês fizerem bom uso dela, +ela será copiada para outras linguagens, +e todas as linguagens futuras vão incluí-la. +Vocês podem ser parte da descoberta de algo +quase tão profundo quanto a invenção da própria sub-rotina. + ____ -Hettinger admite que está tentando muito vender a instrução `with`. +Hettinger admite que está forçando a "venda" da instrução `with`. Mesmo assim, é um recurso bem útil. -Quando ele usou a analogia do sanduíche para explicar como `with` é o complemento da sub-rotina, muitas possibilidades se abriram na minha mente. +Quando ele usou a analogia do sanduíche para explicar como `with` é o +complemento da sub-rotina, muitas possibilidades se abriram na minha mente. -Se você precisa convencer alguém que Python é maravilhoso, assista a palestra de abertura de Hettinger. -A parte sobre gerenciadores de contexto fica entre 23:00 to 26:15. Mas a palestra inteira é excelente. +Se você precisa convencer alguém que Python é maravilhoso, assista a palestra de +abertura de Hettinger. A parte sobre gerenciadores de contexto fica entre 23:00 +to 26:15. Mas a palestra inteira é excelente. -[role="soapbox-title"] -Recursão eficiente com chamadas de cauda apropriadas +*Recursão eficiente com chamadas de cauda apropriadas* -As implementações((("Soapbox sidebars", "proper tail calls (PTC)", id="SStail18")))((("proper tail calls (PTC)", id="PTC18")))((("tail call optimization (TCO)", id="tco18"))) padrão de Scheme são obrigadas a oferecer _chamadas de cauda apropriadas_ (PTC, sigla em inglês para _proper tail calls_), para tornar a iteração por recursão uma alternativa prática aos loops `while` das linguagens imperativas. -Alguns autores se referem às PTC como _otimização de chamadas de cauda_ (TCO, sigla em inglês para _tail call optimization_); -para outros, TCO é uma coisa diferente. -Para mais detalhes, leia https://pt.wikipedia.org/wiki/Recursividade_(ci%C3%AAncia_da_computa%C3%A7%C3%A3o)#Fun%C3%A7%C3%B5es_recursivas_em_cauda["Chamadas recursivas de cauda] na Wikipedia em português e -https://fpy.li/18-44["Tail call"] (EN), mais aprofundado, na Wikipedia em inglês, e -https://fpy.li/18-45["Tail call optimization in ECMAScript 6"] (EN). -Uma _chamada de cauda_ é quando uma função devolve o resultado de uma chamada de função, que pode ou não ser a ela mesma (a função que está devolvendo o resultado). -Os exemplos `gcd` no <> e no <> fazem chamadas de cauda (recursivas) no lado _falso_ do `if`. +As implementações((("Soapbox sidebars", "proper tail calls (PTC)", +id="SStail18")))((("proper tail calls (PTC)", id="PTC18")))((("tail call +optimization (TCO)", id="tco18"))) padrão de Scheme são obrigadas a oferecer +_chamadas de cauda apropriadas_ (PTC, sigla de _Proper Tail Call_), +para tornar a iteração por recursão uma alternativa prática aos laços `while` e `for` +das linguagens imperativas. Alguns autores se referem às PTC como _otimização de +chamadas de cauda_ (TCO, sigla de _Tail Call Optimization_); para +outros, TCO é uma coisa diferente. Para mais detalhes, leia +https://fpy.li/a2[«Chamadas recursivas de cauda»] na Wikipedia em português e +https://fpy.li/18-44[«Tail call»], mais aprofundado, na Wikipedia em inglês, e +https://fpy.li/18-45[«Tail call optimization in ECMAScript 6»]. -Por outro lado, essa `factorial` não faz uma chamada de cauda: +Uma _chamada de cauda_ é quando uma função devolve o resultado de uma chamada de +função, que pode ou não ser a ela mesma (a função que está devolvendo o +resultado). Os exemplos `gcd` no <> e no <> fazem +chamadas de cauda (recursivas) no desvio _falso_ do `if`. + +Por outro lado, esta `factorial` não faz uma chamada de cauda: [source, python] ---- @@ -1250,11 +1724,12 @@ def factorial(n): return n * factorial(n - 1) ---- -A chamada para `factorial` na última linha não é uma chamada de cauda, pois o valor de `return` não é somente o resultado de uma chamada recursiva: -o resultado é multiplicado por `n` antes de ser devolvido. +A chamada para `factorial` na última linha não é uma chamada de cauda, pois o +valor de `return` não é somente o resultado de uma chamada recursiva: o +resultado é multiplicado por `n` antes de ser devolvido. Aqui está uma alternativa que usa uma chamada de cauda, -e é portanto _recursiva de cauda_: +e é portanto recursiva de cauda (_tail recursive_): [source, python] ---- @@ -1264,57 +1739,65 @@ def factorial_tc(n, product=1): return factorial_tc(n - 1, product * n) ---- -Python não tem PTC -então não há vantagem em escrever funções recursivas de cauda. -Neste caso, a primeira versão é, na minha opinião, mais curta e mais legível. -Para usos na vida real, não se esqueça que Python tem o `math.factorial`, -escrito em C sem recursão. -O ponto é que, mesmo em linguagens que implementam PTC, isso não beneficia toda função recursiva, -apenas aquelas cuidadosamente escritas para fazerem chamadas de cauda. - -Se PTC são suportadas pela linguagem, -quando o interpretador vê uma chamada de cauda, -ele pula para dentro do corpo da função chamada sem criar um novo stack frame, economizando memória. -Há também linguagens compiladas que implementam PTC, por vezes como uma otimização que pode ser ligada e desligada. - -Não existe um consenso universal sobre a definição de TCO ou -sobre o valor das PTC em linguagens que não foram projetadas como -linguagens funcionais desde o início, como Python e Javascript. -Em linguagens funcionais, PTC é um recurso esperado, não apenas uma otimização boa de ter à mão. -Se a linguagem não tem outro mecanismo de iteração além da recursão, -então PTC é necessário para tornar prático o uso da linguagem. -O https://fpy.li/18-46[_lis.py_] de Norvig -não implementa PTC, mas seu interpretador mais elaborado, o +Python não tem PTC então não há vantagem em escrever funções recursivas de +cauda. Neste caso, versão `factorial` é mais curta e mais legível, na minha opinião, +do que a `factorial_tc`. +Para usos na vida real, não se esqueça que Python tem o +`math.factorial`, escrito em C sem recursão. O ponto é que, mesmo em linguagens +que implementam PTC, isso não beneficia toda função recursiva, apenas aquelas +cuidadosamente escritas para fazerem chamadas de cauda. + +Se a linguagem implementa PTC, quando o interpretador vê uma chamada de +cauda, ele pula para dentro do corpo da função chamada sem criar um novo stack +frame, economizando memória. Há também linguagens compiladas que implementam +PTC, por vezes como uma otimização que pode ser ligada e desligada. + +Não existe um consenso universal sobre a definição de TCO ou sobre o valor das +PTC em linguagens que não foram projetadas como linguagens funcionais desde o +início, como Python e Javascript. +Em linguagens funcionais, PTC não é apenas uma otimização desejável. Se a linguagem não tem +outro mecanismo de iteração além da recursão, então PTC é necessário para viabilizar +o uso da linguagem na prática. O +https://fpy.li/18-46[_lis.py_] de Norvig não +implementa PTC, mas seu interpretador mais elaborado, o https://fpy.li/18-16[_lispy.py_], implementa. -[role="soapbox-title"] -Os argumentos contra chamadas de cauda apropriadas em Python e Javascript - +*Argumentos contra chamadas de cauda apropriadas em Python e Javascript* O CPython não implementa PTC, e provavelmente nunca o fará. Guido van Rossum escreveu -https://fpy.li/18-48["Final Words on Tail Calls" (_"Últimas Palavras sobre Chamadas de Cauda"_)] +https://fpy.li/18-48[_Final Words on Tail Calls_] +(Últimas Palavras sobre Chamadas de Cauda) para explicar o motivo. Resumindo, aqui está uma passagem fundamental de seu post: [quote] ____ -Pessoalmente, acho que é um bom recurso para algumas linguagens, mas não acho que se encaixe no Python: -a eliminação dos registros do stack para algumas chamadas mas não para outras certamente confundiria muitos usuários, que não foram criados na religião das chamadas de cauda, mas podem ter aprendido sobre a semântica das chamadas restreando algumas chamadas em um depurador. + +Pessoalmente, acho que é um bom recurso para algumas linguagens, mas não acho +que se encaixe no Python: a eliminação dos registros do stack para algumas +chamadas mas não para outras certamente confundiria muitos usuários, que não +foram criados na religião das chamadas de cauda, mas podem ter aprendido sobre a +semântica das chamadas restreando algumas chamadas em um depurador. + ____ Em 2015, PTC foram incluídas no padrão ECMAScript 6 para JavaScript. -Em outubro de 2021 o interpretador no -https://fpy.li/18-49[WebKit as implementa] (EN). +Em outubro de 2021 o interpretador do +https://fpy.li/18-49[«WebKit as implementa»]. O WebKit é usado pelo Safari. Os interpretadores JS em todos os outros navegadores populares não têm PTC, assim como o Node.js, que depende da engine V8 que o Google mantém para o Chrome. -Transpiladores e polyfills (_injetores de código_) voltados para o JS, como o TypeScript, o ClojureScript e o Babel, também não suportam PTC, de acordo com essa https://fpy.li/18-50[" Tabela de compatibilidade com ECMAScript 6"] (EN). +Transpiladores e polyfills (_injetores de código_) voltados para o JS, +como o TypeScript, o ClojureScript e o Babel, também não suportam PTC, +de acordo com uma https://fpy.li/18-50[«tabela de compatibilidade com ECMAScript 6»]. -Já vi várias explicações para a rejeição das PTC por parte dos implementadores, mas a mais comum é a mesma que Guido van Rossum mencionou: +Já vi várias explicações para a rejeição das PTC por parte dos implementadores, +mas a mais comum é a mesma que Guido van Rossum mencionou: PTC tornam a depuração mais difícil para todo mundo, e beneficiam apenas uma minoria que prefere usar recursão para fazer iteração. Para mais detalhes, veja -https://fpy.li/18-51["What happened to proper tail calls in JavaScript?" "_O que aconteceu com as chamadas de cauda apropriadas em Javascript?_"] de Graham Marlow. +https://fpy.li/18-51[_What happened to proper tail calls in JavaScript?_] +(O que aconteceu com as chamadas de cauda apropriadas em Javascript?) de Graham Marlow. Há casos em que a recursão é a melhor solução, mesmo no Python sem PTC. Em um https://fpy.li/18-52[post anterior] @@ -1323,27 +1806,35 @@ sobre o assunto, Guido escreveu: [quote] ____ [...] uma implementação típica de Python permite 1000 recursões, -o que é bastante para código não-recursivo e para código que usa recursão para atravessar, +o que é bastante para código não-recursivo e para código que usa recursão para percorrer, por exemplo, um árvore de parsing típica, -mas não o bastante para um loop escrito de forma recursiva sobre uma lista grande. +mas não o bastante para um laço escrito de forma recursiva sobre uma lista grande. ____ + Concordo com Guido e com a maioria dos implementadores de Javascript. -A falta de PTC é a maior restrição ao desenvolvimento de programas Python em um estilo funcional—mais que a sintaxe limitada de `lambda`. -Se você estiver curioso em ver como PTC funciona em um interpretador com menos recursos (e menos código) que o _lispy.py_ de Norvig, veja o https://fpy.li/18-53[__mylis_2__]. -O truque é iniciar com o loop infinito em `evaluate` e o código no `case` para chamadas de função: -essa combinação faz o interpretador pular para dentro do corpo da próxima `Procedure` sem chamar `evaluate` recursivamente durante a chamada de cauda. -Esses pequenos interpretadores demonstram o poder da abstração: -apesar de Python não implementar PTC, é possível e não muito difícil escrever um interpretador, em Python, que implementa PTC. -Aprendi a fazer isso lendo o código de Peter Norvig. -Obrigado por compartilhar, professor!((("", startref="tco18")))((("", startref="PTC18")))((("", startref="SStail18"))) +A falta de PTC é a maior restrição ao desenvolvimento de programas Python em um +estilo funcional—mais que a sintaxe limitada de `lambda`. + +Se você estiver curioso em ver como PTC funciona em um interpretador com menos +recursos (e menos código) que o _lispy.py_ de Norvig, veja o +https://fpy.li/18-53[__mylis_2__]. O truque começa com o laço infinito em +`evaluate` e o código no `case` que faz chamadas de função: essa combinação faz o +interpretador pular para dentro do corpo da próxima `Procedure` sem invocar +`evaluate` recursivamente durante a chamada de cauda. +Estes pequenos +interpretadores demonstram o poder da abstração: apesar de Python não +implementar PTC, é possível e não muito difícil escrever um interpretador, em +Python, que implementa PTC. Aprendi a fazer isso lendo o código de Peter Norvig. +Obrigado por compartilhar, professor!((("", startref="tco18")))((("", +startref="PTC18")))((("", startref="SStail18"))) -[role="soapbox-title"] -A opinião de Norvig sobre evaluate() com _pattern matching_ +*A opinião de Norvig sobre `evaluate()` com casamento de padrões* -Eu((("Soapbox sidebars", "lis.py and evaluate function"))) compartilhei o código da versão Python 3.10 de _lis.py_ com Peter Norvig. -Ele gostou do exemplo usando _pattern matching_, mas sugeriu uma solução diferente: +Mostrei((("Soapbox sidebars", "lis.py and evaluate function"))) +o código da versão Python 3.10 de _lis.py_ para Peter Norvig. +Ele gostou do exemplo usando casamento de padrões, mas sugeriu uma solução diferente: em vez de usar os guardas que escrevi, ele teria exatamente um `case` por palavra reservada, e teria testes dentro de cada `case`, para fornecer mensagens de `SyntaxError` @@ -1351,12 +1842,13 @@ mais específicas—por exemplo, quando o corpo estiver vazio. Isso também tornaria o guarda em `case [func_exp, *args] if func_exp not in KEYWORDS:` desnecessário, pois todas as palavras reservadas teriam sido tratadas antes do `case` para chamadas de função. -// [role="pagebreak-before less_space"] -Provavelmente seguirei o conselho do professor Norvig quando acrescentar mais funcionalidades ao +Provavelmente seguirei o conselho do professor Norvig quando +acrescentar mais funcionalidades ao https://fpy.li/18-54[_mylis_]. -Mas a forma como estruturei `evaluate` no <> tem algumas vantagens didáticas nesse livro: +Mas a forma como estruturei `evaluate` no <> +tem algumas vantagens didáticas nesse livro: o exemplo é paralelo à implementação com `if/elif/…` (<>), -as cláusulas `case` demonstram mais recursos de _pattern matching_ -e o código é mais conciso.((("", startref="WMEBsoap18"))) +as cláusulas `case` demonstram mais recursos de casamento de padrões +e o código é mais enxuto.((("", startref="WMEBsoap18"))) **** diff --git a/online/cap19.adoc b/online/cap19.adoc index a85437c..1e177fc 100644 --- a/online/cap19.adoc +++ b/online/cap19.adoc @@ -7,18 +7,27 @@ ____ Concorrência é lidar com muitas coisas ao mesmo tempo. + Paralelismo é fazer muitas coisas ao mesmo tempo. + -Não são a mesma coisa, mas estão relacionados. + +Não são a mesma coisa, mas têm relação. + Uma é sobre estrutura, outro é sobre execução. + A concorrência fornece uma maneira de estruturar uma solução para resolver um problema que pode (mas não necessariamente) ser paralelizado.footnote:[Slide 8 of the talk https://fpy.li/19-1[_Concurrency Is Not Parallelism_].] ____ Este((("concurrency models", "benefits of concurrency"))) capítulo é sobre como -fazer Python "lidar com muitas coisas ao mesmo tempo." -Isso pode envolver programação concorrente ou paralela—e mesmo os acadêmicos rigorosos com terminologia discordam sobre o uso dessas palavras. -Vou adotar as definições informais de Rob Pike, na epígrafe desse capítulo, mas saiba que encontrei artigos e livros que dizem ser sobre computação paralela mas são quase que inteiramente sobre concorrência.footnote:[Estudei e trabalhei com o Prof. Imre Simon, que gostava de dizer que há dois grandes pecados na ciência: usar palavras diferentes para significar a mesma coisa e usar uma palavra para significar coisas diferentes. Imre Simon (1942-2009) foi um pioneiro da ciência da computação no Brasil, com contribuições seminais para a Teoria dos Autômatos. Ele fundou o campo da Matemática Tropical e foi também um defensor do software livre, da cultura livre, e da Wikipédia.] - -O paralelismo((("parallelism"))) é, na perspectiva de Pike, um caso especial de concorrência. +fazer Python "lidar com muitas coisas ao mesmo tempo." Isso pode envolver +programação concorrente ou paralela. Até mesmo acadêmicos discordam sobre +o uso destas palavras. Vou adotar as definições +informais de Rob Pike, na epígrafe acima, mas encontrei +artigos e livros que dizem ser sobre computação paralela mas são quase que +inteiramente sobre concorrência.footnote:[Estudei e trabalhei com o Prof. Imre +Simon, que gostava de dizer que há dois grandes pecados na ciência: usar +palavras diferentes para significar a mesma coisa e usar uma palavra para +significar coisas diferentes. Imre Simon (1942-2009) foi um pioneiro da ciência +da computação no Brasil, com contribuições seminais para a Teoria dos Autômatos. +Ele fundou o campo da Matemática Tropical e foi também um defensor do software +livre, da cultura livre, e da Wikipédia.] + +Na perspectiva de Pike, o paralelismo((("parallelism"))) é, um caso especial de concorrência. Todo sistema paralelo é concorrente, mas nem todo sistema concorrente é paralelo. No início dos anos 2000, usávamos laptops GNU Linux de um único núcleo, que rodavam 100 processos ao mesmo tempo. @@ -29,15 +38,15 @@ em nosso cotidiano é concorrente e não paralela. O SO administra centenas de processos, assegurando que cada um tenha a oportunidade de progredir, mesmo quando a CPU em si não roda mais que quatro tarefas em parelelo. -Este((("concurrency models", "topics covered"))) capítulo não assume que você tenha qualquer conhecimento prévio de programação concorrente ou paralela. +Este((("concurrency models", "topics covered"))) capítulo não assume que você tenha conhecimento prévio de programação concorrente ou paralela. Após uma breve introdução conceitual, vamos estudar exemplos simples, para apresentar e comparar os principais pacotes da biblioteca padrão de Python dedicados a programação concorrente: `threading`, `multiprocessing`, e `asyncio`. O último terço do capítulo é uma revisão geral de ferramentas, servidores de aplicação e filas de tarefas distribuídas -(_distributed task queues_) de vários desenvolvedores, capazes de melhorar o desempenho e a escalabilidade de aplicações Python. +(_distributed task queues_) de vários fornecedores, capazes de melhorar o desempenho e a escalabilidade de aplicações Python. Todos esses são tópicos importantes, mas fogem do escopo de um livro focado nos recursos fundamentais da linguagem Python. -Mesmo assim, achei importante mencionar esses temas nessa segunda edição do _Python Fluente_, +Mesmo assim, achei importante mencionar estes temas nesta segunda edição do _Python Fluente_, porque a aptidão de Python para computação concorrente e paralela não está limitada ao que a biblioteca padrão oferece. Por isso YouTube, DropBox, Instagram, Reddit e outros foram capazes de atingir alta escalabilidade quando começaram, usando Python como sua linguagem primária—apesar das persistentes alegações de que "Python não escala." @@ -45,42 +54,66 @@ usando Python como sua linguagem primária—apesar das persistentes alegações === Novidades neste capítulo Este((("concurrency models", "significant changes to"))) capítulo é novo, escrito para a segunda edição do _Python Fluente_. -Os exemplos com os caracteres giratórios no <> antes estavam no capítulo sobre _asyncio_. +Os exemplos com os caracteres giratórios na <> antes estavam no capítulo sobre _asyncio_. Aqui eles foram revisados, e apresentam uma primeira ilustração das três abordagens de Python à concorrência: threads, processos e corrotinas nativas. -O restante do conteúdo é novo, exceto por alguns parágrafos, que apareciam originalmente nos capítulos sobre `concurrent.futures` e _asyncio_. +O resto do conteúdo é novo, exceto por alguns parágrafos, que apareciam originalmente nos capítulos sobre `concurrent.futures` e `asyncio`. A <> é diferente do resto do livro: não há código exemplo. O objetivo ali é apresentar brevemente ferramentas importantes, que você pode querer estudar para conseguir concorrência e paralelismo de alto desempenho, para além do que é possível com a biblioteca padrão de Python. +.Nota sobre o cenário em 2026 +[NOTE] +==== +Contratualmente, esta tradução precisa seguir o conteúdo do +_Fluent Python, Second Edition_, que publiquei pela O'Reilly em 2022. + +Pesquisei e escrevi este capítulo em 2021, quando a versão mais recente do Python era a 3.10. +Desde então, novas versões do Python têm trazido melhorias importantes para a programação +concorrente, inclusive novas formas de contornar a GIL. + +Além disso, o eco-sistema de desenvolvimento para novas aplicações Web hoje é +dominado pelas soluções de provedores de nuvem, como AWS, que oferecem +substitutos para gerenciadores de fila como _Cellery_ e novas arquiteturas para +execução concorrente diferentes dos servidores de aplicação como _uWSGI_ e +_Gunicorn_. + +Mais do que qualquer outro capítulo no livro, +este precisaria de muitas atualizações para refletir o cenário em 2026, +mas os princípios e conceitos fundamentais continuam válidos, +especialmente para o desenvolvimento de sistemas _on premise_, +independentes de um provedor de nuvem. +==== + === A visão geral Há((("concurrency models", "basics of concurrency"))) muitos fatores que tornam a programação concorrente difícil, mas quero tocar no mais básico deles: iniciar threads ou processos é fácil, mas como administrá-los?footnote:[Essa seção foi sugerida por meu amigo Bruce Eckel—autor de livros sobre Kotlin, Scala, Java, e {cpp}.] -Quando você chama uma função, o código que origina a chamada fica bloqueado até que função retorne. +Quando você invoca uma função, o código que faz a chamada fica aguardando até que função retorne. Então você sabe que a função terminou, e pode facilmente acessar o valor devolvido por ela. -Se a função lançar uma exceção, o código de origem pode cercar aquela chamada com um bloco `try/except` para tratar o erro. +Se a função lançar uma exceção, o código cliente pode cercar aquela chamada com um bloco `try/except` para tratar o erro. -Essas opções não existem quando você inicia threads ou um processo: +Tais opções não existem quando você inicia threads ou um processo: você não sabe automaticamente quando eles terminaram, -e obter os resultados ou os erros requer criar algum canal de comunicação, tal como uma fila de mensagens. - -Além disso, criar uma thread ou um processo não é barato, -você não quer iniciar uma delas apenas para executar uma única computação e desaparecer. -Muitas vezes queremos amortizar o custo de inicialização transformando cada thread ou processo em um "worker" ou "unidade de trabalho", -que entra em um loop e espera por dados para processar. -Isso complica ainda mais a comunicação e introduz mais perguntas. +e obter os resultados ou os erros requer algum canal de comunicação +que você precisa fornecer, como uma fila de mensagens (_message queue_). + +Além disso, criar uma thread ou um processo tem um custo, +você não quer iniciar um deles apenas para executar uma única computação e encerrar. +Muitas vezes queremos amortizar o custo de inicialização transformando cada thread ou processo em um _worker_ ou "unidade de trabalho", +que entra em um laço e espera por dados para processar. +Isso complica ainda mais a comunicação e introduz mais questões. Como terminar um "worker" quando ele não é mais necessário? E como fazer para encerrá-lo sem interromper uma tarefa inacabada, deixando dados inconsistentes e recursos não liberados—tal como arquivos abertos? -A resposta envolve novamente mensagens e filas. +A resposta envolve novamente filas e mensagens. Uma corrotina é fácil de iniciar. Se você inicia uma corrotina usando a palavra-chave `await`, -é fácil obter o valor de retorno e há um local óbvio para interceptar exceções. +é fácil obter o valor de retorno e há um local óbvio para tratar exceções. Mas corrotinas muitas vezes são iniciadas pelo framework assíncrono, e isso pode torná-las tão difíceis de monitorar quanto threads ou processos. @@ -91,17 +124,18 @@ Então vamos primeiro garantir que estamos na mesma página em relação a algun === Um pouco de jargão -Aqui((("concurrency models", "relevant terminology", id="CBterm19"))) estão alguns termos que vou usar pelo restante desse capítulo e nos dois seguintes: +Aqui((("concurrency models", "relevant terminology", id="CBterm19"))) +estão alguns termos que usaremos pelo restante deste capítulo e nos dois seguintes: Concorrência:: - A habilidade de lidar com múltiplas tarefas pendentes, fazendo progredir uma por vez ou várias em paralelo (se possível), - de forma que cada uma delas avance até terminar com sucesso ou falha. - Uma CPU de núcleo único é capaz de concorrência se rodar um "agendador" (_scheduler_) do sistema operacional, que intercale a execução das tarefas pendentes. - Também conhecida como multitarefa (_multitasking_). + A capacidade de lidar com múltiplas tarefas pendentes, fazendo progredir uma por vez ou várias em paralelo (se possível), + de forma que cada uma delas avance até terminar com sucesso ou falhar. + Uma CPU de um núcleo é capaz de concorrência se rodar um _scheduler_ (escalonador) do sistema operacional, que intercale a execução das tarefas pendentes. + Esta capacidade também é conhecida como multitarefa (_multitasking_). Paralelismo:: A((("parallelism"))) habilidade de executar múltiplas operações computacionais ao mesmo tempo. Isso requer uma CPU com múltiplos núcleos, múltiplas CPUs, uma - https://fpy.li/19-2[GPU], ou múltiplos computadores em um _cluster_ (agrupamento)). + https://fpy.li/19-2[GPU], ou múltiplos computadores em um _cluster_ (agrupamento). Unidades de execução:: Termo genérico((("execution units"))) para objetos que executam código de forma concorrente, cada um com um estado e uma pilha de chamada independentes. @@ -109,19 +143,19 @@ Unidades de execução:: _processos_, _threads_, e _corrotinas_. Processo:: - Uma((("processes", "definition of term"))) instância de um programa de computador em execução, usando memória e uma fatia do tempo da CPU. - Sistemas operacionais modernos em nossos computadores e celulares rotineiramente mantém centenas de processos de forma concorrente, cada um deles isolado em seu próprio espaço de memória privado. - Processos se comunicam via pipes, soquetes ou arquivos mapeados da memória. Todos esses métodos só comportam bytes em estado bruto. + Uma((("processes", "definition of term"))) instância de um programa de computador em execução, usando parte da memória e uma fatia do tempo da CPU. + Os sistemas operacionais modernos em nossos computadores e celulares rotineiramente mantém centenas de processos de forma concorrente, cada um deles isolado em seu próprio espaço de memória privado. + Processos se comunicam via _pipes_, soquetes ou arquivos mapeados na memória (_memory mapped files_). Todos esses métodos só comportam bytes em estado bruto. Objetos Python precisam ser serializados (convertidos em sequências de bytes) para passarem de um processo a outro. Isto é caro, e nem todos os objetos Python podem ser serializados. Um processo pode gerar subprocessos, chamados "processos filhos". Estes também rodam isolados entre si e do processo original. Os processos permitem _multitarefa preemptiva_: o agendador do sistema operacional exerce __preempção__—isto é, suspende cada processo em execução periodicamente, para permitir que outro processos sejam executados. - Isto significa que um processo paralisado não pode paralisar todo o sistema—em teoria. + Isto significa que um processo travado não pode travar todo o sistema—em teoria. Thread:: - Uma((("threads", "definition of term"))) unidade de execução dentro de um único processo. + Uma((("threads", "definition of term"))) unidade de execução dentro de um processo. Quando um processo se inicia, ele tem uma única thread: a thread principal. Um processo pode chamar APIs do sistema operacional para criar mais threads para operar de forma concorrente. Threads dentro de um processo compartilham o mesmo espaço de memória, onde são mantidos objetos Python "vivos" (não serializados). @@ -133,14 +167,14 @@ Thread:: Corrotina:: Uma((("coroutines", "definition of term"))) função que pode suspender sua própria execução e continuar depois. - Em Python, _corrotinas clássicas_ são criadas a partir de funções geradoras, e _corrotinas nativas_ são definidas com `async def`. - A <> introduziu o conceito, e <> trata do uso de corrotinas nativas. - As corrotinas de Python normalmente rodam dentro de uma única thread, sob a supervisão de um _loop de eventos_, também na mesma thread. - Frameworks de programação assíncrona como _asyncio_, _Curio_, ou _Trio_ fornecem um loop de eventos e bibliotecas de apoio para E/S não-bloqueante baseado em corrotinas. + Em Python, corrotinas clássicas são criadas a partir de funções geradoras, e corrotinas nativas são definidas com `async def`. + A <> introduziu o conceito, e o <> trata do uso de corrotinas nativas. + As corrotinas de Python normalmente rodam dentro de uma única thread, sob a supervisão de um laço de eventos (_event loop_), também na mesma thread. + Frameworks de programação assíncrona como _asyncio_, _Curio_, ou _Trio_ fornecem um laço de eventos e bibliotecas de apoio para E/S não-bloqueante baseado em corrotinas. Corrotinas permitem _multitarefa cooperativa_: cada corrotina deve ceder explicitamente o controle com as palavras-chave `yield` ou `await`, para que outra possa continuar de forma concorrente (mas não em paralelo). - Isso significa que qualquer código bloqueante em uma corrotina bloqueia a execução do loop de eventos e de todas as outras corrotinas—ao contrário da _multitarefa preemptiva_ suportada por processos e threads. - Por outro lado, cada corrotina consome menos recursos para executar o mesmo trabalho de uma thread ou processo. + Isso significa que qualquer código bloqueante em uma corrotina bloqueia a execução do laço de eventos e de todas as outras corrotinas—ao contrário da _multitarefa preemptiva_ suportada por processos e threads. + Por outro lado, cada corrotina consome menos recursos para executar o mesmo trabalho que uma thread ou processo. Fila (_queue_):: Uma((("queues", "definition of term"))) estrutura de dados que nos permite adicionar e retirar itens, normalmente na ordem FIFO: o primeiro que entra é o primeiro que sai.footnote:[NT: "FIFO" é a sigla em inglês para "first in, first out".] @@ -150,14 +184,14 @@ Fila (_queue_):: Trava (_lock_):: Um((("locks, definition of term"))) objeto que as unidades de execução podem usar para sincronizar suas ações e evitar corrupção de dados. - Ao atualizar uma estrutura de dados compartilhada, o código em execução deve manter uma trava associada a tal estrutura. + Ao atualizar uma estrutura de dados compartilhada, o código em execução deve invocar uma função para obter uma trava associada a tal estrutura. Isso sinaliza a outras partes do programa que elas devem aguardar até que a trava seja liberada, antes de acessar a mesma estrutura de dados. O tipo mais simples de trava é conhecida também como mutex (de _mutual exclusion_, exclusão mútua). - A implementação de uma trava depende do modelo de concorrência subjacente. + O mecanismo para implementar uma trava depende do modelo de concorrência subjacente. Contenda (_contention_):: Disputa((("contention"))) por um recurso limitado. - Contenda por recursos ocorre quando múltiplas unidades de execução tentam acessar um recurso compartilhado — tal como uma trava ou o armazenamento. + Contenda por recursos ocorre quando múltiplas unidades de execução tentam acessar um recurso compartilhado—tal como uma trava ou unidade de armazenamento. Há também contenda pela CPU, quando processos ou threads de computação intensiva precisam aguardar até que o agendador do SO dê a eles uma quota do tempo da CPU. Agora vamos usar um pouco desse jargão para entender o suporte à concorrência no Python.((("", startref="CBterm19"))) @@ -166,7 +200,11 @@ Agora vamos usar um pouco desse jargão para entender o suporte à concorrência Veja((("concurrency models", "Python programming concepts", id="CMconcepts19"))) como os conceitos que acabamos de tratar se aplicam ao Python, em dez pontos: -. Cada instância do interpretador Python é um processo. Você pode iniciar processos Python adicionais usando as bibliotecas _multiprocessing_ ou _concurrent.futures_. A biblioteca _subprocess_ de Python foi projetada para rodar programas externos, independente das linguagens usadas para escrever tais programas. +. Cada instância do interpretador Python é um processo. Você pode iniciar +processos Python adicionais usando as bibliotecas `multiprocessing` ou +`concurrent.futures`. A biblioteca _subprocess_ de Python foi projetada para +rodar programas externos, independente das linguagens usadas para escrever tais +programas. . O interpretador Python usa uma única thread para rodar o programa do usuário e o coletor de lixo da memória. Você pode iniciar threads Python adicionais usando as bibliotecas _threading_ ou _concurrent.futures_. @@ -175,26 +213,37 @@ a((("Global Interpreter Lock (GIL)"))) Global Interpreter Lock (GIL) ou _Trava G A qualquer dado momento, apenas uma thread de Python pode reter a trava. Isso significa que apenas uma thread pode executar código Python a cada momento, independente do número de núcleos da CPU. -. Para evitar que uma thread de Python segure a GIL indefinidamente, o interpretador de bytecode de Python pausa a thread Python corrente a cada 5ms por default,footnote:[Chame https://docs.python.org/pt-br/3/library/sys.html#sys.getswitchinterval[`sys.getswitchinterval()`] para obter o intervalo; ele pode ser modificado com https://docs.python.org/pt-br/3/library/sys.html#sys.setswitchinterval[`sys.setswitchinterval(s)`].] liberando a GIL. +. Para evitar que uma thread de Python segure a GIL indefinidamente, o interpretador de bytecode de Python pausa a thread Python corrente a cada 5ms por default,footnote:[Chame https://fpy.li/a5[`sys.getswitchinterval()`] para obter o intervalo; ele pode ser modificado com https://fpy.li/ag[`sys.setswitchinterval(s)`].] liberando a GIL. A thread pode então tentar readquirir a GIL, mas se existirem outras threads esperando, o agendador do SO pode escolher uma delas para continuar. -. Quando escrevemos código Python, não temos controle sobre a GIL. Mas uma função embutida ou uma extensão escrita em C—ou qualquer linguagem que trabalhe no nível da API Python/C—pode liberar a GIL enquanto estiver rodando alguma tarefa longa. +. Quando escrevemos código Python, não temos controle sobre a GIL. Mas uma função embutida ou uma extensão escrita em C—ou qualquer linguagem que trabalhe no nível da API Python/C—pode liberar a GIL enquanto estiver rodando alguma tarefa demorada. -. Toda função na biblioteca padrão de Python que executa uma syscallfootnote:[Uma syscall é uma chamada a partir do código do usuário para uma função do núcleo (_kernel_) do sistema operacional. E/S, temporizadores e travas são alguns dos serviços do núcleo do SO disponíveis através de syscalls. Para aprender mais sobre esse tópico, leia o artigo https://pt.wikipedia.org/wiki/Chamada_de_sistema["Chamada de sistema"] na Wikipedia.] libera a GIL. Isso inclui todas as funções que executam operações de escrita e leitura em disco, escrita e leitura na rede, e `time.sleep()`. Muitas funções de uso intensivo da CPU nas bibliotecas NumPy/SciPy, bem como as funções de compressão e descompressão dos módulos `zlib` and `bz2`, também liberam a GIL.footnote:[Os módulos `zlib` e `bz2` são mencionados nominalmente em uma https://fpy.li/19-6[mensagem de Antoine Pitrou na python-dev] (EN). Pitrou contribuiu para a lógica da divisão de tempo da GIL no Python 3.2.] +. Toda função na biblioteca padrão de Python que executa uma _syscall_ libera a +GILfootnote:[Uma syscall é uma chamada a partir do código do usuário para uma +função do núcleo (_kernel_) do sistema operacional. E/S, temporizadores e travas +são alguns dos serviços do núcleo do SO disponíveis através de syscalls. Para +aprender mais sobre esse tópico, leia o artigo https://fpy.li/a6[«Chamada de +sistema»] na Wikipedia.]. Isto inclui todas as funções que executam operações de +escrita e leitura de arquivos, escrita e leitura na rede, e `time.sleep()`. +Muitas funções de uso intensivo da CPU nas bibliotecas NumPy/SciPy, bem como as +funções de compressão e descompressão dos módulos `zlib` and `bz2`, também +liberam a GIL.footnote:[Os módulos `zlib` e `bz2` são mencionados nominalmente +em uma https://fpy.li/19-6[mensagem de Antoine Pitrou na python-dev]. Pitrou +contribuiu para a lógica da divisão de tempo da GIL no Python 3.2.] -. Extensões que se integram no nível da API Python/C também podem iniciar outras threads não-Python, que não são afetadas pela GIL. Essas threads fora do controle da GIL normalmente não podem modificar objetos Python, mas podem ler e escrever na memória usada por objetos que suportam o https://fpy.li/pep3118[buffer protocol] (EN), como `bytearray`, `array.array`, e arrays do _NumPy_. +. Extensões binárias que se comunicam via API Python/C também podem iniciar outras threads não-Python, que não são afetadas pela GIL. Essas threads fora do controle da GIL normalmente não podem modificar objetos Python, mas podem ler e escrever na memória usada por objetos que suportam o https://fpy.li/pep3118[buffer protocol], como `bytearray`, `array.array`, e arrays do _NumPy_. -. O efeito da GIL sobre a programação de redes com threads Python é relativamente pequeno, porque as funções de E/S liberam a GIL, e ler e escrever na rede sempre implica em alta latência—comparado a ler e escrever na memória. Consequentemente, cada thread individual já passa muito tempo esperando mesmo, então sua execução pode ser intercalada sem maiores impactos no desempenho geral. Por isso David Beazley diz: "As threads de Python são ótimas em fazer nada."footnote:[Fonte: slide 106 do tutorial de Beazley, https://fpy.li/19-7["Generators: The Final Frontier" (EN)].] +. O efeito da GIL sobre a programação de redes com threads Python é relativamente pequeno, porque as funções de E/S liberam a GIL, e ler e escrever na rede sempre implica em alta latência—comparado a ler e escrever na memória. Consequentemente, cada thread individual já passa muito tempo esperando mesmo, então sua execução pode ser intercalada sem maiores impactos no desempenho geral. Por isso David Beazley diz: "As threads de Python são ótimas em fazer nada."footnote:[Fonte: slide 106 do tutorial de Beazley, https://fpy.li/19-7["Generators: The Final Frontier"].] -. As contendas pela GIL desaceleram as threads Python de processamento intensivo. Código sequencial de uma única thread é mais simples e mais rápido para esse tipo de tarefa. +. As contendas pela GIL desaceleram as threads Python que fazem processamento intensivo. Código sequencial de uma única thread é mais simples e mais rápido para este tipo de tarefa. . Para rodar código Python de uso intensivo da CPU em múltiplos núcleos, você precisa usar múltiplos processos Python. -Aqui está um bom resumo, da documentação do módulo `threading`:footnote:[Fonte: início do capítulo https://docs.python.org/pt-br/3/library/threading.html#["threading — Paralelismo baseado em Thread"] (EN).] +Aqui está um bom resumo, da documentação do módulo `threading`:footnote:[Fonte: início do capítulo https://fpy.li/a7["threading — Paralelismo baseado em Thread"].] [quote] ____ -*Detalhe de implementação do CPython*: Em CPython, devido à Trava Global do Interpretador, apenas uma thread pode executar código Python de cada vez (mas certas bibliotecas orientadas ao desempenho podem superar essa limitação). Se você quer que sua aplicação faça melhor uso dos recursos computacionais de máquinas com CPUs de múltiplos núcleos, aconselha-se usar `multiprocessing` ou +*Detalhe de implementação do CPython*: Em CPython, devido à Trava Global do Interpretador, apenas uma thread pode executar código Python de cada vez (mas certas bibliotecas de alto desempenho podem contornar esta limitação). Se você quer que sua aplicação faça melhor uso dos recursos computacionais de máquinas com CPUs de múltiplos núcleos, aconselha-se usar `multiprocessing` ou `concurrent.futures.ProcessPoolExecutor`. Entretanto, threads ainda são o modelo adequado se você deseja rodar múltiplas tarefas ligadas a E/S simultaneamente. @@ -204,8 +253,8 @@ O parágrafo anterior começa com "Detalhe de implementação do CPython" porque [NOTE] ==== -Essa seção não mencionou corrotinas, pois por default elas compartilham a mesma thread Python entre si e com o loop de eventos supervisor fornecido por um framework assíncrono. Assim, a GIL não as afeta. -É possível usar múltiplas threads em um programa assíncrono, mas a melhor prática é ter uma thread rodando o loop de eventos e todas as corrotinas, enquanto as threads adicionais executam tarefas específicas. +Esta seção não mencionou corrotinas, pois por default elas compartilham a mesma thread Python entre si e com o laço de eventos supervisor fornecido por um framework assíncrono. Assim, a GIL não as afeta. +É possível usar múltiplas threads em um programa assíncrono, mas a melhor prática é ter uma thread rodando o laço de eventos e todas as corrotinas, enquanto as threads adicionais executam tarefas específicas. Isso será explicado na <>. ==== @@ -227,16 +276,17 @@ Vamos começar com a versão `threading`, que pode parecer familiar se você já A((("spinners (loading indicators)", "created with threading", id="Sthread19")))((("threads", "spinners (loading indicators) using", id="Tspin19"))) ideia dos próximos exemplos é simples: iniciar uma função que pausa por 3 segundos enquanto anima caracteres no terminal, para deixar o usuário saber que o programa está "pensando" e não congelado. -O script cria uma animação giratória e mostra em sequência cada caractere da string `"\|/-"` -na mesma posição da tela.footnote:[O Unicode tem muitos caracteres úteis para animações simples, como por exemplo os https://fpy.li/19-11[padrões Braille]. Usei os caracteres ASCII `"\|/-"` para simplificar os exemplos do livro.] Quando a computação lenta termina, a linha com a animação é apagada e o resultado é apresentado: `Answer: 42`. +O script cria uma animação giratória mostrando em sequência cada caractere da string `'\|/-'` +na mesma posição da tela.footnote:[O Unicode tem muitos caracteres úteis para animações simples, como por exemplo os https://fpy.li/19-11[padrões Braille]. Usei os caracteres ASCII `'\|/-'` para simplificar os exemplos do livro.] Quando a computação lenta termina, a linha com a animação é apagada e o resultado é apresentado: `Answer: 42`. -<> mostra a saída de duas versões do exemplo: primeiro com threads, depois com corrotinas. Se você estiver longe do computador, imagine que o `\` na última linha está girando. +<> mostra a saída de duas versões do exemplo: primeiro com threads, depois com corrotinas. Se você estiver longe do computador, imagine que o +o hífen (`-`) na última linha está girando. [[spinner_fig]] -.Os scripts spinner_thread.py e spinner_async.py produzem um resultado similar: o repr do objeto spinner e o texto "Answer: 42". Na captura de tela, spinner_async.py ainda está rodando, e a mensagem animada "/ thinking!" é apresentada; aquela linha será substituída por "Answer: 42" após 3 segundos. +.Os scripts spinner_thread.py e spinner_async.py produzem um resultado similar: o repr do objeto spinner e o texto "Answer: 42". Na captura de tela, spinner_async.py ainda está rodando, e a mensagem animada "- thinking!" é apresentada; aquela linha será substituída por "Answer: 42" após 3 segundos. image::../images/flpy_1901.png[Captura de tela do console mostrando a saída dos dois exemplos.] -Vamos revisar o script _spinner_thread.py_ primeiro. O <> +Vamos estudar o script _spinner_thread.py_ primeiro. O <> lista as duas primeiras funções no script, e o <> mostra o restante. [[spinner_thread_top_ex]] @@ -247,11 +297,11 @@ lista as duas primeiras funções no script, e o <> most include::../code/19-concurrency/spinner_thread.py[tags=SPINNER_THREAD_TOP] ---- ==== -<1> Essa função vai rodar em uma thread separada. O argumento `done` é uma instância de `threading.Event`, um objeto simples para sincronizar threads. -<2> Isso é um loop infinito, porque `itertools.cycle` produz um caractere por vez, circulando pela string para sempre. -<3> O truque para animação em modo texto: mova o cursor de volta para o início da linha com o caractere de controle ASCII de retorno (`'\r'`). -<4> O método `Event.wait(timeout=None)` retorna `True` quando o evento é acionado por outra thread; se o `timeout` passou, ele retorna `False`. O tempo de 0,1s estabelece a "velocidade" da animação para 10 FPS. Se você quiser que uma animação mais rápida, use um tempo menor aqui. -<5> Sai do loop infinito. +<1> Esta função vai rodar em uma thread separada. O argumento `done` é uma instância de `threading.Event`, um objeto simples para sincronizar threads. +<2> Isto é um laço infinito, porque `itertools.cycle` produz um caractere por vez, circulando pela string para sempre. +<3> O truque para animação em modo texto: mova o cursor de volta para o início da linha com o caractere ASCII _carriage return_: `'\r'`. +<4> O método `Event.wait(timeout=None)` retorna `True` quando o evento é sinalizado por outra thread; se o `timeout` passou, ele retorna `False`. O tempo de 0,1s estabelece a velocidade da animação em 10 FPS (quadros por segundo). Se quiser uma animação mais rápida, use um tempo menor aqui. +<5> Sai do laço infinito. <6> Sobrescreve a linha de status com espaços para limpá-la e move o cursor de volta para o início. <7> `slow()` será chamada pela thread principal. Imagine que isso é uma chamada de API lenta, através da rede. Chamar `sleep` bloqueia a thread principal, mas a GIL é liberada e a thread da animação pode continuar. @@ -262,13 +312,14 @@ O primeiro detalhe importante deste exemplo é que `time.sleep()` bloqueia a thr As funções `spin` e `slow` serão executadas de forma concorrente. A thread principal—a única thread quando o programa é iniciado—vai iniciar uma nova thread para rodar `spin` e então chamará `slow`. -Propositalmente, não há qualquer API para terminar uma thread em Python. -É preciso enviar uma mensagem para encerrar uma thread. - -A classe `threading.Event` é o mecanismo de sinalização mais simples de Python para coordenar threads. -Uma instância de `Event` tem uma flag booleana interna que começa como `False`. -Uma chamada a `Event.set()` muda a flag para `True`. -Enquanto a flag for falsa, se uma thread chamar `Event.wait()`, ela será bloqueada até que outra thread chame `Event.set()`, quando então `Event.wait()` retorna `True`. +Propositalmente, não existe API para terminar uma thread em Python. +É preciso enviar algum sinal para encerrar uma thread. + +A classe `threading.Event` é o mecanismo de sinalização para coordenar threads mais simples no Python. +Uma instância de `Event` tem um atributo booleano interno que começa como `False`. +Uma chamada a `Event.set()` muda o atributo para `True`. +Enquanto o atributo for falso, se uma thread chamar `Event.wait()`, ela será bloqueada até que outra thread chame `Event.set()` +Então a próxima invocação de `Event.wait()` retornará `True`, sem esperar. Se um tempo de espera (_timeout_) em segundos é passado para `Event.wait(s)`, essa chamada retorna `False` quando aquele tempo tiver passado, ou retorna `True` assim que `Event.set()` é chamado por outra thread. A função `supervisor`, que aparece no <>, usa um `Event` para sinalizar para a função `spin` que ela deve encerrar. @@ -283,18 +334,18 @@ A função `supervisor`, que aparece no <>, usa um `Even include::../code/19-concurrency/spinner_thread.py[tags=SPINNER_THREAD_REST] ---- ==== -<1> `supervisor` irá retornar o resultado de `slow`. +<1> `supervisor` retornará o resultado de `slow`. <2> A instância de `threading.Event` é a chave para coordenar as atividades das threads `main` e `spinner`, como explicado abaixo. -<3> Para criar uma nova `Thread`, forneça uma função como argumento palavra-chave `target`, e argumentos posicionais para a `target` como uma tupla passada via `args`. +<3> Para criar uma nova `Thread`, forneça uma função como nomeado `target`, e argumentos posicionais para a `target` como uma tupla passada via `args`. <4> Mostra o objeto `spinner`. A saída é ``, onde `initial` é o estado da thread—significando aqui que ela ainda não foi iniciada. <5> Inicia a thread `spinner`. <6> Chama `slow`, que bloqueia a thread principal. Enquanto isso, a thread secundária está rodando a animação. -<7> Muda a flag de `Event` para `True`; isso vai encerrar o loop `for` dentro da função `spin`. +<7> Muda o estado de `Event` para `True`; isso vai encerrar o laço `for` dentro da função `spin`. <8> Espera até que a thread `spinner` termine. <9> Roda a função `supervisor`. Escrevi `main` e `supervisor` como funções separadas para deixar esse exemplo mais parecido com a versão `asyncio` no <>. -Quando a thread `main` aciona o evento `done`, a thread `spinner` acabará notando e encerrando corretamente. +Quando a thread `main` sinaliza o evento `done`, a thread `spinner` acabará notando e encerrará corretamente. Agora vamos ver um exemplo similar usando o pacote `multiprocessing`.((("", startref="Tspin19")))((("", startref="Sthread19"))) @@ -304,7 +355,7 @@ Agora vamos ver um exemplo similar usando o pacote `multiprocessing`.((("", star O((("spinners (loading indicators)", "created with multiprocessing package")))((("multiprocessing package"))) pacote `multiprocessing` permite executar tarefas concorrentes em processos Python separados em vez de threads. Quando você cria uma instância de `multiprocessing.Process`, todo um novo interpretador Python é iniciado como um processo filho, em segundo plano. Como cada processo Python tem sua própria GIL, isto permite que seu programa use todos os núcleos de CPU disponíveis—mas isso depende, em última instância, do agendador do sistema operacional. -Veremos os efeitos práticos em <>, mas para esse programa simples não faz grande diferença. +Veremos os efeitos práticos na <>, mas para este programa simples não faz grande diferença. O objetivo dessa seção é apresentar o `multiprocessing` e mostrar como sua API emula a API de `threading`, tornando fácil converter programas simples de threads para processos, como mostra o _spinner_proc.py_ (<>). @@ -315,42 +366,38 @@ O objetivo dessa seção é apresentar o `multiprocessing` e mostrar como sua AP ---- include::../code/19-concurrency/spinner_proc.py[tags=SPINNER_PROC_IMPORTS] -# [snip] the rest of spin and slow functions are unchanged from spinner_thread.py +# [snip] o resto das funções spin e slow são iguais a spinner_thread.py include::../code/19-concurrency/spinner_proc.py[tags=SPINNER_PROC_SUPER] # [snip] main function is unchanged as well ---- ==== -<1> A API básica de `multiprocessing` imita a API de `threading`, mas as dicas de tipo e o Mypy mostram essa diferença: `multiprocessing.Event` é uma função (e não uma classe como `threading.Event`) que retorna uma instância de `synchronize.Event`... +<1> A API básica de `multiprocessing` imita a API de `threading`, mas as dicas de tipo e o Mypy revelam esta diferença: `multiprocessing.Event` é uma função (e não uma classe como `threading.Event`) que retorna uma instância de `synchronize.Event`... <2> ...nos obrigando a importar `multiprocessing.synchronize`... <3> ...para escrever essa dica de tipo. <4> O uso básico da classe `Process` é similar ao da classe `Thread`. -<5> O objeto `spinner` aparece como `, -onde `14868` é o `id` do processo da instância de Python que está executando o -[.keep-together]#_spinner_proc.py_.# +<5> O objeto `spinner` aparece como ``, +onde `14868` é o `id` do processo filho: a outra instância de Python que está executando o +_spinner_proc.py_. As APIs básicas de `threading` e `multiprocessing` são similares, mas sua implementação é muito diferente, e `multiprocessing` tem uma API muito maior, para dar conta da complexidade adicional da programação multiprocessos. -Por exemplo, um dos desafios ao converter um programa de threads para processos é a comunicação entre processos, que estão isolados pelo sistema operacional e não podem compartilhar objetos Python. +Por exemplo, um dos desafios ao converter um programa de threads para processos é a comunicação entre processos, que são isolados pelo sistema operacional e não podem compartilhar objetos Python. Isso significa que objetos cruzando fronteiras entre processos precisam ser serializados e deserializados, criando custos adicionais. No <>, o único dado que cruza a fronteira entre os processos é o estado de `Event`, que é implementado com um semáforo de baixo nível do SO, no código em C sob o módulo `multiprocessing`.footnote:[O semáforo é um bloco fundamental que pode ser usado para implementar outros mecanismos de sincronização. Python fornece diferentes classes de semáforos para uso com threads, processos e corrotinas. Veremos o `asyncio.Semaphore` na <> (<>).] [TIP] ==== -Desde o Python 3.8, há o pacote https://docs.python.org/pt-br/3/library/multiprocessing.shared_memory.html[`multiprocessing.shared_memory`] (_memória compartilhada para acesso direto entre processos_) na biblioteca padrão, mas ele não suporta instâncias de classes definidas pelo usuário. -Além bytes nus, o pacote permite que processos compartilhem uma `ShareableList`, uma sequência mutável que pode manter um número fixo de itens dos tipos `int`, `float`, `bool`, e `None`, bem como `str` e `bytes`, até o limite de 10 MB por item. +Desde o Python 3.8, existe o pacote https://fpy.li/a8[`multiprocessing.shared_memory`] +na biblioteca padrão, mas ele não suporta instâncias de classes definidas pelo usuário. +Além bytes puros, o pacote permite que processos compartilhem uma `ShareableList`, uma sequência mutável que pode manter um número fixo de itens dos tipos `int`, `float`, `bool`, e `None`, bem como `str` e `bytes`, até o limite de 10 MB por item. Veja a documentação de -https://docs.python.org/pt-br/3/library/multiprocessing.shared_memory.html#multiprocessing.shared_memory.ShareableList[`ShareableList`] +https://fpy.li/a9[`ShareableList`] para mais detalhes. ==== -// [NOTE] -// ==== -// The semaphore is a fundamental building block that can be used to implement other synchronization mechanisms. Python provides different semaphore classes for use with threads, processes, and coroutines. We'll see `asyncio.Semaphore` in <> (<>). -// ==== - Agora vamos ver como o mesmo comportamento pode ser obtido com corrotinas em vez de threads ou processos. [[spinner_async_sec]] @@ -358,12 +405,12 @@ Agora vamos ver como o mesmo comportamento pode ser obtido com corrotinas em vez [NOTE] ==== -O <> é((("spinners (loading indicators)", "created using coroutines", id="Scoroutine19")))((("coroutines", "spinners (loading indicators) using", id="Cspin19"))) inteiramente dedicado à programação assíncrona com corrotinas. Essa seção é apenas um introdução rápida, para contrastar essa abordagem com as threads e os processos. Assim, vamos ignorar muitos detalhes. +O <> é((("spinners (loading indicators)", "created using coroutines", id="Scoroutine19")))((("coroutines", "spinners (loading indicators) using", id="Cspin19"))) inteiramente dedicado à programação assíncrona com corrotinas. Essa seção é apenas um introdução rápida, para contrastar essa abordagem com as threads e os processos. Por isso, vamos passar por cima de alguns detalhes. ==== -Alocar tempo da CPU para a execução de threads e processos é trabalho dos agendadores do SO. As corrotinas, por outro lado, são controladas por um loop de evento no nível da aplicação, que gerencia uma fila de corrotinas pendentes, as executa uma por vez, monitora eventos disparados por operações de E/S iniciadas pelas corrotinas, e passa o controle de volta para a corrotina correspondente quando cada evento acontece. -O loop de eventos e as corrotinas da biblioteca e as corrotinas do usuário todas rodam em uma única thread. -Assim, o tempo gasto em uma corrotina desacelera loop de eventos—e de todas as outras corrotinas. +Alocar tempo da CPU para a execução de threads e processos é trabalho dos agendadores do SO. As corrotinas, por outro lado, são controladas por um laço de evento no nível da aplicação, que gerencia uma fila de corrotinas pendentes, as executa uma por vez, monitora eventos disparados por operações de E/S iniciadas pelas corrotinas, e passa o controle de volta para a corrotina correspondente quando cada evento acontece. +O laço de eventos, as corrotinas da biblioteca, e as corrotinas do usuário rodam todas em uma única thread. +Assim, o tempo gasto em uma corrotina bloqueia o laço de eventos e todas as outras corrotinas. A versão com corrotinas do programa de animação é mais fácil de entender se começarmos por uma função `main`, e depois olharmos a `supervisor`. É isso que o <> mostra. @@ -376,45 +423,32 @@ A versão com corrotinas do programa de animação é mais fácil de entender se include::../code/19-concurrency/spinner_async.py[tags=SPINNER_ASYNC_START] ---- ==== -<1> `main` é a única função regular definida nesse programa—as outras são [.keep-together]#corrotinas#. -<2> A função`asyncio.run` inicia o loop de eventos para controlar a corrotina que irá em algum momento colocar as outras corrotinas em movimento. +<1> `main` é a única função normal definida nesse programa—as outras são corrotinas. +<2> A função `asyncio.run` inicia o laço de eventos para acionar a corrotina que em algum momento colocará as outras corrotinas em movimento. A função `main` ficará bloqueada até que `supervisor` retorne. -O valor de retorno de `supervisor` será o valor de retorno de `asyncio.run`. +O valor devolvido por `supervisor` será o valor devolvido por `asyncio.run`. <3> Corrotinas nativas são definidas com `async def`. <4> `asyncio.create_task` agenda a execução futura de `spin`, retornando imediatamente uma instância de `asyncio.Task`. <5> O `repr` do objeto `spinner` se parece com `>`. -<6> A palavra-chave `await` chama `slow`, bloqueando `supervisor` até que `slow` retorne. O valor de retorno de `slow` será atribuído a `result`. - +<6> A palavra-chave `await` chama `slow`, bloqueando `supervisor` até que `slow` retorne. O devolvido por `slow` é atribuído a `result`. <7> O método `Task.cancel` lança uma exceção `CancelledError` dentro da corrotina, como veremos no <>. -//// -PROD: Tech reviewer Caleb Hattingh reported: - -""" -the `async` word is not being syntax highlighted as a keyword, -it's rendered as a regular identifier, same as "spinner" and unlike "def" or "return" -I had the same problem in my book where the "async" keyword was not being formatted correctly. -IIRC I got OReilly tech support to fix it. -""" - -The same issue is affecting other recently introduced Python keywords: `await`, `match`, and `case`. -//// O <> demonstra as três principais formas de rodar uma corrotina: `asyncio.run(coro())`:: - É chamado a partir de uma função regular, para controlar o objeto corrotina, que é normalmente o ponto de entrada para todo o código assíncrono no programa, como a `supervisor` nesse exemplo. Esta chamada bloqueia a função até que `coro` retorne. O valor de retorno da chamada a `run()` é o que quer que `coro` retorne. + É invocada a partir de uma função normal, para acionar o objeto corrotina, que é o ponto de entrada para todo o código assíncrono no programa, como a `supervisor` nesse exemplo. Esta chamada bloqueia até que `coro` retorne. O resultado de `coro` será o resultado de `run`. `asyncio.create_task(coro())`:: - É chamado de uma corrotina para agendar a execução futura de outra corrotina. + É invocada dentro de uma corrotina para agendar a execução futura de outra corrotina. Essa chamada não suspende a corrotina atual. - Ela retorna uma instância de `Task`, um objeto que contém o objeto corrotina e fornece métodos para controlar e consultar seu estado. + Ela retorna imediatamente uma instância de `Task`, um objeto que contém o objeto corrotina e fornece métodos para controlar e consultar seu estado. `await coro()`:: - É chamado de uma corrotina para transferir o controle para o objeto corrotina retornado por `coro()`. Isso suspende a corrotina atual até que `coro` retorne. O valor da expressão `await` será é o que quer que `coro` retorne. + É invocada dentro de uma corrotina para transferir o controle para o objeto corrotina retornado por `coro()`. Isto suspende a corrotina atual até que `coro` retorne. O valor da expressão `await` será o que quer que `coro` devolva como resultado. [NOTE] ==== Lembre-se: invocar uma corrotina como `coro()` retorna imediatamente um objeto corrotina, mas não executa o corpo da função `coro`. -Acionar o corpo de corrotinas é a função do loop de eventos. +Acionar o corpo de corrotinas é a função do laço de eventos. ==== Vamos estudar agora as corrotinas `spin` e `slow` no <>. @@ -429,10 +463,10 @@ include::../code/19-concurrency/spinner_async.py[tags=SPINNER_ASYNC_TOP] ==== <1> Não precisamos do argumento `Event`, que era usado para sinalizar que `slow` havia terminado de rodar no _spinner_thread.py_ (<>). <2> Use `await asyncio.sleep(.1)` em vez de `time.sleep(.1)`, para pausar sem bloquear outras corrotinas. Veja o experimento após o exemplo. -<3> `asyncio.CancelledError` é lançada quando o método `cancel` é chamado na `Task` que controla essa corrotina. É hora de sair do loop. +<3> `asyncio.CancelledError` é lançada quando o método `cancel` é chamado na `Task` que controla essa corrotina. É hora de sair do laço. <4> A corrotina `slow` também usa `await asyncio.sleep` em vez de `time.sleep`. -===== Experimento: Estragando a animação para sublinhar um ponto +===== Experimento: quebrar a animação para revelar um fato Aqui está um experimento que recomendo para entender como _spinner_async.py_ funciona. Importe o módulo `time`, daí vá até a corrotina `slow` e substitua a linha `await asyncio.sleep(3)` por uma chamada a `time.sleep(3)`, como no <>. @@ -456,7 +490,7 @@ Quando você roda o experimento, você vê isso: . A animação nunca aparece. O programa trava por 3 segundos. . `Answer: 42` aparece e o programa termina. -Para entender o que está acontecendo, lembre-se que o código Python que está usando `asyncio` tem apenas um fluxo de execução, +Para entender o que está acontecendo, lembre-se que o código Python que está usando `asyncio` tem apenas uma unidade de execução, a menos que você inicie explicitamente threads ou processos adicionais. Isso significa que apenas uma corrotina é executada a qualquer dado momento. A concorrência é obtida controlando a passagem de uma corrotina a outra. @@ -470,26 +504,26 @@ No <>, vamos nos concentrar no que ocorre nas corro include::../code/19-concurrency/spinner_async_experiment.py[tags=SPINNER_ASYNC_EXPERIMENT] ---- ==== -<1> A tarefa `spinner` é criada para, no futuro, controlar a execução de `spin`. -<2> O display mostra que `Task` está "pending"(_em espera_). +<1> A tarefa `spinner` é criada para, no futuro, acionar a corrotina `spin`. +<2> O display mostra que `Task` está _pending_ (pendente, em espera). <3> A expressão `await` transfere o controle para a corrotina `slow`. <4> `time.sleep(3)` bloqueia tudo por 3 segundos; nada pode acontecer no programa, porque a thread principal está bloqueada—e ela é a única thread. O sistema operacional vai seguir com outras atividades. Após 3 segundos, `sleep` desbloqueia, e `slow` retorna. -<5> Logo após `slow` retornar, a tarefa `spinner` é cancelada. O fluxo de controle jamais chegou ao corpo da corrotina `spin`. +<5> Logo após `slow` retornar, a tarefa `spinner` é cancelada. O corpo da corrotina `spin` nunca foi acionado. O _spinner_async_experiment.py_ ensina uma lição importante, como explicado no box abaixo. [WARNING] ==== -Nunca use `time.sleep(…)` em corrotinas `asyncio`, a menos que você queira pausar o programa inteiro. +Nunca use `time.sleep(…)` em corrotinas assíncronas, a menos que você queira pausar o programa inteiro. Se uma corrotina precisa passar algum tempo sem fazer nada, ela deve `await asyncio.sleep(DELAY)`. -Isso devolve o controle para o loop de eventos de `asyncio`, que pode acionar outras corrotinas em espera.((("", startref="Cspin19")))((("", startref="Scoroutine19"))) +Isso devolve o controle para o laço de eventos do `asyncio`, que pode acionar outras corrotinas em espera.((("", startref="Cspin19")))((("", startref="Scoroutine19"))) ==== [[gevent_box]] .Greenlet e gevent **** Ao((("greenlet package"))) discutir concorrência com corrotinas, -é importante mencionar o pacote https://fpy.li/19-14[_greenlet_], +vale mencionar o pacote https://fpy.li/19-14[_greenlet_], que já existe há muitos anos e é muito usado.footnote:[Agradeço aos revisores técnicos Caleb Hattingh e Jürgen Gmach, que não me deixaram esquecer de _greenlet_ e _gevent_.] O pacote suporta multitarefa cooperativa através de corrotinas leves—chamadas _greenlets_—que não exigem qualquer sintaxe especial tal como `yield` ou `await`, @@ -508,7 +542,11 @@ https://fpy.li/gunicorn[_Gunicorn_]—mencionado em <>. ==== Supervisores lado a lado -O((("spinners (loading indicators)", "comparing supervisor functions"))) número de linhas de _spinner_thread.py_ e _spinner_async.py_ é quase o mesmo. As funções `supervisor` são o núcleo desses exemplos. Vamos compará-las mais detalhadamente. O <> mostra apenas a `supervisor` do <>. +O((("spinners (loading indicators)", "comparing supervisor functions"))) número +de linhas de _spinner_thread.py_ e _spinner_async.py_ é quase o mesmo. +As funções `supervisor` são a parte mais importante destes exemplos. Vamos compará-las mais +detalhadamente. O <> mostra apenas a `supervisor` do +<>. [[thread_supervisor_ex]] .spinner_thread.py: a função `supervisor` com threads @@ -550,14 +588,16 @@ Aqui está um resumo das diferenças e semelhanças notáveis entre as duas impl * Uma `Task` aciona um objeto corrotina, e uma `Thread` invoca um _callable_. * Uma corrotina passa o controle explicitamente com a palavra-chave `await` * Você não instancia objetos `Task` diretamente, eles são obtidos passando uma corrotina para `asyncio.create_task(…)`. -* Quando `asyncio.create_task(…)` retorna um objeto `Task`, -ele já esta agendado para rodar, mas uma instância de `Thread` precisa ser iniciada explicitamente através de uma chamada a seu método `start`. -* Na `supervisor` da versão com threads, `slow` é uma função comum e é invocada diretamente pela thread principal. Na versão assíncrona da `supervisor`, `slow` é uma corrotina guiada por `await`. -* Não há API para terminar uma thread externamente; em vez disso, é preciso enviar um sinal—como acionar o `done` no objeto `Event`. Para objetos `Task`, há o método de instância `Task.cancel()`, que dispara um `CancelledError` na expressão `await` na qual o corpo da corrotina está suspensa naquele momento. -* A corrotina `supervisor` deve ser iniciada com `asyncio.run` na [.keep-together]#função# `main`. +* Quando `asyncio.create_task(…)` devolve um objeto `Task`, +ele já está agendado para rodar, mas uma instância de `Thread` precisa ser iniciada explicitamente através de uma chamada a seu método `start`. +* Na `supervisor` da versão com threads, `slow` é uma função comum e é invocada diretamente pela thread principal. Na versão assíncrona da `supervisor`, `slow` é uma corrotina acionada por `await`. +* Não existe um método para terminar uma thread externamente; em vez disso, é preciso enviar um sinal—como invocar `set` no objeto `Event`. +Objetos `Task` oferecem o método `.cancel()`, que levantará um `CancelledError` na expressão `await` onde a corrotina está suspensa naquele momento. +* A corrotina `supervisor` é acionada com `asyncio.run` na função `main`. Essa comparação ajuda a entender como a concorrência é orquestrada com _asyncio_, -em contraste com como isso é feito com o módulo `Threading`, possivelmente mais familiar ao leitor. +em contraste com como isso é feito com o módulo `threading`, que pode ser mais familiar +para quem já usou threads em qualquer linguagem. Um último ponto relativo a threads versus corrotinas: quem já escreveu qualquer programa não-trivial com threads @@ -571,21 +611,30 @@ corrotinas são "sincronizadas" por definição: apenas uma delas está rodando em qualquer momento. Para entregar o controle, você usa `await` para passar o controle de volta ao agendador. Por isso é possível cancelar uma corrotina de forma segura: -por definição, uma corrotina só pode ser cancelada quando está suspensa em uma expressão `await`, então é possível realizar qualquer limpeza necessária capturando a exceção `CancelledError`. - -A chamada `time.sleep()` bloqueia mas não faz nada. Vamos agora experimentar com uma chamada de uso intensivo da CPU, para entender melhor a GIL, bem como o efeito de funções de processamento intensivo sobre código assíncrono.((("", startref="CMhello19"))) - - - - - -=== O real impacto da GIL - -Na((("concurrency models", "Global Interpreter Lock impact", id="CMimpact19")))((("Global Interpreter Lock (GIL)", id="gil19")))((("spinners (loading indicators)", "Global Interpreter Lock impact", id="SPgil19"))) versão com threads(<>), -você pode substituir a chamada `time.sleep(3)` na função `slow` por um requisição de cliente HTTP de sua biblioteca favorita, e a animação continuará girando. -Isso acontece porque uma biblioteca de programação para rede bem desenhada liberará a GIL enquanto estiver esperando uma resposta. - -Você também pode substituir a expressão `asyncio.sleep(3)` na corrotina `slow` para que `await` espere pela resposta de uma biblioteca bem desenhada de acesso assíncrono à rede, pois tais bibliotecas fornecem corrotinas que devolvem o controle para o loop de eventos enquanto esperam por uma resposta da rede. +por definição, uma corrotina só pode ser cancelada quando está suspensa em uma expressão `await`, então é possível realizar qualquer limpeza necessária capturando a exceção `CancelledError` naquele ponto da corrotina. + +A chamada `time.sleep()` bloqueia mas não faz nada. Vamos agora experimentar com +uma função intensiva em CPU, para entender melhor a GIL, bem como o +efeito de funções de processamento intensivo sobre código assíncrono.((("", +startref="CMhello19"))) + + +=== O verdadeiro impacto da GIL + +Na((("concurrency models", "Global Interpreter Lock impact", +id="CMimpact19")))((("Global Interpreter Lock (GIL)", id="gil19")))((("spinners (loading indicators)", +"Global Interpreter Lock impact", id="SPgil19"))) versão +com threads(<>), você pode trocar a chamada +`time.sleep(3)` na função `slow` por um requisição de cliente HTTP de sua +biblioteca favorita, e a animação continuará girando. Isso acontece porque +qualquer boa biblioteca de programação para rede vai liberar a GIL enquanto +estiver esperando uma resposta. Por padrão, toda operação de E/S em Python +libera a GIL. + +Você também pode trocar a expressão `asyncio.sleep(3)` na corrotina `slow` para +fazer `await` esperar a resposta de uma corrotina de biblioteca bem desenhada de +acesso assíncrono à rede. Tais bibliotecas implementam corrotinas para devolver +o controle para o laço de eventos enquanto esperam por uma resposta da rede. Enquanto isso, a animação seguirá girando. Com código de uso intensivo da CPU, a história é outra. @@ -593,7 +642,7 @@ Considere a função `is_prime` no <>, que retorna `True` se o argumento for um número primo, `False` se não for. [[def_is_prime_ex]] -.primes.py: uma checagem de números primos fácil de entender, do exemplo em https://docs.python.org/pt-br/3/library/concurrent.futures.html#processpoolexecutor-example[pass:[ProcessPool​Executor] na documentação de Python] +.primes.py: uma checagem de números primos fácil de entender, do exemplo em https://fpy.li/aa[pass:[ProcessPool​Executor] na documentação de Python] ==== [source, python] ---- @@ -610,7 +659,7 @@ Uma das partes da resposta é um pouco mais complicada (pelo menos para mim foi) [quote] ____ -O quê aconteceria à animação se fossem feitas as seguintes modificações, presumindo que `n = 5_000_111_000_222_021`—aquele mesmo número primo que minha máquina levou 3,3s para checar: +O quê aconteceria à animação se fossem feitas as seguintes modificações, presumindo que `n = 5_000_111_000_222_021`—aquele número primo que minha máquina levou 3,3s para checar: . Em _spinner_proc.py_, substitua `time.sleep(3)` com uma chamada a `is_prime(n)`? . Em _spinner_thread.py_, substitua `time.sleep(3)` com uma chamada a `is_prime(n)`? @@ -619,7 +668,7 @@ ____ Antes de executar o código ou continuar lendo, recomendo chegar as respostas por você mesmo. -Depois, copie e modifique os exemplos pass:[spinner_*.py] como [.keep-together]#sugerido#. +Depois, copie e modifique os exemplos pass:[spinner_*.py] como sugerido. Agora as respostas, da mais fácil para a mais difícil. @@ -634,21 +683,21 @@ A((("threads", "Global Interpreter Lock impact"))) animação é controlada por Não acertei essa resposta inicialmente: Esperava que a animação congelasse, porque superestimei o impacto da GIL. -Nesse exemplo em particular, a animação segue girando porque Python suspende a thread em execução a cada 5ms (por default), tornando a GIL disponível para outras threads pendentes. -Assim, a thread principal executando `is_prime` é interrompida a cada 5ms, permitindo à thread secundária acordar e executar uma vez o loop `for`, até chamar o método `wait` do evento `done`, quando então ela liberará a GIL. +Neste exemplo em particular, a animação segue girando porque Python suspende a thread em execução a cada 5ms (por default), tornando a GIL disponível para outras threads pendentes. +Assim, a thread principal executando `is_prime` é interrompida a cada 5ms, permitindo à thread secundária acordar e executar uma vez o laço `for`, até chamar o método `wait` do evento `done`, quando então ela liberará a GIL. A thread principal então pegará a GIL, e o cálculo de `is_prime` continuará por mais 5 ms. -Isso não tem um impacto visível no tempo de execução deste exemplo específico, porque a função `spin` rapidamente realiza uma iteração e libera a GIL, enquanto espera pelo evento `done`, então não há muita disputa pela GIL. +Isso não tem um impacto visível no tempo de execução deste exemplo específico, porque a função `spin` rapidamente realiza uma iteração e libera a GIL, enquanto espera pelo evento `done`, então não há muita contenda pela GIL. A thread principal executando `is_prime` terá a GIL na maior parte do tempo. Conseguimos nos safar usando threads para uma tarefa de processamento intensivo nesse experimento simples porque só temos duas threads: uma ocupando a CPU, e a outra acordando apenas 10 vezes por segundo para atualizar a animação. -Mas se você tiver duas ou mais threads disputando por mais tempo da CPU, seu programa será mais lento que um programa sequencial. +Mas se você tiver duas ou mais threads disputando mais tempo da CPU, seu programa será mais lento que um programa sequencial. ===== 3. Resposta para asyncio Se((("coroutines", "Global Interpreter Lock impact"))) você chamar `is_prime(5_000_111_000_222_021)` na corrotina `slow` do exemplo _spinner_async.py_, a animação nunca vai aparecer. -O efeito seria o mesmo [.keep-together]#que vimos# no <>, +O efeito seria o mesmo que vimos no <>, quando substituímos `await asyncio.sleep(3)` por `time.sleep(3)`: nenhuma animação. O fluxo de controle vai passar da `supervisor` para `slow`, e então para `is_prime`. @@ -658,7 +707,7 @@ O programa parecerá congelado por aproximadamente 3s, e então mostrará a resp .Soneca profunda com sleep(0) **** Uma((("spinners (loading indicators)", "keeping alive"))) maneira de manter a animação funcionando é reescrever `is_prime` como uma corrotina, -e periodicamente chamar `asyncio.sleep(0)` em uma expressão `await`, para passar o controle de volta para o loop de eventos, como no <>. +e periodicamente chamar `asyncio.sleep(0)` em uma expressão `await`, para passar o controle de volta para o laço de eventos, como no <>. [[example-19-11]] .spinner_async_nap.py: `is_prime` agora é uma corrotina @@ -670,10 +719,14 @@ include::../code/19-concurrency/primes/spinner_prime_async_nap.py[tags=PRIME_NAP ==== <1> Vai dormir a cada 50.000 iterações (porque o argumento `step` em `range` é 2). -O https://fpy.li/19-20[Issue #284] (EN) no repositório do `asyncio` tem uma discussão informativa sobre o uso de `asyncio.sleep(0)`. +O https://fpy.li/19-20[Issue #284] no repositório do `asyncio` trata do uso de `asyncio.sleep(0)`. -Entretanto, observe que isso vai tornar `is_prime` mais lento, e—mais importante—vai também tornar o loop de eventos e o seu programa inteiro mais lentos. -Quando eu usei `await asyncio.sleep(0)` a cada 100.000 iterações, a animação foi suave mas o programa rodou por 4,9s na minha máquina, quase 50% a mais que a função `primes.is_prime` rodando sozinha com o mesmo argumento (`5_000_111_000_222_021`). +Entretanto, observe que isso vai tornar `is_prime` mais lento, e—mais +importante—vai também atrasar o laço de eventos e tornar o programa inteiro mais +lento. Quando usei `await asyncio.sleep(0)` a cada 100.000 iterações, a +animação foi suave mas o programa rodou por 4,9s na minha máquina, quase 50% a +mais que a função `primes.is_prime` rodando sozinha com o mesmo argumento +(`5_000_111_000_222_021`). Usar `await asyncio.sleep(0)` deve ser considerada uma medida paliativa até o código assíncrono ser refatorado para delegar computações de uso intensivo da CPU para outro processo. Veremos uma forma de fazer isso com o https://fpy.li/19-21[`asyncio.loop.run_in_executor`], abordado no <>. Outra opção seria uma fila de tarefas, que vamos discutir brevemente na <>. @@ -686,15 +739,17 @@ Até aqui experimentamos com uma única chamada para uma função de uso intensi [NOTE] ==== + Escrevi((("concurrency models", "process pools", id="CMprocess19")))((("process pools", "example problem"))) -essa seção para mostrar o uso de múltiplos processos em cenários de uso intensivo de CPU, +esta seção para mostrar o uso de múltiplos processos em cenários de uso intensivo de CPU, e o padrão comum de usar filas para distribuir tarefas e coletar resultados. O <> apresenta uma forma mais simples de distribuir tarefas para processos: um `ProcessPoolExecutor` do pacote `concurrent.futures`, que internamente usa filas. + ==== -Nessa seção vamos escrever programas para checar se os números dentro de uma amostra de 20 inteiros são primos. Os números variam de 2 até 9.999.999.999.999.999—isto é, 10^16^ – 1, ou mais de 2^53^. -A amostra inclui números primos pequenos e grandes, bem como números compostos com fatores primos grandes e pequenos. +Nesta seção vamos escrever programas para checar se os números dentro de uma amostra de 20 inteiros são primos. Os números variam de 2 até 9.999.999.999.999.999—isto é, 10^16^ - 1, ou mais de 2^53^. +A amostra inclui números primos pequenos e grandes, bem como números compostos com fatores primos pequenos e grandes. O((("sequential.py program"))) programa _sequential.py_ fornece a linha base de desempenho. Aqui está o resultado de uma execução de teste: @@ -743,7 +798,7 @@ include::../code/19-concurrency/primes/sequential.py[] ---- ==== [role="pagebreak-before less_space"] -<1> A função `check` (na próxima chamada) retorna uma tupla `Result` com o valor booleano da chamada a `is_prime` e o tempo decorrido. +<1> A função `check` (logo abaixo) devolve uma tupla `Result` com o valor booleano da chamada a `is_prime` e o tempo decorrido. <2> `check(n)` chama `is_prime(n)` e calcula o tempo decorrido para retornar um `Result`. <3> Para cada número na amostra, chamamos `check` e apresentamos o resultado. <4> Calcula e mostra o tempo total decorrido. @@ -794,26 +849,42 @@ O tempo total neste caso é muito menor que a soma dos tempos decorridos para ca [NOTE] ==== -A função `multiprocessing.cpu_count()` retorna `12` no MacBook Pro que estou usando para escrever esse capítulo. Ele é na verdade um i7 com uma CPU de 6 núcleos, mas o SO informa 12 CPUs devido ao _hyperthreading_, uma tecnologia da Intel que executa duas threads por núcleo. Entretanto, _hyperthreading_ funciona melhor quando uma das threads não está trabalhando tão pesado quanto a outra thread no mesmo núcleo—talvez a primeira esteja parada, esperando por dados após uma perda de cache, e a outra está mastigando números. De qualquer forma, não há almoço grátis: este laptop tem o desempenho de uma máquina com 6 CPUs para atividades de processamento intensivo com pouco uso de memória—como essa checagem simples de números primos. + +A função `multiprocessing.cpu_count()` retorna `12` no MacBook Pro que usei para +escrever este capítulo. Ele é um i7 com uma CPU de 6 núcleos, mas o SO informa +12 CPUs devido ao _hyperthreading_, uma tecnologia da Intel que executa duas +threads por núcleo. Entretanto, _hyperthreading_ funciona melhor quando uma das +threads não está trabalhando tão pesado quanto a outra thread no mesmo +núcleo—talvez a primeira esteja parada, esperando por dados após a invalidação +do cache, enquanto a outra está mastigando números. De qualquer forma, não +existe almoço grátis: este laptop tem o desempenho de uma máquina com 6 CPUs +para atividades de processamento intensivo com pouco uso de memória, +como este exemplo. + ==== [[code_for_multicore_prime_sec]] -==== Código para o checador de números primos com múltiplos núcleos +==== Código do checador de primos usando múltiplos núcleos -Quando((("process pools", "code for multicore prime checker", id="PPmulticore19"))) delegamos processamento para threads e processos, nosso código não chama a função de trabalho diretamente, então não conseguimos simplesmente retornar um resultado. -Em vez disso, a função de trabalho é guiada pela biblioteca de threads ou processos, e por fim produz um resultado que precisa ser armazenado em algum lugar. -Coordenar threads ou processos de trabalho e coletar resultados são usos comuns de filas em programação concorrente—e também em sistemas distribuídos. +Quando((("process pools", "code for multicore prime checker", +id="PPmulticore19"))) delegamos processamento para threads e processos, nosso +código não invoca diretamente a função que realiza o trabalho, então não +conseguimos simplesmente devolver um resultado com `return`. Em vez disso, a +função de trabalho é acionada pela biblioteca de threads ou processos, e por fim +produz um resultado que precisa ser armazenado em algum lugar. Coordenar threads +ou processos de trabalho e coletar resultados são usos comuns de filas em +programação concorrente, e também em sistemas distribuídos. -Muito do código novo em _procs.py_ se refere a configurar e usar filas. O início do arquivo está no <>. +Muito do código novo em _procs.py_ se refere a configurar e usar filas. O início do arquivo está no <>. [WARNING] ==== `SimpleQueue` foi acrescentada a `multiprocessing` no Python 3.9. Se você estiver usando uma versão anterior de Python, -pode substituir `SimpleQueue` por `Queue` no <>. +pode substituir `SimpleQueue` por `Queue` no <>. ==== -[[primes_procs_top_ex]] +[[ex_primes_procs_top]] .procs.py: checagem de primos com múltiplos processos; importações, tipos, e funções ==== [source, python] @@ -822,44 +893,43 @@ include::../code/19-concurrency/primes/procs.py[tags=PRIMES_PROC_TOP] ---- ==== <1> Na tentativa de emular `threading`, `multiprocessing` fornece `multiprocessing.SimpleQueue`, mas esse é um método vinculado a uma instância pré-definida de uma classe de nível mais baixo, `BaseContext`. -Temos que chamar essa `SimpleQueue` para criar uma fila. Por outro lado, não podemos usá-la em dicas de tipo. +Temos que chamar essa `SimpleQueue` para criar uma fila. Mas não podemos usá-la em dicas de tipo. <2> `multiprocessing.queues` contém a classe `SimpleQueue` que precisamos para dicas de tipo. <3> `PrimeResult` inclui o número checado. Manter `n` junto com os outros campos do resultado simplifica a exibição mais tarde. <4> Isso é um apelido de tipo para uma `SimpleQueue` que a função `main` (<>) vai usar para enviar os números para os processos que farão a checagem. <5> Apelido de tipo para uma segunda `SimpleQueue` que vai coletar os resultados em `main`. Os valores na fila serão tuplas contendo o número a ser testado e uma tupla `Result`. <6> Isso é similar a _sequential.py_. <7> `worker` recebe uma fila com os números a serem checados, e outra para colocar os resultados. -<8> Nesse código, usei o número `0` como uma _pílula venenosa_: um sinal para que o processo encerre. Se `n` não é `0`, continue com o loop.footnote:[Nesse exemplo, `0` é uma sentinela conveniente. `None` também é comumente usado para essa finalidade, mas usar `0` simplifica a dica de tipo para `PrimeResult` e a implementação de `worker`.] +<8> Nesse código, usei o número `0` como um sinal para que o processo encerre. Se `n` não é `0`, o laço continua.footnote:[Nesse exemplo, `0` é um sinal conveniente. `None` também é bastante usado para este fim, mas o `0` simplifica a dica de tipo para `PrimeResult` e a implementação de `worker`.] <9> Invoca a checagem de número primo e coloca o `PrimeResult` na fila. -<10> Devolve um `PrimeResult(0, False, 0.0)`, para informar ao loop principal que esse processo terminou seu trabalho. +<10> Devolve um `PrimeResult(0, False, 0.0)`, para informar ao laço principal que esse processo terminou seu trabalho. <11> `procs` é o número de processos que executarão a checagem de números primos em paralelo. -<12> Coloca na fila `jobs` os números a serem checados. -<13> Cria um processo filho para cada `worker`. Cada um desses processos executará o loop dentro de sua própria instância da função `worker`, até encontrar um `0` na fila `jobs`. +<12> Coloca na fila `jobs`todos os números a serem checados. +<13> Cria um processo filho para cada `worker`. Cada um desses processos executará o laço dentro de sua própria instância da função `worker`, até encontrar um `0` na fila `jobs`. <14> Inicia cada processo filho. -<15> Coloca um `0` na fila de cada processo, para encerrá-los. +<15> Coloca um `0` na fila para cada processo, para encerrá-los. [[good_poison_pill_tip]] -.Loops, sentinelas e pílulas venenosas +.Laços, sentinelas e pílulas venenosas **** -A((("concurrency models", "indefinite loops and sentinels")))((("indefinite loops")))((("sentinels"))) -função `worker` no <> segue um modelo comum em programação concorrente: -percorrer indefinidamente um loop, pegando itens em um fila e processando cada um deles com uma função que realiza o trabalho real. -O loop termina quando a fila produz um valor sentinela. -Nesse modelo, a sentinela que encerra o processo é muitas vezes chamada de "pílula venenosa. - -`None` é bastante usado como valor sentinela, mas pode não ser adequado se existir a possibilidade dele aparecer entre os dados. -Chamar `object()` é uma maneira comum de obter um valor único para usar como sentinela. -Entretanto, isso não funciona entre processos, pois os objetos Python precisam ser serializados para comunicação entre processos. -Quando você `pickle.dump` e `pickle.load` -uma instância de `object`, a instância recuperada em `pickle.load` é diferentes da original: elas não serão iguais se comparadas. +A((("concurrency models", "indefinite laços and sentinels")))((("indefinite laços")))((("sentinels"))) +função `worker` no <> segue um modelo comum em programação concorrente: +rodar um laço continuamente, pegando itens de uma fila e processando cada um com uma função que realiza o trabalho real. +O laço termina quando `worker` retira da fila um valor sentinela. +Neste modelo, a sentinela que encerra o processo é muitas vezes chamada de _poison pill_ (pílula venenosa). + +`None` é bastante usado como valor sentinela, mas pode não ser adequado se for também um valor válido na série de dados. +Invocar `object()` é uma forma comum de obter um objeto único para usar como sentinela. +Entretanto, isto não funciona entre processos, pois os objetos Python precisam ser serializados para comunicação entre processos. +Quando você serializa um objeto com `pickle.dump` e desserializa com `pickle.load`, +a instância recuperada tem uma identidade diferente do original. Uma boa alternativa a `None` é o objeto embutido `Ellipsis` (também conhecido como `\...`), que sobrevive à serialização sem perder sua identidade.footnote:[Sobreviver à serialização sem perder nossa identidade é um ótimo objetivo de vida.] A biblioteca padrão de Python usa -https://fpy.li/19-22[muitos valores diferentes] (EN) como sentinelas. -A https://fpy.li/pep661[PEP 661—Sentinel Values] (EN) +https://fpy.li/19-22[«muitos valores diferentes»] como sentinelas. +A https://fpy.li/pep661[_PEP 661—Sentinel Values_] propõe um tipo sentinela padrão. -Em março de 2023, é apenas um rascunho. **** Agora vamos estudar a função `main` de _procs.py_ no <>. @@ -872,34 +942,41 @@ Agora vamos estudar a função `main` de _procs.py_ no <>. include::../code/19-concurrency/primes/procs.py[tags=PRIMES_PROC_MAIN] ---- ==== -<1> Se nenhum argumento é dado na linha de comando, define o número de processos como o número de núcleos na CPU; caso contrário, cria quantos processos forem passados no primeiro [.keep-together]#argumento.# -<2> `jobs` e `results` são as filas descritas no <>. -<3> Inicia `proc` processos para consumir `jobs` e informar `results`. -<4> Recupera e exibe os resultados; `report` está definido em pass:[6]. +<1> Se nenhum argumento é dado na linha de comando, +define o número de processos como o número de núcleos na CPU; +caso contrário, cria a quantidade de processos indicada no primeiro argumento. +<2> `jobs` e `results` são as filas descritas no <>. +<3> Inicia `proc` processos para consumir `jobs` e computar `results`. +<4> Recupera e exibe os resultados; `report` está definido em `⑥`. <5> Mostra quantos números foram checados e o tempo total decorrido. <6> Os argumentos são o número de `procs` e a fila para armazenar os resultados. -<7> Percorre o loop até que todos os processos terminem. -<8> Obtém um `PrimeResult`. Chamar `.get()` em uma fila deixa o processamento bloqueado até que haja um item na fila. Também é possível fazer isso de forma não-bloqueante ou estabelecer um timeout. Veja os detalhes na documentação de https://docs.python.org/pt-br/3/library/queue.html#queue.SimpleQueue.get[`SimpleQueue.get`]. +<7> Percorre o laço até que todos os processos terminem. +<8> Obtém um `PrimeResult`. Chamar `.get()` em uma fila deixa o processamento bloqueado até que haja um item na fila. Também é possível fazer isso de forma não-bloqueante ou estabelecer um timeout. Veja os detalhes na documentação de https://fpy.li/ab[`SimpleQueue.get`]. <9> Se `n` é zero, então um processo terminou; incrementa o contador `procs_done`. <10> Senão, incrementa o contador `checked` (para acompanhar os números checados) e mostra os resultados. -Os resultados não vão retornar na mesma ordem em que as tarefas foram submetidas. Por isso for necessário incluir `n` em cada tupla `PrimeResult`. -De outra forma eu não teria como saber que resultado corresponde a cada número. +Os resultados não vão retornar na mesma ordem em que as tarefas foram submetidas. +Por isso inclui `n` em cada tupla `PrimeResult`. +De outra forma não teríamos como saber qual resultado corresponde a cada número. Se o processo principal terminar antes que todos os subprocessos finalizem, -podem surgir relatórios de rastreamento (_tracebacks_) confusos, com referências a exceções de `FileNotFoundError` causados por uma trava interna em `multiprocessing`. -Depurar código concorrente é sempre difícil, e depurar código baseado no `multiprocessing` é ainda mais difícil devido a toda a complexidade por trás da fachada emulando threads. +podemos ter _tracebacks_ difíceis de analisar, +com referências a exceções de `FileNotFoundError` causados por uma trava interna em `multiprocessing`. +Depurar código concorrente é sempre difícil, +e depurar código baseado no `multiprocessing` é ainda mais difícil devido a toda a complexidade por trás da fachada que imita threads. Felizmente, o `ProcessPoolExecutor` que veremos no <> é mais fácil de usar e mais robusto. [NOTE] ==== -Agradeço((("race conditions"))) ao leitor Michael Albert, que notou que o código que publiquei durante o pré-lançamento tinha uma https://pt.wikipedia.org/wiki/Condi%C3%A7%C3%A3o_de_corrida[_"condição de corrida" (race condition)_] no <>. -Uma condição de corrida (ou de _concorrência_) é um bug que pode ou não aparecer, dependendo da ordem das ações realizadas pelas unidades de execução concorrentes. +Agradeço((("race conditions"))) ao leitor Michael Albert, que notou que o código que publiquei +durante o pré-lançamento tinha uma _race condition_ (https://fpy.li/ac[condição de corrida]) no <>. +Uma condição de corrida é um bug que pode ou não ocorrer, +dependendo da ordem das ações realizadas pelas unidades de execução concorrentes. Se "A" acontecer antes de "B", tudo segue normal; -mas se "B" acontecer antes, surge um erro. -Essa é a corrida. +mas se "B" acontecer antes, acontece um erro. +Esta é a corrida. -Se você estiver curiosa, esse diff mostra o bug e sua correção: +Se tiver curiosidade, este diff mostra o bug e sua correção: https://fpy.li/19-25[_example-code-2e/commit/2c123057_]—mas note que depois eu refatorei o exemplo para delegar partes de `main` para as funções `start_jobs` e `report`. Há um arquivo https://fpy.li/19-26[_README.md_] na mesma pasta explicando o problema e a solução.((("", startref="PPmulticore19"))) @@ -908,7 +985,8 @@ na mesma pasta explicando o problema e a solução.((("", startref="PPmulticore1 ==== Experimentando com mais ou menos processos -Você((("process pools", "varying process numbers"))) poderia tentar rodar _procs.py_, passando argumentos que modifiquem o número de processos filho. Por exemplo, este comando... +Você((("process pools", "varying process numbers"))) pode experimentar rodar _procs.py_, +passando argumentos que modifiquem o número de processos filhos. Por exemplo, este comando... [source] @@ -916,7 +994,9 @@ Você((("process pools", "varying process numbers"))) poderia tentar rodar _proc $ python3 procs.py 2 ---- -...vai iniciar dois subprocessos, produzindo os resultados quase duas vezes mais rápido que _sequential.py_—se a sua máquina tiver uma CPU com pelo menos dois núcleos e não estiver ocupada rodando outros [.keep-together]#programas#. +...vai iniciar dois subprocessos, produzindo os resultados quase duas vezes mais rápido +que _sequential.py_—se a sua máquina tiver uma CPU com pelo menos dois núcleos +e não estiver muito ocupada rodando outros programas. Rodei _procs.py_ 12 vezes, usando de 1 a 20 subprocessos, totalizando 240 execuções. Então calculei a mediana do tempo para todas as execuções com o mesmo número de subprocessos, e desenhei a <>. @@ -931,11 +1011,23 @@ Eu não esperava e não sei explicar porque o desempenho melhorou com 11 process com tempos medianos apenas ligeiramente maiores que o menor tempo mediano com 6 processos. [[thread_non_solution_sec]] -==== Não-solução baseada em threads - -Também((("process pools", "thread-based nonsolution")))((("threads", "thread-based process pools"))) escrevi _threads.py_, uma versão de _procs.py_ usando `threading` em vez de `multiprocessing`. O código é muito similar quando convertemos exemplo simples entre as duas APIs.footnote:[See https://fpy.li/19-27[_19-concurrency/primes/threads.py_] no https://fpy.li/code[repositório de código do _Fluent Python_].] Devido à GIL e à natureza de processamento intensivo de `is_prime`, a versão com threads é mais lenta que a versão sequencial do <>, e fica mais lenta conforme aumenta o número de threads, por causa da disputa pela CPU e o custo da mudança de contexto. Para passar de uma thread para outra, o SO precisa salvar os registradores da CPU e atualizar o contador de programas e o ponteiro do stack, disparando efeitos colaterais custosos, -como invalidar os caches da CPU e talvez até trocar páginas de memória. -footnote:[Para saber mais, consulte https://pt.wikipedia.org/wiki/Troca_de_contexto["Troca de contexto"] na Wikipedia.] +==== Solução equivocada baseada em threads + +Também((("process pools", "thread-based nonsolution")))((("threads", "thread-based process pools"))) +escrevi _threads.py_, uma versão de _procs.py_ usando `threading` em vez de +`multiprocessing`. O código é muito similar quando convertemos exemplo simples +entre as duas APIs.footnote:[See +https://fpy.li/19-27[_19-concurrency/primes/threads.py_] no +https://fpy.li/code[repositório de código do _Fluent Python_].] +Devido à GIL e à +natureza de processamento intensivo de `is_prime`, a versão com threads é mais +lenta que a versão sequencial do <>, e fica mais lenta +conforme aumenta o número de threads, por causa da disputa pela CPU e o custo da +mudança de contexto. Para passar de uma thread para outra, o SO precisa salvar +os registradores da CPU e atualizar o contador de programas e o ponteiro do +stack, disparando efeitos colaterais custosos, como invalidar os caches da CPU e +talvez até trocar páginas de memória. footnote:[Para saber mais, consulte +https://fpy.li/ad["Troca de contexto"] na Wikipedia.] Os dois próximos capítulos tratam de mais temas ligados à programação concorrente em Python, usando a biblioteca de alto nível _concurrent.futures_ para gerenciar threads e processos (<>) e a biblioteca _asyncio_ para programação assíncrona (<>). @@ -950,37 +1042,60 @@ ____ [[py_in_multicore_world_sec]] === Python no mundo multi-núcleo. -Considere((("Python", "functioning with multicore processors", id="Pmulti19")))((("concurrency models", "multicore processors and", id="CMmulti19")))((("multicore processing", "increased availability of"))) a seguinte passagem, do artigo muito citado https://fpy.li/19-29["The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software" (_O Almoço Grátis Acabou: Uma Virada Fundamental do Software em Direção à Concorrência_) (EN) de Herb Sutter]: +Considere((("Python", "functioning with multicore processors", +id="Pmulti19")))((("concurrency models", "multicore processors and", +id="CMmulti19")))((("multicore processing", "increased availability of"))) a +seguinte passagem, do artigo muito citado +https://fpy.li/19-29[_The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software_] +(O Almoço Grátis Acabou: Uma Virada Fundamental do Software em Direção à Concorrência_) de Herb +Sutter, publicado em 2005: [quote] ____ -Os mais importantes fabricantes e arquiteturas de processadores, da Intel e da AMD até a Sparc e o PowerPC, esgotaram o potencial da maioria das abordagens tradicionais de aumento do desempenho das CPUs. -Ao invés de elevar a frequência do _clock_ [dos processadores] e a taxa de transferência das instruções encadeadas a níveis cada vez maiores, eles estão se voltando em massa para o hyper-threading (hiperprocessamento) e para arquiteturas multi-núcleo. -Março de 2005. [Disponível online]. + +Os mais importantes fabricantes e arquiteturas de processadores, da Intel e da +AMD até a Sparc e o PowerPC, esgotaram o potencial da maioria das abordagens +tradicionais de aumento do desempenho das CPUs. Ao invés de elevar a frequência +do _clock_ [dos processadores] e a taxa de transferência das instruções +encadeadas a níveis cada vez maiores, eles estão se voltando em massa para o +hyper-threading (hiperprocessamento) e para arquiteturas multi-núcleo. + ____ O que Sutter chama de "almoço grátis" era a tendência do software ficar mais rápido sem qualquer esforço adicional por parte dos desenvolvedores, -porque as CPUs estavam executando código sequencial cada vez mais rápido, ano após ano. -Desde 2004 isso não é mais verdade: +porque as CPUs executavam código sequencial cada vez mais rápido, +com avanços exponenciais a cada nova geração. +Desde 2004 isto não é mais verdade: a frequência dos _clocks_ das CPUs e as otimizações de execução atingiram um platô, e agora qualquer melhoria significativa no desempenho precisa vir do aproveitamento de múltiplos núcleos ou do _hyperthreading_, avanços que só beneficiam código escrito para execução concorrente. -A história de Python começa no início dos anos 1990, -quando as CPUs ainda estavam ficando exponencialmente mais rápidas na execução de código sequencial. -Naquele tempo não se falava de CPUs com múltiplos núcleos, exceto para supercomputadores. -Assim, a decisão de ter uma ((("Global Interpreter Lock (GIL)"))) GIL era óbvia. -A GIL torna o interpretador rodando em um único núcleo mais rápido, e simplifica sua implementação.footnote:[Provavelmente foram essas mesmas razões que levaram o criador de Ruby, Yukihiro Matsumoto, a também usar uma GIL no seu interpretador.] -A GIL também torna mais fácil escrever extensões simples com a API Python/C. +A história de Python começa no início dos anos 1990, quando as CPUs ainda +estavam ficando exponencialmente mais rápidas na execução de código sequencial. +Naquele tempo não se falava de CPUs com múltiplos núcleos, exceto para +supercomputadores. Assim, a decisão de ter uma +((("Global Interpreter Lock (GIL)"))) GIL era óbvia. +A GIL torna mais leve e rápido o interpretador rodando +em um único núcleo, e simplifica sua implementação.footnote:[Provavelmente foram +estas razões que levaram o criador de Ruby, Yukihiro Matsumoto, a também +usar uma GIL no seu interpretador.] A GIL também torna mais fácil escrever +extensões simples com a API Python/C. [NOTE] ==== -Escrevi "extensões simples" porque uma extensão não é obrigada a lidar com a GIL. -Uma função escrita em C ou Fortran pode ser centenas de vezes mais rápida que sua equivalente em Python.footnote:[Na faculdade, como exercício, tive que implementar o algorítimo de compressão LZW em C. Mas antes escrevi o código em Python, para verificar meu entendimento da especificação. A versão C foi cerca de 900 vezes mais rápida.] -Assim, a complexidade adicional de liberar a GIL para tirar proveito de CPUs multi-núcleo pode, em muitos casos, não ser necessária. -Então podemos agradecer à GIL por muitas das extensões disponíveis em Python—e isso é certamente uma das razões fundamentais da popularidade da linguagem hoje. + +Escrevi "extensões simples" porque uma extensão não é obrigada a lidar com a +GIL. Uma função escrita em C ou Fortran pode ser centenas de vezes mais rápida +que sua equivalente em Python.footnote:[Na faculdade, como exercício, tive que +implementar o algorítimo de compressão LZW em C. Mas antes escrevi o código em +Python, para verificar meu entendimento da especificação. A versão C foi cerca +de 900 vezes mais rápida.] Assim, a complexidade adicional de liberar a GIL para +tirar proveito de CPUs multi-núcleo pode ser desnecessária em muitos casos. +Então podemos agradecer à GIL por muitas das extensões disponíveis em Python—e +isso é certamente uma das razões fundamentais da popularidade da linguagem hoje. + ==== Apesar da GIL, Python está cada vez mais popular entre aplicações que exigem execução concorrente ou paralela, @@ -992,19 +1107,26 @@ e desenvolvimento de aplicações para servidores no mundo do processamento dist ==== Administração de sistemas O ((("multicore processing", "system administration")))((("system administration"))) Python é largamente utilizado para gerenciar grandes frotas de servidores, roteadores, balanceadores de carga e armazenamento conectado à rede (_network-attached storage_ ou NAS). -Ele é também a opção preferencial para redes definidas por software (SND, _software-defined networking_) e hacking ético. +Ele é também a opção preferencial para redes definidas por software (SDN, _software-defined networking_) e hacking ético. Os maiores provedores de serviços na nuvem suportam Python através de bibliotecas e tutoriais de sua própria autoria ou da autoria de suas grande comunidades de usuários da linguagem. -Nesse campo, scripts Python automatizam tarefas de configuração, emitindo comandos a serem executados pelas máquinas remotas, então raramente há operações limitadas pela CPU. -Threads ou corrotinas são bastante adequadas para tais atividades. -Em particular, o pacote `concurrent.futures`, que veremos no <>, pode ser usado para realizar as mesmas operações em muitas máquinas remotas ao mesmo tempo, sem grande complexidade. +Nesse campo, scripts Python automatizam tarefas de configuração, emitindo +comandos a serem executados pelas máquinas remotas, então raramente há operações +limitadas pela CPU da máquina do administrador de sistemas. Threads ou +corrotinas são bastante adequadas para tais atividades. Em particular, o pacote +`concurrent.futures`, que veremos no <>, pode ser usado para +realizar as mesmas operações em muitas máquinas remotas ao mesmo tempo, sem +grande complexidade. Além da biblioteca padrão, há muito projetos populares baseados em Python para gerenciar clusters (_agrupamentos_) de servidores: -ferramentas como o https://fpy.li/19-30[_Ansible_] (EN) e o https://fpy.li/19-31[_Salt_] (EN), -bem como bibliotecas como a https://fpy.li/19-32[_Fabric_] (EN). - -Há também um número crescente de bibliotecas para administração de sistemas que suportam corrotinas e `asyncio`. -Em 2016, a https://fpy.li/19-33[equipe de Engenharia de Produção] (EN) do Facebook relatou: +ferramentas como o +https://fpy.li/19-30[_Ansible_] e o +https://fpy.li/19-31[_Salt_], +bem como bibliotecas como a +https://fpy.li/19-32[_Fabric_]. + +Há também um número crescente de bibliotecas para administração de sistemas que suportam corrotinas e _asyncio_. +Em 2016, a https://fpy.li/19-33[equipe de Engenharia de Produção] do Facebook relatou: "Estamos cada vez mais confiantes no AsyncIO, introduzido no Python 3.4, e vendo ganhos de desempenho imensos conforme migramos as bases de código de Python 2." @@ -1020,62 +1142,78 @@ Em 2021, o ecossistema de ciência de dados de Python já incluía algumas ferra https://fpy.li/19-34[Project Jupyter]:: Duas((("Project Jupyter"))) interfaces para navegadores—Jupyter Notebook e JupyterLab—que permitem aos usuários rodar e documentar código analítico, potencialmente sendo executado através da rede em máquinas remotas. - Ambas são aplicações híbridas Python/Javascript, suportando kernels de processamento escritos em diferentes linguagens, todos integrados via ZeroMQ—uma biblioteca de comunicação por mensagens assíncrona para aplicações distribuídas. - O nome _Jupyter_, inclusive remete a Julia, Python, e R, as três primeiras linguagens suportadas pelo Notebook. - O rico ecossistema construído sobre as ferramentas Jupyter incluí o https://fpy.li/19-35[Bokeh], uma poderosa biblioteca de visualização iterativa que permite aos usuários navegarem e interagirem com grandes conjuntos de dados ou um fluxo de dados continuamente atualizado, graças ao desempenho dos navegadores modernos e seus interpretadores JavaScript. + Ambas são aplicações híbridas Python/Javascript, suportando servidores de processamento (chamados _kernel_) escritos em diferentes linguagens, todos integrados via ZeroMQ—uma biblioteca de comunicação por mensagens assíncrona para aplicações distribuídas. + O nome _Jupyter_ remete a Julia, Python, e R, as três primeiras linguagens suportadas pelo Notebook. + O rico ecossistema construído sobre as ferramentas Jupyter incluí o https://fpy.li/19-35[_Bokeh_], uma poderosa biblioteca de visualização iterativa que permite aos usuários navegarem e interagirem com grandes conjuntos de dados ou um fluxo de dados continuamente atualizado, graças ao desempenho dos navegadores modernos e seus interpretadores JavaScript. https://fpy.li/19-36[TensorFlow] e https://fpy.li/19-37[PyTorch]:: Estes((("TensorFlow")))((("PyTorch"))) são os principais frameworks de aprendizagem profunda (_deep learning_), de acordo com o - https://fpy.li/19-38[relatório de Janeiro de 2021 da O'Reilly's] (EN) + https://fpy.li/19-38[relatório de Janeiro de 2021 da O'Reilly] medido pela utilização em 2020. Os dois projetos são escritos em {cpp}, e conseguem se beneficiar de múltiplos núcleos, GPUs e clusters. Eles também suportam outras linguagens, mas Python é seu maior foco e é usado pela maioria de seus usuários. - O TensorFlow foi criado e é usado internamente pelo Google; O Pythorch pelo Facebook. + O TensorFlow foi criado e é usado internamente pelo Google; o PyTorch pelo Facebook. https://fpy.li/dask[Dask]:: - Uma((("Dask"))) biblioteca de computação paralela que consegue delegar para processos locais ou um cluster de máquinas, - "testado em alguns dos maiores supercomputadores do mundo"—como seu https://fpy.li/dask[site] (EN) afirma. - O Dask oferece APIs que emulam muito bem a NumPy, o pandas, e o scikit-learn—hoje as mais populares bibliotecas em ciência de dados e aprendizagem de máquina. - O Dask pode ser usado a partir do JupyterLab ou do Jupyter Notebook, e usa o Bokeh - não apenas para visualização de dados mas também para um quadro interativo mostrando o fluxo de dados e o processamento entre processos/máquinas quase em tempo real. - O Dask é tão impressionante que recomento assistir um vídeo tal como esse, https://fpy.li/19-39[15-minute demo], onde Matthew Rocklin—um mantenedor do projeto—mostra o Dask mastigando dados em 64 núcleos distribuídos por 8 máquinas EC2 na AWS. + Uma((("Dask"))) biblioteca de computação paralela que delega tarefas para processos locais ou um cluster de máquinas, + "testado em alguns dos maiores supercomputadores do mundo"—como seu https://fpy.li/dask[site] afirma. + O Dask oferece APIs que emulam muito bem a NumPy, a pandas, e a scikit-learn—hoje as mais populares bibliotecas em ciência de dados e aprendizagem de máquina. + O Dask pode ser usado a partir do JupyterLab ou do Jupyter Notebook, e usa o _Bokeh_ + não apenas para visualização de dados mas também para um painel interativo (_dashboard_) que mostra o fluxo de dados e a carga de processamento entre processos/máquinas quase em tempo real. + O Dask é tão impressionante que recomento assistir o https://fpy.li/19-39[vídeo de demonstração], onde o mantenedor Matthew Rocklin apresenta o Dask mastigando dados em 64 núcleos distribuídos por 8 máquinas EC2 na AWS. -Estes são apenas alguns exemplos para ilustrar como a comunidade de ciência de dados está criando soluções que extraem o melhor de Python e superam as limitações do runtime do CPython. +Estes são apenas alguns exemplos para ilustrar como a comunidade de ciência de +dados está criando soluções que aproveitam o melhor de Python e superam as +limitações do runtime do CPython. [[server_side_sec]] -==== Desenvolvimento de aplicações server-side para Web/Computação Móvel +==== Servidires para Web/Computação Móvel -O((("multicore processing", "server-side web/mobile development")))((("server-side web/mobile development")))((("web/mobile development"))) Python é largamente utilizado em aplicações Web e em APIs de apoio a aplicações para computação móvel no servidor. -Como o Google, o YouTube, o Dropbox, o Instagram, o Quora, e o Reddit—entre outros—conseguiram desenvolver aplicações de servidor em Python que atendem centenas de milhões de usuários 24X7? Novamente a resposta vai bem além do que Python fornece "de fábrica". -Antes de discutir as ferramentas necessárias para usar Python larga escala, preciso citar uma advertência da _Technology Radar_ da Thoughtworks: +O((("multicore processing", "server-side web/mobile development")))((("server-side web/mobile development")))((("web/mobile development"))) +Python é largamente utilizado em aplicações Web e em APIs de +apoio a aplicações para computação móvel no servidor. Como o Google, o YouTube, +o Dropbox, o Instagram, o Quora, e o Reddit—entre outros—conseguiram desenvolver +aplicações de servidor em Python que atendem centenas de milhões de usuários +todo dia? Novamente a resposta vai bem além do que Python fornece em sua biblioteca padrão. +Antes de discutir as ferramentas necessárias para usar Python em larga escala, +preciso citar uma advertência do relatório _Technology Radar_ da consultoria Thoughtworks: [quote] ____ -*Inveja de alto desempenho/inveja de escala da web* +*Inveja de alto desempenho/inveja de escala da Web* + +Vemos muitas equipes se metendo em apuros por escolher ferramentas, frameworks +ou arquiteturas complexas, porque eles "talvez precisem de escalabilidade". +Empresas como Twitter e Netflix precisam suportar cargas extremas, então +precisam dessas arquiteturas, mas elas também têm equipes de desenvolvimento +numerosas, com anos de experiência, capazes de lidar com a complexidade. A +maioria das situações não exige estas façanhas de engenharia; as equipes devem +manter sua _inveja da escalabilidade na web_ sob controle, e preferir soluções +simples que ainda assim fazem o que precisa ser feito.footnote:[Fonte: +Thoughtworks Technology Advisory Board, https://fpy.li/19-40[_Technology +Radar—November 2015_].] -Vemos muitas equipes se metendo em apuros por escolher ferramentas, frameworks ou arquiteturas complexas, porque eles "talvez precisem de escalabilidade". -Empresas como o Twitter e a Netflix precisam aguentar cargas extremas, então precisam dessas arquiteturas, mas elas também tem equipes de desenvolvimento extremamente habilitadas, capazes de lidar com a complexidade. -A maioria das situações não exige essas façanhas de engenharia; as equipes devem manter sua _inveja da escalabilidade na web_ sob controle, e preferir soluções simples que ainda assim fazem o que precisa ser feito.footnote:[Fonte: Thoughtworks Technology Advisory Board, https://fpy.li/19-40[_Technology Radar_—November 2015] (EN).] ____ -Na _escala da web_, a chave é uma arquitetura que permita escalabilidade horizontal. -Neste cenário, todos os sistemas são sistemas distribuídos, -e possivelmente nenhuma linguagem de programação será a única alternativa ideal para todas as partes da solução. +Na _escala da Web_, a chave é uma arquitetura que permita escalabilidade +horizontal. Neste cenário, todos os sistemas são sistemas distribuídos, e +possivelmente nenhuma linguagem de programação será a alternativa ideal para +todas as partes da solução. Sistemas distribuídos são um campo da pesquisa acadêmica, mas felizmente alguns profissionais da área escreveram livros acessíveis, baseados em pesquisas sólidas e experiência prática. -Um deles é Martin Kleppmann, o autor de _Designing Data-Intensive Applications_ (_Projetando Aplicações de Uso Intensivo de Dados_) (O'Reilly). +Um deles é Martin Kleppmann, o autor de _Designing Data-Intensive Applications_ (Projetando Aplicações de Uso Intensivo de Dados) (O'Reilly). -Observe a <>, o primeiro de muitos diagramas de arquitetura no livro de Kleppmann. +Observe a <>, o primeiro de muitos diagramas de arquitetura que adaptamos do livro de Kleppmann. Aqui há alguns componentes que vi em muitos ambientes Python onde trabalhei ou que conheci pessoalmente: * Caches de aplicação:footnote:[Compare os caches de aplicação—usados diretamente pelo código de sua aplicação—com caches HTTP, que estariam no limite superior da <>, servindo recursos estáticos como imagens e arquivos CSS ou JS. Redes de Fornecimento de Conteúdo (CDNs de _Content Delivery Networks_) oferecem outro tipo de cache HTTP, instalados em datacenters próximos aos usuários finais de sua aplicação.] _memcached_, _Redis_, _Varnish_ -* bancos de dados relacionais: _PostgreSQL_, _MySQL_ +* Bancos de dados relacionais: _PostgreSQL_, _MySQL_ * Bancos de documentos: _Apache CouchDB_, _MongoDB_ * Full-text indexes (_índices de texto integral_): _Elasticsearch_, _Apache Solr_ -* Enfileiradores de mensagens: _RabbitMQ_, _Redis_ +* Filas de mensagens: _RabbitMQ_, _Redis_ [[one_possible_architecture_fig]] .Uma arquitetura possível para um sistema, combinando diversos componentes.footnote:[Diagrama adaptado da Figure 1-1, _Designing Data-Intensive Applications_ de Martin Kleppmann (O'Reilly).] @@ -1096,38 +1234,63 @@ As duas próximas seções exploram esses componentes, recomendados pelas boas p [[wsgi_app_server_sec]] ==== Servidores de aplicação WSGI -O WSGI—((("multicore processing", "WSGI application servers")))((("Web Server Gateway Interface (WSGI)")))((("servers", "Web Server Gateway Interface (WSGI)"))) https://fpy.li/pep3333[Web Server Gateway Interface] (_Interface de Gateway de Servidores Web_)—é a API padrão para uma aplicação ou um framework Python receber requisições de um servidor HTTP e enviar para ele as respostas.footnote:[Alguns palestrantes soletram a sigla WSGI, enquanto outros a pronunciam como uma palavra rimando com "whisky."] -Servidores de aplicação WSGI gerenciam um ou mais processos rodando a sua aplicação, maximizando o uso das CPUs disponíveis. + +A((("multicore processing", +"WSGI application servers")))((("Web Server Gateway Interface (WSGI)")))((("servers", +"Web Server Gateway Interface (WSGI)"))) WSGI, +https://fpy.li/pep3333[_Web Server Gateway Interface_] (Interface de Integração de +Servidores Web), é a API padrão para uma aplicação ou um framework Python +receber requisições de um servidor HTTP e enviar para ele as +respostas.footnote:[Alguns palestrantes soletram a sigla WSGI, enquanto outros a +pronunciam como uma palavra rimando com "whisky."] Servidores de aplicação WSGI +gerenciam um ou mais processos rodando a sua aplicação, maximizando o uso das +CPUs disponíveis. A <> ilustra uma instalação WSGI típica. [TIP] ==== -Se quiséssemos fundir os dois diagramas, o conteúdo do retângulo tracejado na <> substituiria o retângulo sólido "Application code"(_código da aplicação_) no topo da <>. + +Se quiséssemos fundir os dois diagramas, o conteúdo do retângulo tracejado na +<> substituiria o retângulo sólido "Application code"(_código da +aplicação_) no topo da <>. + ==== -Os servidores de aplicação mais conhecidos em projeto web com Python são: +Os servidores de aplicação mais conhecidos em projetos Web com Python são: * https://fpy.li/19-41[_mod_wsgi_] -* https://fpy.li/19-42[_uWSGI_]footnote:[_uWSGI_ é escrito com um "u" minúsculo, mas pronunciado como a letra grega "µ," então o nome completo soa como "micro-whisky", mas com um "g" no lugar do "k."] +* https://fpy.li/19-42[_uWSGI_]footnote:[_uWSGI_ é escrito com um "u" minúsculo, +mas pronunciado como a letra grega "µ," então o nome completo soa como +"micro-whisky", mas com um "g" no lugar do "k."] * https://fpy.li/gunicorn[_Gunicorn_] * https://fpy.li/19-43[_NGINX Unit_] -Para usuários do servidor HTTP Apache, _mod_wsgi_ é a melhor opção. -Ele é tão antigo com a própria WSGI, mas tem manutenção ativa, e agora pode ser iniciado via linha de comando com o `mod_wsgi-express`, que o torna mais fácil de configurar e mais apropriado para uso com containers Docker. +Para usuários do servidor HTTP Apache, _mod_wsgi_ é a melhor opção. Ele é tão +antigo quanto a própria WSGI, mas é ativamente mantido, e agora pode ser iniciado +via linha de comando com o `mod_wsgi-express`, que o torna mais fácil de +configurar e mais apropriado para uso com containers Docker. [[app_server_fig]] -.Clientes se conectam a um servidor HTTP que entrega arquivos estáticos e roteia outras requisições para o servidor de aplicação, que inicia processo filhos para executar o código da aplicação, utilizando múltiplos núcleos de CPU. A API WSGI é a ponte entre o servidor de aplicação e o código da aplicação Python. +.Clientes se conectam a um servidor HTTP que entrega arquivos estáticos e roteia outras requisições para o servidor de aplicação, que gerencia processos filhos para executar o código da aplicação, utilizando múltiplos núcleos de CPU. A API WSGI integra o servidor de aplicação ao código da aplicação Python. image::../images/flpy_1904.png["Diagrama de bloco mostrando o cliente conectado ao servidor HTTP, conectado ao servidor de aplicação, conectado a quatro processos Python."] -O _uWSGI_ e o _Gunicorn_ são as escolhas mais populares entre os projetos recentes que conheço. -Ambos são frequentemente combinados com o servidor HTTP _NGINX_. -_uWSGI_ oferece muita funcionalidade adicional, incluindo um cache de aplicação, uma fila de tarefas, tarefas periódicas estilo cron, e muitas outras. -Por outro lado, o _uWSGI_ é mais difícil de configurar corretamente que o _Gunicorn_.footnote:[Os engenheiros da Bloomberg Peter Sperl and Ben Green escreveram https://fpy.li/19-44["Configuring uWSGI for Production Deployment" (_Configurando o uWSGI para Implantação em Produção_)] (EN), explicando como muitas das configurações default do _uWSGI_ não são adequadas para cenários comuns de implantação. Sperl apresentou um resumo de suas recomendações na https://fpy.li/19-45[EuroPython 2019]. Muito recomendado para usuários de _uWSGI_.] +O _uWSGI_ e o _Gunicorn_ são as escolhas mais populares entre os projetos +recentes que conheço. Ambos são frequentemente combinados com o servidor HTTP +_NGINX_. _uWSGI_ oferece muita funcionalidade adicional, incluindo um cache de +aplicação, uma fila de tarefas, tarefas periódicas estilo cron, e muitas outras. +Por outro lado, o _uWSGI_ é mais difícil de configurar corretamente que o +_Gunicorn_.footnote:[Os engenheiros da Bloomberg Peter Sperl and Ben Green +escreveram https://fpy.li/19-44["Configuring uWSGI for Production Deployment" +(_Configurando o uWSGI para Implantação em Produção_)], explicando como muitas +das configurações default do _uWSGI_ não são adequadas para cenários comuns de +implantação. Sperl apresentou um resumo de suas recomendações na +https://fpy.li/19-45[EuroPython 2019]. Muito recomendado para usuários de +_uWSGI_.] Lançado em 2018, o _NGINX Unit_ é um novo produto dos desenvolvedores do conhecido servidor HTTP e proxy reverso _NGINX_. @@ -1141,13 +1304,14 @@ o servidor de aplicação lida de forma transparente com a concorrência. [[asgi_note]] .ASGI—Asynchronous Server Gateway Interface -(_Interface Assíncrona de Ponto de Entrada de Servidor_) [NOTE] ==== -A WSGI((("Asynchronous Server Gateway Interface (ASGI)")))((("servers", "Asynchronous Server Gateway Interface (ASGI)"))) é uma API síncrona. Ela não suporta corrotinas com `async/await`—a forma mais eficiente de implementar WebSockets or long pooling de HTTP em Python. -A https://fpy.li/19-46[especificação da ASGI] é a sucessora da WSGI, +A WSGI((("Asynchronous Server Gateway Interface (ASGI)")))((("servers", +"Asynchronous Server Gateway Interface (ASGI)"))) é uma API síncrona. +Ela não suporta corrotinas com `async/await`, que são a forma mais eficiente de implementar WebSockets em Python. +A https://fpy.li/19-46[especificação da ASGI] é a sucessora assíncrona da WSGI, projetada para frameworks Python assíncronos para programação web, como _aiohttp_, _Sanic_, _FastAPI_, etc., -bem como _Django_ e _Flask_, que estão gradativamente acrescentando funcionalidades assíncronas. +bem como _Django_ e _Flask_, que estão gradualmente incorporando mais funcionalidades assíncronas. ==== Agora vamos examinar outra forma de evitar a GIL para obter um melhor desempenho em aplicações Python de servidor. @@ -1155,55 +1319,68 @@ Agora vamos examinar outra forma de evitar a GIL para obter um melhor desempenho [[distributed_task_queues_sec]] ==== Filas de tarefas distribuídas -Quando((("multicore processing", "distributed task queues")))((("distributed task queues")))((("queues", "distributed task queues"))) -o servidor de aplicação entrega uma requisição a um dos processos Python rodando seu código, -sua aplicação precisa responder rápido: -você quer que o processo esteja disponível para processar a requisição seguinte assim que possível. -Entretanto, algumas requisições exigem ações que podem demorar—por exemplo, enviar um e-mail ou gerar um PDF. -As filas de tarefas distribuídas foram projetadas para resolver este problema. +Quando((("multicore processing", "distributed task queues")))((("distributed +task queues")))((("queues", "distributed task queues"))) o servidor de aplicação +entrega uma requisição a um dos processos Python rodando sua aplicação, seu +código precisa responder rápido: você quer que o processo esteja disponível para +processar a requisição seguinte assim que possível. Entretanto, algumas +requisições exigem ações que podem demorar—por exemplo, enviar um e-mail ou +gerar um PDF. As filas de tarefas distribuídas foram projetadas para resolver +este problema. -A https://fpy.li/19-47[_Celery_] e a https://fpy.li/19-48[_RQ_] são as mais conhecidas filas de tarefas _Open Source_ com uma API para Python. -Provedores de serviços na nuvem também oferecem suas filas de tarefas proprietárias. +A https://fpy.li/19-47[_Celery_] e a https://fpy.li/19-48[_RQ_] são as filas de +tarefas _Open Source_ mais conhecidas com uma API para Python. Provedores de +serviços na nuvem também oferecem suas filas de tarefas proprietárias. -Esses produtos encapsulam filas de mensagens e oferecem uma API de alto nível para delegar tarefas a processos executores, possivelmente rodando em máquinas diferentes. +Esses produtos encapsulam filas de mensagens e oferecem uma API de alto nível +para delegar tarefas a processos executores, possivelmente rodando em máquinas +diferentes. [NOTE] ==== -No contexto de filas de tarefas, as palavras _produtor_ e _consumidor_ são usado no lugar da terminologia tradicional de cliente/servidor. -Por exemplo, para gerar documentos, um processador de views do Django _produz_ requisições de serviço, -que são colocadas em uma fila para serem _consumidas_ por um ou mais processos renderizadores de PDFs. + +No contexto de filas de tarefas, as palavras _produtor_ e _consumidor_ são +usadas no lugar da terminologia tradicional de cliente/servidor. Por exemplo, +para gerar documentos, uma view do do Django _produz_ requisições de serviço, +que são colocadas em uma fila para serem _consumidas_ por um ou mais processos +renderizadores de PDFs. + ==== -Citando diretamente o https://fpy.li/19-49[FAQ] do Celery, eis alguns casos de uso: +Citando diretamente o https://fpy.li/19-49[FAQ] da Celery, eis alguns casos de uso: [quote] ____ + * Executar algo em segundo plano. Por exemplo, para encerrar uma requisição web o mais rápido possível, e então atualizar a página do usuário de forma incremental. Isso dá ao usuário a impressão de um bom desempenho e de "vivacidade", ainda que o trabalho real possa na verdade demorar um pouco mais. * Executar algo após a requisição web ter terminado. -* Se assegurar que algo seja feito, através de uma execução assíncrona e usando tentativas repetidas. +* Se assegurar que algo seja feito, através de uma execução assíncrona, repetindo tentativas quando necessário. * Agendar tarefas periódicas. + ____ Além de resolver esses problemas imediatos, as filas de tarefas suportam escalabilidade horizontal. Produtores e consumidores são desacoplados: um produtor não precisa chamar um consumidor, ele coloca uma requisição em uma fila. Consumidores não precisam saber nada sobre os produtores (mas a requisição pode incluir informações sobre o produtor, se uma confirmação for necessária). Pode-se adicionar mais unidades de execução para consumir tarefas a medida que a demanda cresce. -Por isso o _Celery_ e o _RQ_ são chamados de filas de tarefas distribuídas. +Por isso a _Celery_ e a _RQ_ são chamados de filas de tarefas distribuídas. -Lembre-se que nosso simples _procs.py_ (<>) usava duas filas: +Lembre-se que nosso simples _procs.py_ (<>) usava duas filas: uma para requisições de tarefas, outra para coletar resultados. A arquitetura distribuída do _Celery_ e do _RQ_ usa um esquema similar. Ambos suportam o uso do banco de dados NoSQL https://fpy.li/19-50[_Redis_] para armazenar as filas de mensagens e resultados. -O _Celery_ também suporta outras filas de mensagens, como o _RabbitMQ_ ou o _Amazon SQS_, bem como outros bancos de dados para armazenamento de resultados. +O _Celery_ também suporta outras filas de mensagens, como a _RabbitMQ_ ou a _Amazon SQS_, e também alguns bancos de dados para armazenar resultados. Isso encerra nossa introdução à concorrência em Python. -Os dois próximos capítulos continuam nesse tema, se concentrando nos pacotes `concurrent.futures` e `asyncio` packages da biblioteca padrão.((("", startref="CMmulti19")))((("", startref="Pmulti19"))) +Os dois próximos capítulos continuam nesse tema, apresentando os pacotes `concurrent.futures` e `asyncio` da biblioteca padrão.((("", startref="CMmulti19")))((("", startref="Pmulti19"))) === Resumo do capítulo -Após((("concurrency models", "overview of"))) um pouco de teoria, esse capítulo apresentou scripts da animação giratória, implementados em cada um dos três modelos de programação de concorrência nativos de Python: +Após((("concurrency models", "overview of"))) um pouco de teoria, esse capítulo +apresentou scripts da animação giratória, implementados em cada um dos três +modelos de programação de concorrência nativos de Python: * Threads, com o pacote `threading` * Processo, com `multiprocessing` @@ -1211,14 +1388,15 @@ Após((("concurrency models", "overview of"))) um pouco de teoria, esse capítul Então exploramos o impacto real da GIL com um experimento: mudar os exemplos de animação para computar se um inteiro grande era primo e observar o comportamento resultante. -Isso demonstrou graficamente que funções de uso intensivo da CPU devem ser evitadas em `asyncio`, pois elas bloqueiam o loop de eventos. +Assim comprovamos que funções que usam a CPU intensivamente devem ser evitadas em `asyncio`, pois elas bloqueiam o laço de eventos. A versão com threads do experimento funcionou—apesar da GIL—porque Python periodicamente interrompe as threads, e o exemplo usou apenas duas threads: uma fazendo um trabalho de computação intensiva, a outra controlando a animação apenas 10 vezes por segundo. A variante com `multiprocessing` contornou a GIL, iniciando um novo processo só para a animação, enquanto o processo principal calculava se o número era primo. -O exemplo seguinte, computando diversos números primos, destacou a diferença entre `multiprocessing` e `threading`, -provando que apenas processos permitem ao Python se beneficiar de CPUs com múltiplo núcleos. -A GIL de Python torna as threads piores que o código sequencial para processamento pesado. +O exemplo seguinte, computando a primalidade de uma série de números, destacou a +diferença entre `multiprocessing` e `threading`, provando que apenas processos +permitem ao Python se beneficiar de CPUs com múltiplo núcleos. A GIL de Python +torna as threads piores que o código sequencial para processamento pesado. A GIL domina as discussões sobre computação concorrente e paralela em Python, mas não devemos superestimar seu impacto. Este foi o tema da <>. @@ -1235,44 +1413,64 @@ Este((("concurrency models", "further reading on", id="CMfurther19"))) capítulo [[concurrency_further_threads_procs_sec]] ==== Concorrência com threads e processos -A((("threads", "further reading on"))) biblioteca _concurrent.futures_, tratada no <>, usa threads, processos, travas e filas debaixo dos panos, mas você não vai ver as instâncias individuais desses elementos; -eles são encapsulados e gerenciados por abstrações de um nível mais alto: `ThreadPoolExecutor` ou `ProcessPoolExecutor`. +A((("threads", "further reading on"))) biblioteca `concurrent.futures`, tratada no <>, usa threads, processos, travas e filas debaixo dos panos, mas você não verá as instâncias individuais destes componentes; +eles são encapsulados e gerenciados por abstrações de nível mais alto: `ThreadPoolExecutor` ou `ProcessPoolExecutor`. Para aprender mais sobre a prática da programação concorrente com aqueles objetos de baixo nível, -https://fpy.li/19-51["An Intro to Threading in Python"] (_Uma Introdução [à Programação com] Threads no Python_) de Jim Anderson é uma boa primeira leitura. -Doug Hellmann tem um capítulo chamado "Concurrency with Processes, Threads, and Coroutines" (_Concorrência com Processos, Threads, e Corrotinas_) +https://fpy.li/19-51[_An Intro to Threading in Python_] de Jim Anderson é uma boa primeira leitura. +Doug Hellmann tem um capítulo chamado _Concurrency with Processes, Threads, and Coroutines_ em seus https://fpy.li/19-52[site] e livro, https://fpy.li/19-53[_The Python 3 Standard Library by Example_] (Addison-Wesley). -https://fpy.li/effectpy[_Effective Python_], 2nd ed. (Addison-Wesley), de Brett Slatkin, -_Python Essential Reference_, 4th ed. (Addison-Wesley), de David Beazley, e _Python in a Nutshell_, 3rd ed. (O'Reilly) de Martelli et al são outras referências gerais de Python com uma cobertura significativa de `threading` e `multiprocessing`. +O https://fpy.li/effectpy[_Effective Python_], 2nd ed. (Addison-Wesley), de Brett Slatkin, +_Python Essential Reference_, 4th ed. (Addison-Wesley), de David Beazley, e _Python in a Nutshell_, 3rd ed. (O'Reilly) de Martelli et.al. são outras referências gerais de Python com uma cobertura significativa de `threading` e `multiprocessing`. A vasta documentação oficial de `multiprocessing` inclui conselhos úteis em sua seção -https://docs.python.org/pt-br/3/library/multiprocessing.html#programming-guidelines["Programming guidelines" (_Diretrizes de programação_)] (EN). - -Jesse Noller e Richard Oudkerk contribuíram para o pacote `multiprocessing`, introduzido na https://fpy.li/pep371[PEP 371--Addition of the multiprocessing package to the standard library] (EN). A documentação oficial do pacote é um https://docs.python.org/pt-br/3/library/multiprocessing.html[arquivo de 93 KB _.rst_]—são cerca de 63 páginas—tornando-o um dos capítulos mais longos da biblioteca padrão de Python. - -Em pass:[High Performance Python, 2nd ed.,] (O'Reilly), os autores Micha Gorelick e Ian Ozsvald incluem um capítulo sobre `multiprocessing` com um exemplo sobre checagem de números primos usando uma estratégia diferente do nosso exemplo _procs.py_. Para cada número, eles dividem a faixa de fatores possíveis-de 2 a `sqrt(n)`—em subfaixas, e fazem cada unidade de execução iterar sobre uma das subfaixas. -Sua abordagem de dividir para conquistar é típica de aplicações de computação científica, onde os conjuntos de dados são enormes, e as estações de trabalho (ou clusters) tem mais núcleos de CPU que usuários. -Em um sistema servidor, processando requisições de muitos usuários, é mais simples e mais eficiente deixar cada processo realizar uma tarefa computacional do início ao fim—reduzindo a sobrecarga de comunicação e coordenação entre processos. -Além de `multiprocessing`, Gorelick e Ozsvald apresentam muitas outras formas de desenvolver e implantar aplicações de ciência de dados de alto desempenho, aproveitando múltiplos núcleos de CPU, GPUs, clusters, analisadores e compiladores como CYthon e Numba. Seu capítulo final, "Lessons from the Field," (_Lições da Vida Real_) é uma valiosa coleção de estudos de caso curtos, contribuição de outros praticantes de computação de alto desempenho em Python. +https://fpy.li/ae[_Programming guidelines_] (Diretrizes de programação). + +Jesse Noller e Richard Oudkerk contribuíram para o pacote `multiprocessing`, +proposto na https://fpy.li/pep371[_PEP 371—Addition of the multiprocessing package to the standard library_]. +A documentação oficial do pacote é um +https://fpy.li/ah[arquivo _.rst_] de 93 KB (cerca de 63 páginas), +um dos capítulos mais longos da biblioteca padrão de Python. + +Em https://fpy.li/19-56[_High Performance Python_] (O'Reilly), os autores Micha +Gorelick e Ian Ozsvald incluem um capítulo sobre `multiprocessing` com um +exemplo sobre checagem de números primos usando uma estratégia diferente do +nosso exemplo _procs.py_. +Para cada número, eles dividem a faixa de fatores possíveis-de 2 a `sqrt(n)`—em +subfaixas, e fazem cada unidade de execução iterar sobre uma das subfaixas. Sua +abordagem de dividir para conquistar é típica de aplicações de computação +científica, onde os conjuntos de dados são enormes, e as estações de trabalho +(ou clusters) tem mais núcleos de CPU que usuários. Em um sistema servidor, +processando requisições de muitos usuários, é mais simples e mais eficiente +deixar cada processo realizar uma tarefa computacional do início ao +fim—reduzindo a sobrecarga de comunicação e coordenação entre processos. Além de +`multiprocessing`, Gorelick e Ozsvald apresentam muitas outras formas de +desenvolver e implantar aplicações de ciência de dados de alto desempenho, +aproveitando múltiplos núcleos de CPU, GPUs, clusters, analisadores e +compiladores como _Cython_ e _Numba_. Seu capítulo final, _Lessons from the Field_ +(Lições da Vida Real) é uma valiosa coleção de estudos de caso curtos, +contribuição de outros praticantes de computação de alto desempenho em Python. O https://fpy.li/19-57[_Advanced Python Development_], de Matthew Wilkes (Apress), -é um dos raros livros a incluir pequenos exemplos para explicar conceitos, -mostrando ao mesmo tempo como desenvolver uma aplicação realista pronta para implantação em produção: +é mostra como desenvolver uma aplicação realista pronta para implantação em produção: um agregador de dados, similar aos sistemas de monitoramento DevOps ou aos coletores de dados para sensores distribuídos IoT. Dois capítulos no _Advanced Python Development_ tratam de programação concorrente com `threading` e `asyncio`. O https://fpy.li/19-58[_Parallel Programming with Python_] (Packt, 2014), de Jan Palach, -explica os principais conceitos por trás da concorrência e do paralelismo, abarcando a biblioteca padrão de Python bem como o _Celery_. +explica os principais conceitos por trás da concorrência e do paralelismo, abarcando a biblioteca padrão de Python bem como a _Celery_. -"The Truth About Threads" (_A Verdade Sobre as Threads_) é o título do capítulo 2 de -pass:[Using Asyncio in Python], de Caleb Hattingh (O'Reilly).footnote:[Caleb é um dos revisores técnicos dessa edição de _Python Fluente_.] -O capítulo trata dos benefícios e das desvantagens das threads—com citações convincentes de várias fontes abalizadas—deixando claro que os desafios fundamentais das threads não têm relação com Python ou a GIL. -Citando literalmente a página 14 de _Using Asyncio in Python_: +_The Truth About Threads_ (A Verdade Sobre as Threads) é o título do capítulo 2 de +https://fpy.li/hattingh[_Using Asyncio in Python_], +de Caleb Hattingh (O'Reilly).footnote:[Caleb é um dos revisores técnicos da segunda edição de _Python Fluente_.] +O capítulo trata dos benefícios e das desvantagens das threads—com citações +convincentes de várias fontes confiáveis—deixando claro que os desafios +fundamentais das threads não têm relação com Python ou a GIL. Citando +literalmente a página 14 de _Using Asyncio in Python_ (nossa tradução): [quote] ____ -Esses temas se repetem com frequência: +Estes temas se repetem com frequência: * Programação com threads torna o código difícil de analisar. @@ -1281,18 +1479,18 @@ ____ Se você quiser aprender do jeito difícil como é complicado raciocinar sobre threads e travas—sem colocar seu emprego em risco—tente resolver os problemas no livro de Allen Downey https://fpy.li/19-59[_The Little Book of Semaphores_] (Green Tea Press). O livro inclui exercícios muito difíceis e até sem solução conhecida, -mas mesmo os fáceis são desafiadores. +mas até os fáceis são desafiadores. ==== A GIL -Se((("Global Interpreter Lock (GIL)"))) você ficou curioso sobre a GIL, lembre-se que não temos qualquer controle sobre ela a partir do código em Python, então a referência canônica é a documentação da C-API: -https://fpy.li/19-60[_Thread State and the Global Interpreter Lock_] (EN) (_O Estado das Threads e a Trava Global do Interpretador_). -A resposta no FAQ _Python Library and Extension_ (_A Biblioteca e as Extensões de Python_): -https://docs.python.org/pt-br/3/faq/library.html#can-t-we-get-rid-of-the-global-interpreter-lock[_"Can’t we get rid of the Global Interpreter Lock?"_] (_Não podemos remover o Bloqueio Global do interpretador?_). +Se((("Global Interpreter Lock (GIL)"))) você ficou curioso sobre a GIL, lembre-se que não temos controle sobre ela a partir do código em Python, então a referência canônica é a documentação da C-API: +https://fpy.li/19-60[_Thread State and the Global Interpreter Lock_] (O Estado das Threads e a Trava Global do Interpretador). +A resposta no FAQ _Python Library and Extension_ (A Biblioteca e as Extensões de Python): +https://fpy.li/af[_Can’t we get rid of the Global Interpreter Lock?_] (Não podemos remover o Bloqueio Global do interpretador?). Também vale a pena ler os posts de Guido van Rossum e Jesse Noller (contribuidor do pacote `multiprocessing`), respectivamente: -https://fpy.li/19-62["It isn't Easy to Remove the GIL"] (_Não é Fácil Remover a GIL_) e -https://fpy.li/19-63["Python Threads and the Global Interpreter Lock"] (_As Threads de Python e a Trava Global do Interpretador_). +https://fpy.li/19-62[_It isn't Easy to Remove the GIL_] (Não é Fácil Remover a GIL) e +https://fpy.li/19-63[_Python Threads and the Global Interpreter Lock_] (As Threads de Python e a Trava Global do Interpretador). https://fpy.li/19-64[_CPython Internals_], de Anthony Shaw (Real Python) explica a implementação do interpretador CPython 3 no nível da programação em C. O capítulo mais longo do livro é "Parallelism and Concurrency" (_Paralelismo e Concorrência_): @@ -1304,7 +1502,7 @@ Por fim, David Beazley apresentou uma exploração detalhada em https://fpy.li/1 No slide 54 da https://fpy.li/19-66[apresentação], Beazley relata um aumento no tempo de processamento de uma benchmark específica com o novo algoritmo da GIL, introduzido no Python 3.2. O problema não tem importância com cargas de trabalho reais, de acordo com um -https://fpy.li/19-67[comentário] de Antoine Pitrou--que implementou o novo algoritmo da GIL--no relatório de bug submetido por Beazley: +https://fpy.li/19-67[comentário] de Antoine Pitrou—que implementou um novo algoritmo para a GIL—no relatório de bug submetido por Beazley: https://fpy.li/19-68[Python issue #7946]. @@ -1312,57 +1510,69 @@ https://fpy.li/19-68[Python issue #7946]. O _Python Fluente_ se concentra nos recursos fundamentais da linguagem e nas partes centrais da biblioteca padrão. https://fpy.li/19-69[_Full Stack Python_] é um ótimo complemento para esse livro: é sobre o ecossistema de Python, com seções chamadas "Development Environments (_Ambientes de Desenvolvimento_)," "Data (_Dados_)," "Web Development (_Desenvolvimento Web_)," e "DevOps," entre outros. -Já mencionei dois livros que abordam a concorrência usando a biblioteca padrão de Python e também incluem conteúdo significativo sobre bibliotecas de terceiros e ferramentas: - -pass:[High Performance Python, 2nd ed.] -e pass:[Parallel Programming with Python]. -O pass:[Distributed Computing with Python] de Francesco Pierfederici +Já mencionei dois livros que abordam a concorrência usando a biblioteca padrão +de Python e também incluem conteúdo significativo sobre bibliotecas externas +e ferramentas: +https://fpy.li/19-56[_High Performance Python, 2nd ed._] e +https://fpy.li/19-58[_Parallel Programming with Python_]. +O https://fpy.li/19-72[_Distributed Computing with Python_] de Francesco Pierfederici (Packt) cobre a biblioteca padrão e também provedores de infraestrutura de nuvem e clusters HPC (_High-Performance Computing_, computação de alto desempenho). -O https://fpy.li/19-73["Python, Performance, and GPUs"] (EN) de Matthew Rocklin é uma atualização do status do uso de aceleradores GPU com Python, publicado em junho de 2019. +O https://fpy.li/19-73[_Python, Performance, and GPUs_] de Matthew Rocklin é uma +atualização do status do uso de aceleradores GPU com Python, publicado em junho +de 2019. "O Instagram hoje representa a maior instalação do mundo do framework web _Django_, que é escrito inteiramente em Python." -Essa é a linha de abertura do post https://fpy.li/19-74["Web Service Efficiency at Instagram with Python"] (EN), escrito por Min Ni—um engenheiro de software no Instagram. -O post descreve as métricas e ferramentas usadas pelo Instagram para otimizar a eficiência de sua base de código Python, bem como para detectar e diagnosticar regressões de desempenho a cada uma das "30 a 50 vezes diárias" que o back-end é atualizado. +Essa é a linha de abertura do post +https://fpy.li/19-74[_Web Service Efficiency at Instagram with Python_], escrito +por Min Ni, da engenharia do Instagram. O post descreve as métricas e +ferramentas usadas pelo Instagram para otimizar a eficiência de sua base de +código Python, bem como para detectar e diagnosticar regressões de desempenho a +cada uma das "30 a 50 vezes diárias" que o back-end é atualizado. -https://fpy.li/19-75[_Architecture Patterns with Python: Enabling Test-Driven Development, Domain-Driven Design, and Event-Driven Microservices_], de Harry Percival e Bob Gregory (O'Reilly) apresenta modelos de arquitetura para aplicações de servidor em Python. Os autores disponibilizaram o livro gratuitamente online em -pass:[cosmicpython.com] (EN). +https://fpy.li/19-75[_Architecture Patterns with Python: Enabling Test-Driven Development, Domain-Driven Design, and Event-Driven Microservices_], de Harry Percival e Bob Gregory (O'Reilly) apresenta modelos de arquitetura para aplicações de servidor em Python. Os autores publicarm o livro gratuitamente online em https://fpy.li/19-76[_cosmicpython.com_]. -Duas bibliotecas elegantes e fáceis de usar para tarefas de paralelização de processos são a https://fpy.li/19-77[_lelo_] de João S. O. Bueno e a https://fpy.li/19-78[_python-parallelize_] de Nat Pryce. +Duas bibliotecas elegantes e fáceis de usar a paralelização de processos são a https://fpy.li/19-77[_lelo_] de João S. O. Bueno e a https://fpy.li/19-78[_python-parallelize_] de Nat Pryce. O pacote _lelo_ define um decorador `@parallel` que você pode aplicar a qualquer função para torná-la magicamente não-bloqueante: quando você chama uma função decorada, sua execução é iniciada em outro processo. -O pacote _python-parallelize_ de Nat Pryce fornece um gerador `parallelize`, que distribui a execução de um loop `for` por múltiplas CPUs. +O pacote _python-parallelize_ de Nat Pryce fornece um gerador `parallelize`, que distribui a execução de um laço `for` por múltiplas CPUs. Ambos os pacotes são baseados na biblioteca _multiprocessing_. -Eric Snow, um dos desenvolvedores oficiais de Python, -mantém um wiki chamado https://fpy.li/19-79[Multicore Python], +Eric Snow, um dos mantenedores do Python, +mantém um wiki chamado https://fpy.li/19-79[_Multicore Python_], com observações sobre os esforços dele e de outros para melhorar o suporte de Python a execução em paralelo. -Snow é o autor da https://fpy.li/pep554[PEP 554--Multiple Interpreters in the Stdlib]. +Snow é o autor da https://fpy.li/pep554[_PEP 554-Multiple Interpreters in the Stdlib_]. Se aprovada e implementada, a PEP 554 assenta as bases para melhorias futuras, que podem um dia permitir que Python use múltiplos núcleos sem as sobrecargas do _multiprocessing_. Um dos grandes empecilhos é a iteração complexa entre múltiplos subinterpretadores ativos e extensões que assumem a existência de um único interpretador. Mark Shannon—também um mantenedor de Python—criou uma https://fpy.li/19-80[tabela útil] comparando os modelos de concorrência em Python, referida em uma discussão sobre subinterpretadores entre ele, Eric Snow e outros desenvolvedores na lista de discussão https://fpy.li/19-81[python-dev]. -Na tabela de Shannon, a coluna "Ideal CSP" se refere ao modelo teórico de notação -https://fpy.li/19-82[_Communicating Sequential Processes] (processos sequenciais comunicantes) (EN), proposto por Tony Hoare em 1978. -Go também permite objetos compartilhados, violando uma das restrições essenciais do CSP: as unidades de execução devem se comunicar somente através de mensagens enviadas através de canais. +Na tabela de Shannon, a coluna "Ideal CSP" se refere ao modelo teórico +https://fpy.li/19-82[_Communicating Sequential Processes_] (processos sequenciais comunicantes), proposto por Tony Hoare em 1978, +que influenciou o projeto da linguagem Go. +Go também permite objetos compartilhados, violando uma das restrições essenciais +do CSP: as unidades de execução devem se comunicar somente através de mensagens +enviadas através de canais. O https://fpy.li/19-83[_Stackless Python_] (também conhecido como _Stackless_) é um fork do CPython que implementa microthreads, que são threads leves no nível da aplicação—ao contrário das threads do SO. O jogo online multijogador massivo https://fpy.li/19-84[_EVE Online_] foi desenvolvido com _Stackless_, e os engenheiros da desenvolvedora de jogos -https://fpy.li/19-85[CCP] foram +https://fpy.li/19-85[_CCP_] foram https://fpy.li/19-86[mantenedores do _Stackless_] por algum tempo. Alguns recursos do _Stackless_ foram reimplementados no interpretador -https://fpy.li/19-87[_Pypy_] e no pacote https://fpy.li/19-14[_greenlet_], a tecnologia central da biblioteca de programação em rede https://fpy.li/19-17[_gevent_], +https://fpy.li/19-87[_Pypy_] e no pacote https://fpy.li/19-14[_greenlet_], +a tecnologia central da biblioteca de programação em rede https://fpy.li/19-17[_gevent_], que por sua vez é a fundação do servidor de aplicação https://fpy.li/gunicorn[_Gunicorn_]. O modelo de atores (_actor model_) de programação concorrente está no centro das linguagens altamente escaláveis Erlang e Elixir, e é também o modelo do framework Akka para Scala e Java. Se você quiser experimentar o modelo de atores em Python, veja as bibliotecas https://fpy.li/19-90[_Thespian_] e https://fpy.li/19-91[_Pykka_]. -Minhas recomendações restantes fazem pouca ou nenhuma menção ao Python, mas de toda forma são relevantes para leitores interessados no tema do capítulo. +Minhas recomendações restantes fazem pouca ou nenhuma menção direta ao Python, +mas são importantes para aprofundar o tema das arquiteturas escaláveis com +suporte a concorrência. ==== Concorrência e escalabilidade para além de Python @@ -1381,12 +1591,22 @@ Elixir é a linguagem dos exemplos ilustrando o modelo de atores. Um https://fpy.li/19-95[capítulo bonus] alternativo (disponível online gratuitamente) sobre atores usa Scala e o framework Akka. A menos que você já saiba Scala, Elixir é uma linguagem mais acessível para aprender e experimentar o modelo de atores e plataforma de sistemas distribuídos Erlang/OTP. -Unmesh Joshi, da Thoughtworks contribuiu com várias páginas documentando os "Modelos de Sistemas Distribuídos" no https://fpy.li/19-96[blog] de Martin Fowler. -A https://fpy.li/19-97[página de abertura] é uma ótima introdução ao assunto, -com links para modelos individuais. -Joshi está acrescentando modelos gradualmente, mas o que já está publicado espelha anos de experiência adquirida a duras penas em sistema de missão crítica. - -O pass:[Designing Data-Intensive Applications], de Martin Kleppmann (O'Reilly), é um dos raros livros escritos por um profissional com vasta experiência na área e conhecimento acadêmico avançado. O autor trabalhou com infraestrutura de dados em larga escala no LinkedIn e em duas startups, antes de se tornar um pesquisador de sistemas distribuídos na Universidade de Cambridge. Cada capítulo do livro termina com uma extensa lista de referências, incluindo resultados de pesquisas recentes. O livro também inclui vários diagramas esclarecedores e lindos mapas conceituais. +Unmesh Joshi, da Thoughtworks contribuiu com várias páginas documentando padrões +de sistemas distribuídos no https://fpy.li/19-96[blog] de Martin Fowler. A +https://fpy.li/19-97[página de abertura] é uma ótima introdução ao assunto, com +links para vários padrões. Joshi está acrescentando modelos gradualmente, +mas o que já está publicado reflete anos de experiência adquirida a duras penas +em sistema de missão crítica. + +O https://fpy.li/19-98[_Designing Data-Intensive Applications_], de Martin +Kleppmann (O'Reilly), é um dos raros livros escritos por um profissional com +vasta experiência prática e também conhecimento acadêmico avançado sobre +sistemas distribuídos. O autor trabalhou com infraestrutura de dados em larga +escala no LinkedIn e em duas startups, antes de se tornar um pesquisador de +sistemas distribuídos na Universidade de Cambridge. Cada capítulo do livro +termina com uma extensa lista de referências, incluindo resultados de pesquisas +recentes. O livro também inclui vários diagramas esclarecedores e lindos mapas +conceituais. Tive a sorte de estar na audiência do fantástico workshop de Francesco Cesarini sobre a arquitetura de sistemas distribuídos confiáveis, na OSCON 2016: "Designing and architecting for scalability with Erlang/OTP" (_Projetando e estruturando para a escalabilidade com Erlang/OTP_) @@ -1409,13 +1629,13 @@ https://fpy.li/19-40[_inveja da escalabilidade na web_]. O https://fpy.li/19-102[princípio KISS] (KISS é a sigla de _Keep It Simple, Stupid_: "Mantenha Isso Simples, Idiota") continua sendo uma recomendação firme de engenharia. Veja também o artigo -https://fpy.li/19-103["Scalability! But at what COST?"], +https://fpy.li/19-103[_Scalability! But at what COST?_], de Frank McSherry, Michael Isard, e Derek G. Murray. Os autores identificaram sistemas paralelos de processamento de grafos apresentados em simpósios acadêmicos que precisavam de centenas de núcleos para superar "uma implementação competente com uma única thread." Eles também encontraram sistemas que "tem desempenho pior que uma thread em todas as configurações reportadas." -Essas((("", startref="CMfurther19"))) descobertas me lembram uma piada hacker clássica: +Estes((("", startref="CMfurther19"))) resultados me lembram uma piada hacker clássica: [quote] ____ @@ -1427,79 +1647,108 @@ ____ .Ponto de vista **** -[role="soapbox-title"] -Para gerenciar a complexidade, precisamos de restrições +*Para gerenciar a complexidade, precisamos de restrições* Aprendi((("concurrency models", "Soapbox discussion", id="CMsoap19")))((("Soapbox sidebars", "threads-and-locks versus actor-style programming", id="SSthread19"))) a programar em uma calculadora TI-58. Sua "linguagem" era similar ao assembler. Naquele nível, todas as "variáveis" eram globais, e não havia o conforto dos comandos estruturados de controle de fluxo. -Existiam saltos condicionais: instruções que transferiam a execução diretamente para uma localização arbitrária—à frente ou atrás do local atual—dependendo do valor de um registrador ou de uma flag na CPU. +Existiam saltos condicionais: instruções que transferiam a execução diretamente para uma localização arbitrária—à frente ou atrás do local atual—dependendo do valor de um registrador na CPU. É possível fazer basicamente qualquer coisa em assembler, e esse é o desafio: há muito poucas restrições para evitar que você cometa erros, e para ajudar mantenedores a entender o código quando mudanças são necessárias. -A segunda linguagem que aprendi foi o BASIC desestruturado que vinha nos computadores de 8 bits—nada comparável ao Visual Basic, que surgiu mais tarde. -Existiam os comandos `FOR`, `GOSUB` e `RETURN`, mas ainda nenhum conceito de variáveis locais. -O `GOSUB` não permitia passagem de parâmetros: -era apenas um `GOTO` mais chique, que inseria um número de linha de retorno em uma pilha, daí o `RETURN` tinha um local para onde pular de volta. -Subrrotinas podiam ler dados globais, e escrever sobre eles também. Era preciso improvisar outras formas de controle de fluxo, com combinações de `IF` e `GOTO`—que, lembremos, permita pular para qualquer linha do programa. - -Após alguns anos programando com saltos e variáveis globais, lembro da batalha para reestruturar meu cérebro para a "programação estruturada", quando aprendi Pascal. -Agora precisava usar comandos de controle de fluxo em torno de blocos de código que tinham um único ponto de entrada. -Não podia mais saltar para qualquer instrução que desejasse. -Variáveis globais eram inevitáveis em BASIC, mas agora se tornaram tabu. -Eu precisava repensar o fluxo de dados e passar argumentos para funções explicitamente. - -Meu próximo desafio foi aprender programação orientada a objetos. -No fundo, programação orientada a objetos é programação estruturada com mais restrições e polimorfismo. -O ocultamento de informações força uma nova perspectiva sobre onde os dados moram. -Me lembro de mais de uma vez ficar frustrado por ter que refatorar meu código, para que um método que estava escrevendo pudesse obter informações que estavam encapsuladas em um objeto que aquele método não conseguia acessar. +A segunda linguagem que aprendi foi o BASIC desestruturado que vinha nos +computadores de 8 bits—nada comparável ao Visual Basic, que surgiu mais tarde. +BASIC tinha as instruções `FOR`, `GOSUB` e `RETURN`, mas não variáveis locais! +Todas as linhas de código eram explicitamente numeradas no código-fonte. +O `GOSUB` não permitia passagem de parâmetros: era apenas um `GOTO` mais chique, +que guardava um número de linha de retorno em uma pilha, fornecendo um +local para instrução `RETURN` saltar de volta. +Subrrotinas só podiam ler e escrever dados globais. +Era preciso improvisar outras formas de controle de fluxo, +com combinações de `IF` e `GOTO`—que permitia saltar para qualquer +linha do programa. + +Após alguns anos programando com saltos e variáveis globais, lembro da batalha +para reestruturar meu cérebro para a "programação estruturada", quando aprendi +Pascal. Agora era obrigatório usar instruções de controle de fluxo em torno de +blocos de código que tinham um único ponto de entrada. Não podia mais saltar +para qualquer instrução que desejasse. Variáveis globais eram inevitáveis em +BASIC, mas agora se tornaram tabu. Eu precisava repensar o fluxo de dados e +passar argumentos para funções explicitamente. + +Meu próximo desafio foi aprender programação orientada a objetos. No fundo, +programação orientada a objetos é programação estruturada com mais restrições e +polimorfismo. O ocultamento de informações (_information hiding_) força uma nova +perspectiva sobre onde os dados residem. Lembro de mais de uma vez ficar +frustrado por ter que refatorar meu código, para que um método que estava +escrevendo pudesse obter informações que estavam encapsuladas em um objeto que +aquele método não conseguia acessar. Linguagens de programação funcionais acrescentam outras restrições, mas a imutabilidade é a mais difícil de engolir, após décadas de programação imperativa e orientada a objetos. Após nos acostumarmos a tais restrições, as vemos como bêncãos. -Elas fazem com que pensar sobre o código se torne mais simples. +Elas facilitam pensar sobre o código. A falta de restrições é o maior problema com o modelo de threads—e—travas de programação concorrente. Ao resumir o capítulo 1 de _Seven Concurrency Models in Seven Weeks_, Paul Butcher escreveu: [quote] ____ -A maior fraqueza da abordagem, entretanto, é que programação com threads—e—travas é _difícil_. +A maior fraqueza da abordagem, entretanto, é que programação com threads-e-travas é difícil. Pode ser fácil para um projetista de linguagens acrescentá-las a uma linguagem, -mas elas nos dão, a nós pobres programadores, muito pouca ajuda. +mas elas oferecem muito pouca ajuda a nós pobres programadores. ____ Alguns exemplos de comportamento sem restrições naquele modelo: * Threads podem compartilhar estruturas de dados mutáveis arbitrárias. -* O agendador pode interromper uma thread em quase qualquer ponto, incluindo no meio de uma operação simples, como `a += 1`. -Muito poucas operações são atômicas no nível das expressões do código-fonte. -* Travas são, em geral, _recomendações_. Esse é um termo técnico, dizendo que você precisa lembrar de obter explicitamente uma trava antes de atualizar uma estrutura de dados compartilhada. -Se você esquecer de obter a trava, nada impede seu código de bagunçar os dados enquanto outra thread, que obedientemente detém a trava, está atualizando os mesmos dados. -Em comparação, considere algumas restrições impostas pelo modelo de atores, -no qual a unidade de execução é chamada de um _actor_ ("ator"):footnote:[A comunidade Erlang usa o termo "processo" para se referir a atores. Em Erlang, cada processo é uma função em seu próprio loop, que então são muito leves, tornando viável ter milhões deles ativos ao mesmo tempo em uma única máquina—nenhuma relação com os pesados processo do SO, dos quais falamos em outros pontos desse capitulo. Então temos aqui exemplos dos dois pecados descritos pelo Prof. Simon: usar palavras diferentes para se referir à mesma coisa, e usar uma palavra para se referir a coisas diferentes.] +* O _scheduler_ pode interromper uma thread praticamente em qualquer ponto, +incluindo no meio de uma operação simples, como `a += 1`. Muito poucas operações +são atômicas no nível das expressões do código-fonte. + +* Travas são, em geral, recomendações (_advisory_). Esse é um termo técnico, +dizendo que você precisa lembrar de obter explicitamente uma trava antes de +atualizar uma estrutura de dados compartilhada. Se você esquecer de obter a +trava, nada impede seu código de bagunçar os dados enquanto outra thread, que +obedientemente detém a trava, está atualizando os mesmos dados. + +Em comparação, considere algumas restrições impostas pelo modelo de atores, no +qual a unidade de execução é chamada _actor_ (ator):footnote:[A comunidade +Erlang usa o termo "processo" para se referir a um ator. Em Erlang, cada +processo é apenas uma função em seu próprio laço, então são muito leves, +possibilitando ter milhões deles ativos ao mesmo tempo em uma única máquina—nenhuma +relação com os pesados processos do SO, dos quais falamos em outros pontos desse +capitulo. Então temos aqui exemplos dos dois pecados descritos pelo Prof. Simon: +usar palavras diferentes para se referir à mesma coisa, e usar uma palavra para +se referir a coisas diferentes.] * Um ator pode manter um estado interno, mas não pode compartilhar esse estado com outros atores. * Atores só podem se comunicar enviando e recebendo mensagens. * Mensagens só contém cópias de dados, e não referências para dados mutáveis. * Um ator só processa uma mensagem de cada vez. Não há execução concorrente dentro de um único ator. -Claro, é possível adotar uma forma de programação ao _estilo de ator_ para qualquer linguagem, seguindo essas regras. -Você também pode usar idiomas de programação orientada a objetos em C, e mesmo modelos de programação estruturada em assembler. -Mas fazer isso requer muita concordância e disciplina da parte de qualquer um que mexa no código. +Claro, é possível adotar uma forma de programação no estilo de atores em qualquer +linguagem, seguindo essas regras. Você também pode usar padrões de programação +orientada a objetos em C, e até padrões de programação estruturada em +assembler. Mas fazer isso requer muita consistência e disciplina da parte de +qualquer um que mexa no código. Gerenciar travas é desnecessário no modelo de atores, como implementado em Erlang e Elixir, onde todos os tipos de dados são imutáveis. Threads-e-travas não vão desaparecer. -Eu só não acho que lidar com esse tipo de entidade básica seja um bom uso de meu tempo +Eu só acho que lidar com esse tipo de entidade básica não é um bom uso do meu tempo quando escrevo aplicações—e não módulos do kernel, drivers de hardware, ou bancos de dados. -Sempre me reservo o direito de mudar de opinião. -Mas neste momento, estou convencido que o modelo de atores é o modelo de programação concorrente mais sensato que existe. -CSP (Communicating Sequential Processes) também é sensato, mas sua implementação em Go deixa de fora algumas restrições. -A ideia em CSP é que corrotinas (ou _goroutines_ em Go) trocam dados e se sincronizam usando filas (chamadas _channels_, "canais", em Go). -Mas Go também permite compartilhamento de memória e travas. Vi um livro sobre Go defende o uso de memória compartilhada e travas em vez de canais—em nome do desempenho. -É difícil abandonar velhos hábitos.((("", startref="CMsoap19")))((("", startref="SSthread19"))) +Sempre me reservo o direito de mudar de opinião. Mas neste momento, estou +convencido que o modelo de atores é o modelo de programação concorrente mais +sensato que existe. CSP (Communicating Sequential Processes) também é sensato, +mas sua implementação em Go deixa de fora algumas restrições importantes. A +ideia em CSP é que corrotinas (ou _goroutines_ em Go) trocam dados e se +sincronizam somente usando filas, chamadas _channels_ (canais) em Go. Mas Go +também permite compartilhamento de memória e travas. Vi um livro sobre Go +defender o uso de memória compartilhada e travas em vez de canais—para +maximizar o desempenho. É difícil abandonar velhos hábitos.((("", +startref="CMsoap19")))((("", startref="SSthread19"))) **** diff --git a/online/cap20.adoc b/online/cap20.adoc index 23ab185..d2ef5b8 100644 --- a/online/cap20.adoc +++ b/online/cap20.adoc @@ -153,7 +153,7 @@ include::../code/20-executors/getflags/flags.py[tags=FLAGS_PY] <7> É uma boa prática adicionar um timeout razoável para operações de rede, para evitar ficar bloqueado sem motivo por vários minutos. <8> Por default, o _HTTPX_ não segue redirecionamentos.footnote:[Definir `follow_redirects=True` não é necessário nesse exemplo, mas eu queria destacar essa importante diferença entre _HTTPX_ e _requests_. Além disso, definir `follow_redirects=True` nesse exemplo me dá a flexibilidade de armazenar os arquivos de imagem em outro lugar no futuro. Acho sensata a configuração default do _HTTPX_, pass:[follow_redirects​=False], pois redirecionamentos inesperados podem mascarar requisições desnecessárias e complicar o diagnóstico de erro.] <9> Não há tratamento de erros nesse script, mas esse método lança uma exceção se o status do HTTP não está na faixa 2XX—algo mutio recomendado para evitar falhas silenciosas. -<10> `download_many` é a função chave para comparar com as implementações [.keep-together]#concorrentes#. +<10> `download_many` é a função chave para comparar com as implementações concorrentes. <11> Percorre a lista de códigos de país em ordem alfabética, para facilitar a confirmação de que a ordem é preservada na saída; retorna o número de códigos de país baixados. <12> Mostra um código de país por vez na mesma linha, para vermos o progresso a cada download. O argumento `end=' '` substitui a costumeira quebra no final de cada linha escrita com um espaço, assim todos os códigos de país aparecem progressivamente na mesma linha. @@ -202,7 +202,7 @@ include::../code/20-executors/getflags/flags_threadpool.py[tags=FLAGS_THREADPOOL <5> Retorna o número de resultados obtidos. Se alguma das chamadas das threads levantar uma exceção, aquela exceção será levantada aqui quando a chamada implícita `next()`, dentro do construtor de `list`, tentar recuperar o valor de retorno correspondente, no iterador retornado por `executor.map`. <6> Chama a função `main` do módulo `flags`, passando a versão concorrente de `download_many`. -Observe que a função `download_one` do <> é essencialmente o corpo do loop `for` na função `download_many` do <>. Essa é uma refatoração comum quando se está escrevendo código concorrente: transformar o corpo de um loop `for` sequencial em uma função a ser chamada de modo concorrente. +Observe que a função `download_one` do <> é essencialmente o corpo do laço `for` na função `download_many` do <>. Essa é uma refatoração comum quando se está escrevendo código concorrente: transformar o corpo de um laço `for` sequencial em uma função a ser chamada de modo concorrente. [TIP] ==== @@ -219,7 +219,7 @@ pass:[ThreadPool​Executor] decide seu valor usando, desde o max_workers = min(32, os.cpu_count() + 4) ---- -A justificativa é apresentada na https://docs.python.org/pt-br/3.10/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor[documentação de `ThreadPoolExecutor`]: +A justificativa é apresentada na https://fpy.li/aj[documentação de `ThreadPoolExecutor`]: [quote] ____ @@ -283,11 +283,11 @@ Várias funções em ambas as bibliotecas retornam _futures_; outras os usam em Um exemplo desse último caso é o `Executor.map`, que vimos no <>: ele retorna um iterador no qual `+__next__+` chama o método `result` de cada _future_, então recebemos os resultados dos _futures_, mas não os _futures_ em si. -Para ver uma experiência prática com os _futures_, podemos reescrever o <> para usar a função https://docs.python.org/pt-br/3/library/concurrent.futures.html#concurrent.futures.as_completed[`concurrent.futures.as_completed`], que recebe um iterável de _futures_ e retorna um iterador que +Para ver uma experiência prática com os _futures_, podemos reescrever o <> para usar a função https://fpy.li/ak[`concurrent.futures.as_completed`], que recebe um iterável de _futures_ e retorna um iterador que entrega _futures_ quando cada um encerra sua execução. Usar `futures.as_completed` exige mudanças apenas na função `download_many`. -A chamada ao `executor.map`, de alto nível, é substituída por dois loops `for`: +A chamada ao `executor.map`, de alto nível, é substituída por dois laços `for`: um para criar e agendar os _futures_, o outro para recuperar seus resultados. Já que estamos aqui, vamos acrescentar algumas chamadas a `print` para mostrar cada _future_ antes e depois do término de sua execução. O <> mostra o código da nova função `download_many`. @@ -364,7 +364,7 @@ tarefas que usam a CPU intensivamente, usando [[launching_processes_sec]] === Iniciando processos com concurrent.futures -A((("concurrent executors", "launching processes with concurrent.futures", id="CElaunch20")))((("concurrent.futures", "launching processes with", id="CFlaunch20")))((("processes", "launching with concurrent.futures", id="Plaunch20"))) https://docs.python.org/pt-br/3/library/concurrent.futures.html[página de documentação de `concurrent.futures`] tem por subtítulo "Iniciando tarefas em paralelo." O pacote permite computação paralela em máquinas multi-núcleo porque suporta a distribuição de trabalho entre múltiplos processos Python usando a classe +A((("concurrent executors", "launching processes with concurrent.futures", id="CElaunch20")))((("concurrent.futures", "launching processes with", id="CFlaunch20")))((("processes", "launching with concurrent.futures", id="Plaunch20"))) https://fpy.li/am[página de documentação de `concurrent.futures`] tem por subtítulo "Iniciando tarefas em paralelo." O pacote permite computação paralela em máquinas multi-núcleo porque suporta a distribuição de trabalho entre múltiplos processos Python usando a classe // `ProcessPoolExecutor` class. pass:[ProcessPool​Executor]. @@ -389,8 +389,8 @@ def download_many(cc_list: list[str]) -> int: O construtor de `ProcessPoolExecutor` também tem um parâmetro `max_workers`, que por default é `None`. Nesse caso, o executor limita o número de processos de trabalho ao número resultante de uma chamada a `os.cpu_count()`. -Processos usam mais memória e demoram mais para iniciar que threads, então o real valor de [.keep-together]#of `ProcessPoolExecutor`# é em tarefas de uso intensivo da CPU. -Vamos voltar ao exemplo de teste de checagem de números primos de<>, e reescrevê-lo com [.keep-together]#`concurrent.futures`.# +Processos usam mais memória e demoram mais para iniciar que threads, então o real valor de of `ProcessPoolExecutor` é em tarefas de uso intensivo da CPU. +Vamos voltar ao exemplo de teste de checagem de números primos de<>, e reescrevê-lo com `concurrent.futures`. [[multicore_prime_redux_sec]] ==== Checador de primos multinúcleo redux @@ -462,7 +462,7 @@ Total time: 9.65s Aqui está o motivo para aquele comportamento de _proc_pool.py_: * Como mencionado antes, `executor.map(check, numbers)` retorna o resultado na mesma ordem em que `numbers` é enviado. -* Por default, _proc_pool.py_ usa um número de processos de trabalho igual ao número de CPUs—isso é o que [.keep-together]#`ProcessPoolExecutor`# faz quando `max_workers` é `None`. Nesse laptop são então 12 processos. +* Por default, _proc_pool.py_ usa um número de processos de trabalho igual ao número de CPUs—isso é o que `ProcessPoolExecutor` faz quando `max_workers` é `None`. Nesse laptop são então 12 processos. * Como estamos submetendo `numbers` em ordem descendente, o primeiro é 9999999999999999; com 9 como divisor, ele retorna rapidamente. * O segundo número é 9999999999999917, o maior número primo na amostra. Ele vai demorar mais que todos os outros para checar. * Enquanto isso, os 11 processos restantes estarão checando outros números, que são ou primos ou compostos com fatores grandes ou compostos com fatores muito pequenos. @@ -495,7 +495,7 @@ include::../code/20-executors/demo_executor_map.py[tags=EXECUTOR_MAP] <4> Cria um `ThreadPoolExecutor` com três threads. <5> Submete cinco tarefas para o `executor`. Já que há apenas três threads, apenas três daquelas tarefas vão iniciar imediatamente: a chamadas `loiter(0)`, `loiter(1)`, e `loiter(2)`; essa é uma chamada não-bloqueante. <6> Mostra imediatamente o `results` da invocação de `executor.map`: é um gerador, como se vê na saída no <>. -<7> A chamada `enumerate` no loop `for` vai invocar implicitamente `next(results)`, que por sua vez vai invocar `f.result()` no _future_ (interno) `f`, representando a primeira chamada, `loiter(0)`. O método `result` vai bloquear a thread até que o _future_ termine, portanto cada iteração nesse loop vai esperar até que o próximo resultado esteja disponível. +<7> A chamada `enumerate` no laço `for` vai invocar implicitamente `next(results)`, que por sua vez vai invocar `f.result()` no _future_ (interno) `f`, representando a primeira chamada, `loiter(0)`. O método `result` vai bloquear a thread até que o _future_ termine, portanto cada iteração nesse laço vai esperar até que o próximo resultado esteja disponível. [role="pagebreak-before less_space"] Encorajo você a rodar o <> e ver o resultado sendo atualizado de forma incremental. Quando for fazer isso, mexa no argumento `max_workers` do pass:[ThreadPool​Executor] e com a função `range`, que produz os argumentos para a chamada a `executor.map`—ou os substitua por listas com valores escolhidos, para criar intervalos diferentes. @@ -533,9 +533,9 @@ $ python3 demo_executor_map.py <3> `loiter(1)` e `loiter(2)` começam imediatamente (como o pool de threads têm três threads de trabalho, é possível rodar três funções de forma concorrente). <4> Isso mostra que o `results` retornado por `executor.map` é um gerador: nada até aqui é bloqueante, independente do número de tarefas e do valor de `max_workers`. <5> Como `loiter(0)` terminou, a primeira thread de trabalho está disponível para iniciar a quarta thread para `loiter(3)`. -<6> Aqui é ponto a execução pode ser bloqueada, dependendo dos parâmetros passados nas chamadas a `loiter`: o método `+__next__+` do gerador `results` precisa esperar até o primeiro _future_ estar completo. Neste caso, ele não vai bloquear porque a chamada a `loiter(0)` terminou antes desse loop iniciar. Observe que tudo até aqui aconteceu dentro do mesmo segundo: 15:56:50. -<7> `loiter(1)` termina um segundo depois, em 15:56:51. A thread está livre para iniciar [.keep-together]#`loiter(4)`.# -<8> O resultado de `loiter(1)` é exibido: `10`. Agora o loop `for` ficará bloqueado, esperando o resultado de `loiter(2)`. +<6> Aqui é ponto a execução pode ser bloqueada, dependendo dos parâmetros passados nas chamadas a `loiter`: o método `+__next__+` do gerador `results` precisa esperar até o primeiro _future_ estar completo. Neste caso, ele não vai bloquear porque a chamada a `loiter(0)` terminou antes desse laço iniciar. Observe que tudo até aqui aconteceu dentro do mesmo segundo: 15:56:50. +<7> `loiter(1)` termina um segundo depois, em 15:56:51. A thread está livre para iniciar `loiter(4)`. +<8> O resultado de `loiter(1)` é exibido: `10`. Agora o laço `for` ficará bloqueado, esperando o resultado de `loiter(2)`. <9> O padrão se repete: `loiter(2)` terminou, seu resultado é exibido; o mesmo ocorre com `loiter(3)`. <10> Há um intervalo de 2s até `loiter(4)` terminar, porque ela começou em 15:56:51 e não fez nada por 4s. @@ -545,7 +545,15 @@ Para fazer isso, precisamos de uma combinação do método `Executor.submit` e d [TIP] ==== -A combinação de `Executor.submit` e `futures.as_completed` é mais flexível que `executor.map`, pois você pode `submit` [.keep-together]#chamáveis# e argumentos diferentes. Já `executor.map` é projetado para rodar o mesmo invocável com argumentos diferentes. Além disso, [.keep-together]#o conjunto# de _futures_ que você passa para `futures.as_completed` pode vir de mais de um executor—talvez alguns tenham sido criados por uma instância de [.keep-together]#`ThreadPoolExecutor`# enquanto outros vem de um [.keep-together]#`ProcessPoolExecutor`.# + +A combinação de `Executor.submit` e `futures.as_completed` é mais flexível que +`executor.map`, pois você pode submeter invocáveis diferentes e argumentos +diferentes. Já `executor.map` é projetado para rodar o mesmo invocável com +argumentos diferentes. Além disso, o conjunto de _futures_ que você passa para +`futures.as_completed` pode vir de mais de um executor—talvez alguns tenham sido +criados por uma instância de `ThreadPoolExecutor` enquanto outros vem de um +`ProcessPoolExecutor`. + ==== Na próxima seção vamos retomar os exemplos de download de bandeiras com novos requisitos que vão nos obrigar a iterar sobre os resultados de `futures.as_completed` em vez de usar `executor.map`.((("", startref="execumap20")))((("", startref="CEexecutormap20"))) @@ -558,7 +566,7 @@ Como((("concurrent executors", "downloads with progress display and error handli Para testar o tratamento de uma variedade de condições de erro, criei os exemplos `flags2`: -flags2_common.py:: Este módulo contém as funções e configurações comuns, usadas por todos os exemplos `flags2`, incluindo a função `main`, que cuida da interpretação da linha de comando, da medição de tempo e de mostrar os resultados. Isso é código de apoio, sem relevância direta para [.keep-together]#o assunto# desse capítulo, então não vou incluir o código-fonte aqui, mas você pode vê-lo no +flags2_common.py:: Este módulo contém as funções e configurações comuns, usadas por todos os exemplos `flags2`, incluindo a função `main`, que cuida da interpretação da linha de comando, da medição de tempo e de mostrar os resultados. Isso é código de apoio, sem relevância direta para o assunto desse capítulo, então não vou incluir o código-fonte aqui, mas você pode vê-lo no pass:[fluentpython/example-code-2e] repositório: https://fpy.li/20-10[_20-executors/getflags/flags2_common.py_]. @@ -566,7 +574,7 @@ flags2_sequential.py:: Um cliente HTTP sequencial com tratamento de erro correto flags2_threadpool.py:: Cliente HTTP concorrente, baseado em `futures.ThreadPoolExecutor`, para demonstrar o tratamento de erros e a integração da barra de progresso. -flags2_asyncio.py:: Mesma funcionalidade do exemplo anterior, mas implementado com `asyncio` e `httpx`. Isso será tratado na <>, [.keep-together]#no <>.# +flags2_asyncio.py:: Mesma funcionalidade do exemplo anterior, mas implementado com `asyncio` e `httpx`. Isso será tratado na <>, no <>. [[careful_testing_clients]] [WARNING] @@ -761,7 +769,7 @@ include::../code/20-executors/getflags/flags2_sequential.py[tags=FLAGS2_DOWNLOAD <5> As exceções do código de status HTTP ocorridas em `get_flag` e não tratadas por `download_one` são tratadas aqui. <6> Outras exceções referentes à rede são tratadas aqui. Qualquer outra exceção vai interromper o script, porque a função `flags2_common.main`, que chama `download_many`, não tem nenhum `try/except`. -<7> Sai do loop se o usuário pressionar Ctrl-C. +<7> Sai do laço se o usuário pressionar Ctrl-C. <8> Se nenhuma exceção saiu de `download_one`, limpa a mensagem de erro. <9> Se houve um erro, muda o `status` local de acordo com o erro. <10> Incrementa o contador para aquele `status`. @@ -851,10 +859,10 @@ Evitando Threads Eu((("concurrent executors", "Soapbox discussion")))((("Soapbox sidebars", "thread avoidance")))((("threads", "thread avoidance"))) concordo com as citações aparentemente contraditórias de David Beazley e Michele Simionato no início desse capítulo. Assisti um curso de graduação sobre concorrência. -Tudo o que vimos foi programação de https://pt.wikipedia.org/wiki/POSIX_Threads[threads POSIX]. +Tudo o que vimos foi programação de https://fpy.li/an[threads POSIX]. O que aprendi: que não quero gerenciar threads e travas pessoalmente, pela mesma razão que não quero gerenciar a alocação e desalocação de memória pessoalmente. Essas tarefas são melhor desempenhadas por programadores de sistemas, que tem o conhecimento, a inclinação e o tempo para fazê-las direito—ou assim esperamos. -Sou pago para desenvolver aplicações, não sistemas operacionais. Não preciso desse controle fino de threads, travas, `malloc` e `free`—veja https://pt.wikipedia.org/wiki/Aloca%C3%A7%C3%A3o_din%C3%A2mica_de_mem%C3%B3ria_em_C["Alocação dinâmica de memória em C"]. +Sou pago para desenvolver aplicações, não sistemas operacionais. Não preciso desse controle fino de threads, travas, `malloc` e `free`—veja https://fpy.li/ap["Alocação dinâmica de memória em C"]. Por isso acho o pacote `concurrent.futures` interessante: ele trata threads, processos, e filas como infraestrutura, algo a seu serviço, não algo que você precisa controlar diretamente. Claro, ele foi projetado pensando em tarefas simples, os assim chamado problemas embaraçosamente paralelos—ao contrário de sistemas operacionais ou servidores de banco de dados, como aponta Simionato naquela citação. diff --git a/online/cap21.adoc b/online/cap21.adoc index 44e2f32..0b91fe8 100644 --- a/online/cap21.adoc +++ b/online/cap21.adoc @@ -26,7 +26,7 @@ em particular da <>), gerenciadores de contexto (no <>). Vamos estudar clientes HTTP concorrentes similares aos vistos no <>, reescritos com corrotinas nativas e gerenciadores de contexto assíncronos, usando a mesma biblioteca _HTTPX_ de antes, mas agora através de sua API assíncrona. -Veremos também como evitar o bloqueio do loop de eventos, delegando operações lentas para um executor de threads ou processos. +Veremos também como evitar o bloqueio do laço de eventos, delegando operações lentas para um executor de threads ou processos. Após os exemplos de clientes HTTP, teremos duas aplicações simples de servidor, uma delas usando o framework cada vez mais popular _FastAPI_. @@ -83,11 +83,11 @@ Corrotina clássica:: Corrotinas clássicas podem delegar para outras corrotinas clássicas usando `yield from`. Corrotinas clássicas não podem ser controladas por `await`, e não são mais suportadas pelo _asyncio_. -Corrotinas baseadas em geradoras:: +Corrotinas baseadas em geradores:: Uma((("generators", "generator-based coroutines")))((("coroutines", "generator-based"))) função geradora decorada com `@types.coroutine`—introduzido no Python 3.5. - Esse decorador torna a geradora compatível com a nova palavra-chave `await`. + Esse decorador torna o gerador compatível com a nova palavra-chave `await`. -Nesse capítulo vamos nos concentrar nas corrotinas nativas, bem como nas _geradoras assíncronas_: +Nesse capítulo vamos nos concentrar nas corrotinas nativas, bem como nos geradores assíncronos: Geradora assíncrona:: Uma((("asynchronous generators"))) função geradora definida com `async def` que usa `yield` em seu corpo. @@ -155,23 +155,23 @@ include::../code/21-async/domains/asyncio/blogdom.py[] ==== <1> Estabelece o comprimento máximo da palavra-chave para domínios, pois quanto menor, melhor. <2> `probe` devolve uma tupla com o nome do domínio e um valor booleano; `True` significa que o domínio foi resolvido. Incluir o nome do domínio aqui facilita a exibição dos resultados. -<3> Obtém uma referência para o loop de eventos do `asyncio`, para usá-la a seguir. -<4> O método corrotina https://docs.python.org/pt-br/3/library/asyncio-eventloop.html#asyncio.loop.getaddrinfo[`loop.getaddrinfo(…)`] -devolve uma https://docs.python.org/pt-br/3/library/socket.html#socket.getaddrinfo[tupla de parâmetros com cinco partes] para conectar ao endereço dado usando um socket. Neste exemplo não precisamos do resultado. Se conseguirmos um resultado, o domínio foi resolvido; caso contrário, não. +<3> Obtém uma referência para o laço de eventos do `asyncio`, para usá-la a seguir. +<4> O método corrotina https://fpy.li/aq[`loop.getaddrinfo(…)`] +devolve uma https://fpy.li/ar[tupla de parâmetros com cinco partes] para conectar ao endereço dado usando um socket. Neste exemplo não precisamos do resultado. Se conseguirmos um resultado, o domínio foi resolvido; caso contrário, não. <5> `main` precisa ser uma corrotina, para podermos usar `await` aqui. <6> Gerador para produzir palavras-chave com tamanho até `MAX_KEYWORD_LEN`. <7> Gerador para produzir nome de domínio com o sufixo `.dev`. <8> Cria uma lista de objetos corrotina, invocando a corrotina `probe` com cada argumento `domain`. <9> `asyncio.as_completed` é um gerador que produz corrotinas que devolvem os resultados das corrotinas passadas a ele. Ele as produz na ordem em que elas terminam seu processamento, não na ordem em que foram submetidas. É similar ao `futures.as_completed`, que vimos no <>, <>. <10> Nesse ponto, sabemos que a corrotina terminou, pois é assim que `as_completed` funciona. Portanto, a expressão `await` não vai bloquear, mas precisamos dela para obter o resultado de `coro`. Se `coro` gerou uma exceção não tratada, ela será gerada novamente aqui. -<11> `asyncio.run` inicia o loop de eventos e retorna apenas quando o loop terminar. Esse é um modelo comum para scripts usando `asyncio`: implementar `main` como uma corrotina e controlá-la com `asyncio.run` dentro do bloco `if __name__ == '__main__':`. +<11> `asyncio.run` inicia o laço de eventos e retorna apenas quando o laço terminar. Esse é um modelo comum para scripts usando `asyncio`: implementar `main` como uma corrotina e controlá-la com `asyncio.run` dentro do bloco `if __name__ == '__main__':`. [TIP] ==== A função `asyncio.get_running_loop` surgiu no Python 3.7, para uso dentro de corrotinas, como visto em `probe`. -Se não houver um loop em execução, `asyncio.get_running_loop` gera um `RuntimeError`. -Sua implementação é mais simples e mais rápida que a de `asyncio.get_event_loop`, que pode iniciar um loop de eventos se necessário. -Desde o Python 3.10, `asyncio.get_event_loop` foi https://docs.python.org/pt-br/3.10/library/asyncio-eventloop.html#asyncio.get_event_loop[descontinuado], e em algum momento se tornará um alias para `asyncio.get_running_loop`. +Se não houver um laço em execução, `asyncio.get_running_loop` gera um `RuntimeError`. +Sua implementação é mais simples e mais rápida que a de `asyncio.get_event_loop`, que pode iniciar um laço de eventos se necessário. +Desde o Python 3.10, `asyncio.get_event_loop` foi https://fpy.li/as[descontinuado], e em algum momento se tornará um alias para `asyncio.get_running_loop`. ==== ==== O truque de Guido para ler código assíncrono @@ -185,7 +185,7 @@ Por exemplo, imagine que o corpo dessa corrotina... [source, python] ---- async def probe(domain: str) -> tuple[str, bool]: - loop = asyncio.get_running_loop() + laço = asyncio.get_running_loop() try: await loop.getaddrinfo(domain, None) except socket.gaierror: @@ -198,7 +198,7 @@ async def probe(domain: str) -> tuple[str, bool]: [source, python] ---- def probe(domain: str) -> tuple[str, bool]: # no async - loop = asyncio.get_running_loop() + laço = asyncio.get_running_loop() try: loop.getaddrinfo(domain, None) # no await except socket.gaierror: @@ -209,10 +209,10 @@ def probe(domain: str) -> tuple[str, bool]: # no async Usar a sintaxe `await loop.getaddrinfo(...)` evita o bloqueio, porque `await` suspende o objeto corrotina atual. Por exemplo, durante a execução da corrotina `probe('if.dev')`, um novo objeto corrotina é criado por `getaddrinfo('if.dev', None)`. -Aplicar `await` sobre ele inicia a consulta de baixo nível `addrinfo` e devolve o controle para o loop de eventos, não para a corrotina `probe(‘if.dev’)`, que está suspensa. -O loop de eventos pode então ativar outros objetos corrotina pendentes, tal como `probe('or.dev')`. +Aplicar `await` sobre ele inicia a consulta de baixo nível `addrinfo` e devolve o controle para o laço de eventos, não para a corrotina `probe(‘if.dev’)`, que está suspensa. +O laço de eventos pode então ativar outros objetos corrotina pendentes, tal como `probe('or.dev')`. -Quando o loop de eventos recebe uma resposta para a consulta `getaddrinfo('if.dev', None)`, +Quando o laço de eventos recebe uma resposta para a consulta `getaddrinfo('if.dev', None)`, aquele objeto corrotina específico prossegue sua execução, e devolve o controle pra o `probe('if.dev')`—que estava suspenso no `await`—e pode agora tratar alguma possível exceção e devolver a tupla com o resultado. Até aqui, vimos `asyncio.as_completed` e `await` sendo aplicados apenas a corrotinas. @@ -238,12 +238,12 @@ Criar a tarefa é o suficiente para agendar a execução da corrotina. Mesmo que você não precise cancelar a tarefa ou esperar por ela, é necessário preservar o objeto `Task` devolvido por `create_task`, atribuindo ele a uma variável ou coleção que você controla. -O loop de eventos usa referências fracas para gerenciar as tarefas, +O laço de eventos usa referências fracas para gerenciar as tarefas, o que significa que elas podem ser descartadas pelo coletor de lixo antes de executarem. Por isso você precisa criar referências fortes para manter cada tarefa na memória. Veja a documentação de -https://docs.python.org/pt-br/3/library/asyncio-task.html#asyncio.create_task[`asyncio.create_task`]. +https://fpy.li/at[`asyncio.create_task`]. Sobre referências fracas, escrevi o artigo https://fpy.li/weakref["Weak References"] _fluentpython.com_ (EN).footnote:[Agradeço ao leitor Samuel Woodward por ter reportado esse erro para a O'Reilly em fevereiro de 2023] ==== @@ -306,14 +306,14 @@ include::../code/20-executors/getflags/flags_asyncio.py[tags=FLAGS_ASYNCIO_START ---- ==== <1> Essa precisa ser uma função comum—não uma corrotina—para poder ser passada para e chamada pela função `main` do módulo _flags.py_ (<>). -<2> Executa o loop de eventos, monitorando o objeto corrotina `supervisor(cc_list)` até que ele retorne. Isso vai bloquear enquanto o loop de eventos roda. O resultado dessa linha é o que quer que `supervisor` devolver. +<2> Executa o laço de eventos, monitorando o objeto corrotina `supervisor(cc_list)` até que ele retorne. Isso vai bloquear enquanto o laço de eventos roda. O resultado dessa linha é o que quer que `supervisor` devolver. <3> Operação de cliente HTTP assíncronas no `httpx` são métodos de `AsyncClient`, que também é um gerenciador de contexto assíncrono: um gerenciador de contexto com métodos assíncronos de configuração e destruição (veremos mais sobre isso na <>). <4> Cria uma lista de objetos corrotina, chamando a corrotina `download_one` uma vez para cada bandeira a ser obtida. <5> Espera pela corrotina `asyncio.gather`, que aceita um ou mais argumentos esperáveis e aguarda até que todos terminem, devolvendo uma lista de resultados para os esperáveis fornecidos, na ordem em que foram enviados. <6> `supervisor` devolve o tamanho da lista vinda de `asyncio.gather`. -Agora vamos revisar a parte superior de _flags_asyncio.py_ (<>). Reorganizei as corrotinas para podermos lê-las na ordem em que são iniciadas pelo loop de eventos. +Agora vamos revisar a parte superior de _flags_asyncio.py_ (<>). Reorganizei as corrotinas para podermos lê-las na ordem em que são iniciadas pelo laço de eventos. [[flags_asyncio_ex]] .flags_asyncio.py: imports and download functions @@ -328,11 +328,11 @@ include::../code/20-executors/getflags/flags_asyncio.py[tags=FLAGS_ASYNCIO_TOP] <3> `download_one` precisa ser uma corrotina nativa, para poder `await` por `get_flag`—que executa a requisição HTTP. Ela então mostra o código de país bandeira baixada, e salva a imagem. <4> `get_flag` precisa receber o `AsyncClient` para fazer a requisição. <5> O método `get` de uma instância de `httpx.AsyncClient` devolve um objeto `ClientResponse`, que também é um gerenciador assíncrono de contexto. -<6> Operações de E/S de rede são implementadas como métodos corrotina, então eles são controlados de forma assíncrona pelo loop de eventos do `asyncio`. +<6> Operações de E/S de rede são implementadas como métodos corrotina, então eles são controlados de forma assíncrona pelo laço de eventos do `asyncio`. [NOTE] ==== -Seria melhor, em termos de desempenho, que a chamada a `save_flag` dentro de `get_flag` fosse assíncrona, evitando bloquear o loop de eventos. +Seria melhor, em termos de desempenho, que a chamada a `save_flag` dentro de `get_flag` fosse assíncrona, evitando bloquear o laço de eventos. Entretanto, atualmente _asyncio_ não oferece uma API assíncrona de acesso ao sistema de arquivos—como faz o Node.js. A <> vai mostrar como delegar `save_flag` para uma thread. @@ -347,24 +347,24 @@ A((("native coroutines", "humble generators and")))((("humble generators")))(((" O seu código fica entre a biblioteca _asyncio_ e as bibliotecas assíncronas que você estiver usando, como por exemplo a _HTTPX_. Isso está ilustrado na <>. [[await_channel_fig]] -.Em um programa assíncrono, uma função do usuário inicia o loop de eventos, agendando uma corrotina inicial com `asyncio.run`. Cada corrotina do usuário aciona a seguinte com uma expressão `await`, formando um canal que permite a comunicação entre uma biblioteca como a _HTTPX_ e o loop de eventos. +.Em um programa assíncrono, uma função do usuário inicia o laço de eventos, agendando uma corrotina inicial com `asyncio.run`. Cada corrotina do usuário aciona a seguinte com uma expressão `await`, formando um canal que permite a comunicação entre uma biblioteca como a _HTTPX_ e o laço de eventos. image::../images/flpy_2101.png[Diagrama do canal await] -Debaixo dos panos, o loop de eventos do `asyncio` faz as chamadas a `.send` que acionam as nossas corrotinas, e nossas corrotinas `await` por outras corrotinas, incluindo corrotinas da biblioteca. +Debaixo dos panos, o laço de eventos do `asyncio` faz as chamadas a `.send` que acionam as nossas corrotinas, e nossas corrotinas `await` por outras corrotinas, incluindo corrotinas da biblioteca. Como já mencionado, a maior parte da implementação de `await` vem de `yield from`, que também usa chamadas a `.send` para acionar corrotinas. -O canal `await` acaba por chegar a um esperável de baixo nível, que devolve um gerador que o loop de eventos pode acionar em resposta a eventos tais com cronômetros ou E/S de rede. +O canal `await` acaba por chegar a um esperável de baixo nível, que devolve um gerador que o laço de eventos pode acionar em resposta a eventos tais com cronômetros ou E/S de rede. Os esperáveis e geradores no final desses canais `await` estão implementados nas profundezas das bibliotecas, não são parte de suas APIs e podem ser extensões Python/C. Usando funções como `asyncio.gather` e `asyncio.create_task`, -é possível iniciar múltiplos canais `await` concorrentes, permitindo a execução concorrente de múltiplas operações de E/S acionadas por um único loop de eventos, em uma única thread. +é possível iniciar múltiplos canais `await` concorrentes, permitindo a execução concorrente de múltiplas operações de E/S acionadas por um único laço de eventos, em uma única thread. ==== O problema do tudo ou nada Observe que, no <>, não pude reutilizar a função `get_flag` de _flags.py_ (<>). Tive que reescrevê-la como uma corrotina para usar a API assíncrona do _HTTPX_. -Para((("asyncio package", "achieving peak performance with"))) obter o melhor desempenho do _asyncio_, precisamos substituir todas as funções que fazem E/S por uma versão assíncrona, que seja ativada com `await` ou `asyncio.create_task`. Dessa forma o controle é devolvido ao loop de eventos enquanto a função aguarda pela operação de entrada ou saída. Se você não puder reescrever a função bloqueante como uma corrotina, deveria executá-la em uma thread ou um processo separados, como veremos na <>. +Para((("asyncio package", "achieving peak performance with"))) obter o melhor desempenho do _asyncio_, precisamos substituir todas as funções que fazem E/S por uma versão assíncrona, que seja ativada com `await` ou `asyncio.create_task`. Dessa forma o controle é devolvido ao laço de eventos enquanto a função aguarda pela operação de entrada ou saída. Se você não puder reescrever a função bloqueante como uma corrotina, deveria executá-la em uma thread ou um processo separados, como veremos na <>. Essa é a razão da escolha da epígrafe desse capítulo, que incluí o seguinte conselho: "[Ou] você reescreve todo o código, de forma que nada nele bloqueie [o processamento] ou você está só perdendo tempo."" @@ -523,11 +523,11 @@ Primeira diferença: ele exige o parâmetro `client`. <2> Segunda e terceira diferenças: `.get` é um método de `AsyncClient`, e é uma corrotina, então precisamos `await` por ela. <3> Usa o `semaphore` como um gerenciador de contexto assíncrono, assim o programa como um todo não é bloqueado; apenas essa corrotina é suspensa quando o contador do semáforo é zero. Veja mais sobre isso em <>. <4> A lógica de tratamento de erro é idêntica à de `download_one`, do <>. -<5> Salvar a imagem é uma operação de E/S. Para não bloquear o loop de eventos, roda `save_flag` em uma thread. +<5> Salvar a imagem é uma operação de E/S. Para não bloquear o laço de eventos, roda `save_flag` em uma thread. No _asyncio_, toda a comunicação de rede é feita com corrotinas, mas não E/S de arquivos. Entretanto, E/S de arquivos também é "bloqueante"—no sentido que ler/escrever arquivos é https://fpy.li/21-15[milhares de vezes mais demorado] que ler/escrever na RAM. -Se você estiver usando https://pt.wikipedia.org/wiki/Armazenamento_conectado_%C3%A0_rede[armazenamento conectado à rede], isso pode até envolver E/S de rede internamente. +Se você estiver usando https://fpy.li/av[armazenamento conectado à rede], isso pode até envolver E/S de rede internamente. Desde o Python 3.9, a corrotina `asyncio.to_thread` facilitou delegar operações de arquivo para um pool de threads fornecido pelo _asyncio_. Se você precisa suportar Python 3.7 ou 3.8, @@ -538,7 +538,7 @@ Mas primeiro, vamos terminar nosso estudo do código do cliente HTTP. Clientes de rede((("throttling", id="throttle21")))((("semaphores", id="semaphores21"))) como os que estamos estudando devem ser _limitados_ ("_throttled_") (isto é, desacelerados) para que não martelem o servidor com um número excessivo de requisições concorrentes. -Um https://pt.wikipedia.org/wiki/Sem%C3%A1foro_(computa%C3%A7%C3%A3o)[_semáforo_] +Um https://fpy.li/aw[_semáforo_] é uma estrutura primitiva de sincronização, mais flexível que uma trava. Um semáforo pode ser mantido por múltiplas corrotinas, com um número máximo configurável. Isso o torna ideial para limitar o número de corrotinas concorrentes ativas. @@ -553,7 +553,7 @@ e passado como o argumento `semaphore` para `download_one` no < `supervisor` recebe os mesmos argumentos que a função `download_many`, mas ele não pode ser invocado diretamente de `main`, pois é uma corrotina e não uma função simples como `download_many`. <2> Cria um `asyncio.Semaphore` que não vai permitir mais que `concur_req` corrotinas ativas entre aquelas usando este semáforo. O valor de `concur_req` é calculado pela função `main` de _flags2_common.py_, baseado nas opções de linha de comando e nas constantes estabelecidas em cada exemplo. <3> Cria uma lista de objetos corrotina, um para cada chamada à corrotina `download_one`. -<4> Obtém um iterador que vai devolver objetos corrotina quando eles terminarem sua execução. Não coloquei essa chamada a `as_completed` diretamente no loop `for` abaixo porque posso precisar envolvê-la com o iterador `tqdm` para a barra de progresso, dependendo da opção do usuário para verbosidade. +<4> Obtém um iterador que vai devolver objetos corrotina quando eles terminarem sua execução. Não coloquei essa chamada a `as_completed` diretamente no laço `for` abaixo porque posso precisar envolvê-la com o iterador `tqdm` para a barra de progresso, dependendo da opção do usuário para verbosidade. <5> Envolve o iterador `as_completed` com a função geradora `tqdm`, para mostrar o progresso. <6> Declara e inicializa `error` com `None`; essa variável será usada para manter uma exceção além do bloco `try/except`, se alguma for levantada. -<7> Itera pelos objetos corrotina que terminaram a execução; esse loop é similar ao de `download_many` em <>. +<7> Itera pelos objetos corrotina que terminaram a execução; esse laço é similar ao de `download_many` em <>. <8> `await` pela corrotina para obter seu resultado. Isso não bloqueia porque `as_completed` só produz corrotinas que já terminaram. <9> Essa atribuição é necessária porque o escopo da variável `exc` é limitado a essa cláusula `except`, mas preciso preservar o valor para uso posterior. <10> Mesmo que acima. <11> Se houve um erro, muda o `status`. <12> Se em modo verboso, extrai a URL da exceção que foi levantada... <13> ...e extrai o nome do arquivo para mostrar o código do país em seguida. -<14> `download_many` instancia o objeto corrotina `supervisor` e o passa para o loop de eventos com `asyncio.run`, coletando o contador que `supervisor` devolve quando o loop de eventos termina. +<14> `download_many` instancia o objeto corrotina `supervisor` e o passa para o laço de eventos com `asyncio.run`, coletando o contador que `supervisor` devolve quando o laço de eventos termina. No <>, não podíamos usar o mapeamento de `futures` para os códigos de país que vimos em <>, porque os esperáveis devolvidos por `asyncio.as_completed` são os mesmos esperáveis que passamos na chamada a `as_completed`. Internamente, o mecanismo do _asyncio_ pode substituir os esperáveis que fornecemos por outros que irão, no fim, produzir os mesmos resultados.footnote:[Um discussão detalhada sobre esse tópico pode era encontrada em uma thread de discussão que iniciei no grupo python-tulip, intitulada https://fpy.li/21-19["Which other futures may come out of asyncio.as_completed?" (_Que outros futures podem sair de asyncio.as_completed?_ )]. Guido responde e fornece detalhes sobre a implementação de `as_completed`, bem como sobre a relação próxima entre _futures_ e corrotinas no _asyncio_.] [TIP] ==== Já que não podia usar os esperáveis como chaves para recuperar os códigos de país de um `dict` em caso de falha, tive que extrair o código de pais da exceção. -Para fazer isso, mantive a exceção na variável `error`, permitindo sua recuperação fora do bloco `try/except`. Python não é uma linguagem com escopo de bloco: comandos como loops e `try/except` não criam um escopo local aos blocos que eles gerenciam. +Para fazer isso, mantive a exceção na variável `error`, permitindo sua recuperação fora do bloco `try/except`. Python não é uma linguagem com escopo de bloco: comandos como laços e `try/except` não criam um escopo local aos blocos que eles gerenciam. Mas se uma cláusula `except` vincula uma exceção a uma variável, como as variáveis `exc` que acabamos de ver—aquele vínculo só existe no bloco dentro daquela cláusula `except` específica. ==== @@ -706,7 +706,7 @@ Vamos agora discutir o uso de executores de threads ou processos na programaçã === Delegando tarefas a executores Uma((("asynchronous programming", "delegating tasks to executors", id="APRdelegat21")))((("executors, delegating tasks to", id="exedel21"))) vantagem importante do Node.js sobre Python para programação assíncrona é a biblioteca padrão do Node.js, que inclui APIs assíncronas para toda a E/S—não apenas para E/S de rede. -No Python, se você não for cuidadosa, a E/S de arquivos pode degradar seriamente o desempenho de aplicações assíncronas, pois ler e escrever no armazenamento desde a thread principal bloqueia o loop de eventos. +No Python, se você não for cuidadosa, a E/S de arquivos pode degradar seriamente o desempenho de aplicações assíncronas, pois ler e escrever no armazenamento desde a thread principal bloqueia o laço de eventos. No corrotina `download_one` de <>, usei a seguinte linha para salvar a imagem baixada para o disco: @@ -727,10 +727,10 @@ substitua aquela linha pelas linhas em <>. include::../code/20-executors/getflags/flags2_asyncio_executor.py[tags=FLAGS2_ASYNCIO_EXECUTOR] ---- ==== -<1> Obtém uma referência para o loop de eventos. -<2> O primeiro argumento é o executor a ser utilizado; passar `None` seleciona o default, `ThreadPoolExecutor`, que está sempre disponível no loop de eventos do `asyncio`. +<1> Obtém uma referência para o laço de eventos. +<2> O primeiro argumento é o executor a ser utilizado; passar `None` seleciona o default, `ThreadPoolExecutor`, que está sempre disponível no laço de eventos do `asyncio`. <3> Você pode passar argumentos posicionais para a função a ser executada, mas se você precisar passar argumentos nomeados, vai precisar recorrer a `functool.partial`, como descrito na -https://docs.python.org/pt-br/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor[documentação de `run_in_executor`]. +https://fpy.li/ax[documentação de `run_in_executor`]. A função mais recente `asyncio.to_thread` é mais fácil de usar e mais flexível, já que também aceita argumentos nomeados. @@ -766,7 +766,7 @@ Agora saímos de scripts cliente para escrever servidores com o `asyncio`.((("", === Programando servidores asyncio O((("asynchronous programming", "writing asyncio servers", id="APRwrit21")))((("asyncio package", "writing asyncio servers", id="APwrite21")))((("servers", "writing asyncio servers", id="Sasyncio21"))) exemplo clássico de um servidor TCP de brinquedo é um -https://docs.python.org/pt-br/3/library/asyncio-stream.html#tcp-echo-server-using-streams[servidor eco]. Vamos escrever brinquedos um pouco mais interessantes: utilitários de servidor para busca de caracteres Unicode, primeiro usando HTTP com a _FastAPI_, depois usando TCP puro apenas com `asyncio`. +https://fpy.li/7g[servidor eco]. Vamos escrever brinquedos um pouco mais interessantes: utilitários de servidor para busca de caracteres Unicode, primeiro usando HTTP com a _FastAPI_, depois usando TCP puro apenas com `asyncio`. Esse servidores permitem que os usuários façam consultas sobre caracteres Unicode baseadas em palavras em seus nomes padrão no módulo `unicodedata` que discutimos na <>. A <> mostra uma sessão com o _web_mojifinder.py_, o primeiro servidor que escreveremos. @@ -792,7 +792,7 @@ Após indexar os dados do Unicode 13.0.0 incluídos no Python 3.10, `'DIGIT'` se e `'SEVEN'` para 143, incluindo U+1F556—CLOCK FACE SEVEN OCLOCK e U+2790—DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SEVEN. -Veja a <> para uma demonstração usando os registro para `'CAT'` e `'FACE'`.footnote:[O ponto de interrogação encaixotado na captura de tela não é um defeito do livro ou do ebook que você está lendo. É o caractere U+101EC—PHAISTOS DISC SIGN CAT, que não existe na fonte do terminal que usei. O https://pt.wikipedia.org/wiki/Disco_de_Festo[Disco de Festo] é um artefato antigo inscrito com pictogramas, descoberto na ilha de Creta.] +Veja a <> para uma demonstração usando os registro para `'CAT'` e `'FACE'`.footnote:[O ponto de interrogação encaixotado na captura de tela não é um defeito do livro ou do ebook que você está lendo. É o caractere U+101EC—PHAISTOS DISC SIGN CAT, que não existe na fonte do terminal que usei. O https://fpy.li/ay[Disco de Festo] é um artefato antigo inscrito com pictogramas, descoberto na ilha de Creta.] [[inverted_index_fig]] .Explorando o atributo `entries` e o método `search` de `InvertedIndex` no console de Python @@ -802,7 +802,7 @@ O método `InvertedIndex.search` quebra a consulta em palavras separadas, e devo É por isso que buscar por "face" encontra 171 resultados, "cat" encontra 14, mas "cat face" apenas 10. Essa é a bela ideia por trás dos índices invertidos: uma pedra fundamental da recuperação de informação—a teoria por trás dos mecanismos de busca. -Veja o artigo https://pt.wikipedia.org/wiki/Listas_invertidas["Listas Invertidas"] na Wikipedia para saber mais. +Veja o artigo https://fpy.li/az["Listas Invertidas"] na Wikipedia para saber mais. **** [[fastapi_web_service_sec]] @@ -904,10 +904,10 @@ O((("TCP servers", id="tcp21")))((("servers", "TCP servers", id="Stcp22"))) prog image::../images/flpy_2105.png[Captura de tela de conexão via telnet com tcp_mojifinder.py] Este programa é duas vezes mais longo que o _web_mojifinder.py_, então dividi sua apresentação em três partes: -<>, <>, e <>. +<>, <>, e <>. O início de _tcp_mojifinder.py_—incluindo os comandos `import`—está no <>,mas vou começar descrevendo a corrotina `supervisor` e a função `main` que controla o programa. -[[tcp_mojifinder_main]] +[[ex_tcp_mojifinder_main]] .tcp_mojifinder.py: um servidor TCP simples; continua em <> ==== [source, python] @@ -917,14 +917,14 @@ include::../code/21-async/mojifinder/tcp_mojifinder.py[tags=TCP_MOJIFINDER_MAIN] ==== <1> Este `await` rapidamente recebe um instância de `asyncio.Server`, um servidor TCP baseado em _sockets_. Por default, `start_server` cria e inicia o servidor, então ele está pronto para receber conexões. <2> O primeiro argumento para `start_server` é `client_connected_cb`, um callback para ser executado quando a conexão com um novo cliente se inicia. O callback pode ser uma função ou uma corrotina, mas precisa aceitar exatamente dois argumentos: um `asyncio.StreamReader` e um `asyncio.StreamWriter`. Entretanto, minha corrotina `finder` também precisa receber um `index`, então usei `functools.partial` para vincular aquele parâmetro e obter um invocável que receber o leitor (`asyncio.StreamReader`) e o escritor (`asyncio.StreamWriter`). Adaptar funções do usuário a APIs de callback é o caso de uso mais comum de `functools.partial`. -<3> `host` e `port` são o segundo e o terceiro argumentos de `start_server`. Veja a assinatura completa na https://docs.python.org/pt-br/3/library/asyncio-stream.html#asyncio.start_server[documentação do `asyncio`]. +<3> `host` e `port` são o segundo e o terceiro argumentos de `start_server`. Veja a assinatura completa na https://fpy.li/b2[documentação do `asyncio`]. <4> Este `cast` é necessário porque o _typeshed_ tem uma dica de tipo desatualizada para a propriedade `sockets` da classe `Server`—isso em maio de 2021. Veja https://fpy.li/21-36[Issue #5535 no _typeshed_].footnote:[O issue #5535 está fechado desde outubro de 2021, mas o Mypy não lançou uma nova versão desde então, daí o erro permanece.] <5> Mostra o endereço e a porta do primeiro _socket_ do servidor. <6> Apesar de `start_server` já ter iniciado o servidor como uma tarefa concorrente, preciso usar o `await` no método `serve_forever`, para que meu `supervisor` seja suspenso aqui. -Sem essa linha, o `supervisor` retornaria imediatamente, encerrando o loop iniciado com `asyncio.run(supervisor(…))`, e fechando o programa. A -https://docs.python.org/pt-br/3/library/asyncio-eventloop.html#asyncio.Server.serve_forever[documentação de `Server.serve_forever`] diz: "Este método pode ser chamado se o servidor já estiver aceitando conexões." +Sem essa linha, o `supervisor` retornaria imediatamente, encerrando o laço iniciado com `asyncio.run(supervisor(…))`, e fechando o programa. A +https://fpy.li/b3[documentação de `Server.serve_forever`] diz: "Este método pode ser chamado se o servidor já estiver aceitando conexões." <7> Constrói o índice invertido.footnote:[O revisor técnico Leonardo Rochael apontou que a construção do índice poderia ser delegada a outra thread, usando `loop.run_with_executor()` na corrotina `supervisor`. Dessa forma o servidor estaria pronto para receber requisições imediatamente, enquanto o índice é construído. Isso é verdade, mas como consultar o índice é a única coisa que esse servidor faz, isso não seria uma grande vantagem nesse exemplo.] -<8> Inicia o loop de eventos rodando `supervisor`. +<8> Inicia o laço de eventos rodando `supervisor`. <9> Captura `KeyboardInterrupt` para evitar o traceback dispersivo quando encerro o servidor com Ctrl-C, no terminal onde ele está rodando. Pode ser mais fácil entender como o controle flui em _tcp_mojifinder.py_ estudando a saída que ele gera no console do servidor, listada em <>. @@ -950,21 +950,21 @@ $ ==== <1> Saída de `main`. Antes da próxima linha surgir, vi um intervalo de 0,6s na minha máquina, enquanto o índice era construído. <2> Saída de `supervisor`. -<3> Primeira iteração de um loop `while` em `finder`. A pilha TCP/IP atribuiu a porta 58192 a meu cliente Telnet. Se você conectar diversos clientes ao servidor, verá suas várias portas aparecerem na saída. -<4> Segunda iteração do loop `while` em `finder`. -<5> Eu apertei Ctrl-C no terminal cliente; o loop `while` em `finder` termina. +<3> Primeira iteração de um laço `while` em `finder`. A pilha TCP/IP atribuiu a porta 58192 a meu cliente Telnet. Se você conectar diversos clientes ao servidor, verá suas várias portas aparecerem na saída. +<4> Segunda iteração do laço `while` em `finder`. +<5> Eu apertei Ctrl-C no terminal cliente; o laço `while` em `finder` termina. <6> A corrotina `finder` mostra essa mensagem e então encerra. Enquanto isso o servidor continua rodando, pronto para receber outro cliente. -<7> Aperto Ctrl-C no terminal do servidor; `server.serve_forever` é cancelado, encerrando `supervisor` e o loop de eventos. +<7> Aperto Ctrl-C no terminal do servidor; `server.serve_forever` é cancelado, encerrando `supervisor` e o laço de eventos. <8> Saída de `main`. -Após `main` construir o índice e iniciar o loop de eventos, `supervisor` rapidamente mostra a mensagem `Serving on…`, e é suspenso na linha `await server.serve_forever()`. Nesse ponto o controle flui para dentro do loop de eventos e lá permanece, voltando ocasionalmente para a corrotina `finder`, que devolve o controle de volta para o loop de eventos sempre que precisa esperar que a rede envie ou receba dados. +Após `main` construir o índice e iniciar o laço de eventos, `supervisor` rapidamente mostra a mensagem `Serving on…`, e é suspenso na linha `await server.serve_forever()`. Nesse ponto o controle flui para dentro do laço de eventos e lá permanece, voltando ocasionalmente para a corrotina `finder`, que devolve o controle de volta para o laço de eventos sempre que precisa esperar que a rede envie ou receba dados. -Enquanto o loop de eventos estiver ativo, uma nova instância da corrotina `finder` será iniciada para cada cliente que se conecte ao servidor. Dessa forma, múltiplos clientes podem ser atendidos de forma concorrente por esse servidor simples. Isso segue até que ocorra um `KeyboardInterrupt` no servidor ou que seu processo seja eliminado pelo SO. +Enquanto o laço de eventos estiver ativo, uma nova instância da corrotina `finder` será iniciada para cada cliente que se conecte ao servidor. Dessa forma, múltiplos clientes podem ser atendidos de forma concorrente por esse servidor simples. Isso segue até que ocorra um `KeyboardInterrupt` no servidor ou que seu processo seja eliminado pelo SO. Agora vamos ver o início de _tcp_mojifinder.py_, com a corrotina `finder`. [[tcp_mojifinder_top]] -.tcp_mojifinder.py: continuação de <> +.tcp_mojifinder.py: continuação de <> ==== [source, python] ---- @@ -975,7 +975,7 @@ include::../code/21-async/mojifinder/tcp_mojifinder.py[tags=TCP_MOJIFINDER_TOP] <1> `format_results` é útil para mostrar os resultado de `InvertedIndex.search` em uma interface de usuário baseada em texto, como a linha de comando ou uma sessão Telnet. <2> Para passar `finder` para `asyncio.start_server`, a envolvi com `functools.partial`, porque o servidor espera uma corrotina ou função que receba apenas os argumentos `reader` e `writer`. <3> Obtém o endereço do cliente remoto ao qual o socket está conectado. -<4> Esse loop controla um diálogo que persiste até um caractere de controle ser recebido do cliente. +<4> Esse laço controla um diálogo que persiste até um caractere de controle ser recebido do cliente. <5> O método `StreamWriter.write` não é uma corrotina, é apenas um função normal; essa linha envia o prompt `?>`. <6> O `StreamWriter.drain` esvazia o buffer de `writer`; ela é uma corrotina, então precisa ser acionada com `await`. <7> `StreamWriter.readline` é um corrotina que devolve `bytes`. @@ -983,11 +983,11 @@ include::../code/21-async/mojifinder/tcp_mojifinder.py[tags=TCP_MOJIFINDER_TOP] <9> Decodifica os `bytes` para `str`, usando a codificação UTF-8 como default. <10> Pode ocorrer um `UnicodeDecodeError` quando o usuário digita Ctrl-C e o cliente Telnet envia caracteres de controle; se isso acontecer, substitui a consulta pelo caractere null, para simplificar. <11> Registra a consulta no console do servidor. -<12> Sai do loop se um caractere de controle ou null foi recebido. +<12> Sai do laço se um caractere de controle ou null foi recebido. <13> `search` realiza a busca efetiva; o código será apresentado a seguir. <14> Registra a resposta no console do servidor. <15> Fecha o `StreamWriter`. -<16> Espera até `StreamWriter` fechar. Isso é recomendado na https://docs.python.org/pt-br/3/library/asyncio-stream.html#asyncio.StreamWriter.close[documentação do método `.close()`]. +<16> Espera até `StreamWriter` fechar. Isso é recomendado na https://fpy.li/b4[documentação do método `.close()`]. <17> Registra o final dessa sessão do cliente no console do servidor. O último pedaço desse exemplo é a corrotina `search`, listada no <>. @@ -1012,10 +1012,14 @@ Observe que toda a E/S de rede em _tcp_mojifinder.py_ é feita em `bytes`; preci [WARNING] ==== Veja que alguns dos métodos de E/S são corrotinas, e precisam ser acionados com `await`, enquanto outros são funções simples, Por exemplo, `StreamWriter.write` é uma função normal, porque escreve em um buffer. -Por outro lado, `StreamWriter.drain`—que esvazia o buffer e executa o E/S de rede—é uma corrotina, assim como `StreamReader.readline`—mas não `StreamWriter.writelines`! Enquanto estava escrevendo a primeira edição desse livro, a documentação da API `asyncio` API docs foi melhorada pela https://docs.python.org/pt-br/3/library/asyncio-stream.html#streamwriter[indicação clara das corrotinas como tal]. +Por outro lado, `StreamWriter.drain`—que esvazia o buffer e executa o E/S de rede—é uma corrotina, assim como `StreamReader.readline`—mas não `StreamWriter.writelines`! Enquanto estava escrevendo a primeira edição desse livro, a documentação da API `asyncio` API docs foi melhorada pela https://fpy.li/b5[indicação clara das corrotinas como tal]. ==== -O código de _tcp_mojifinder.py_ se vale da https://fpy.li/21-40[API Streams] de alto nível do `asyncio`, que fornece um servidor pronto para ser usado, de forma que basta implemetar uma função de processamento, que pode ser um callback simples ou uma corrotina. Há também uma https://docs.python.org/pt-br/3/library/asyncio-protocol.html[API de Transportes e Protocolos] (EN) de baixo nível, inspirada pelas abstrações transporte e protocolo do framework _Twisted_. Veja a documentação do `asyncio` para mais informações, incluindo os https://docs.python.org/pt-br/3/library/asyncio-protocol.html#tcp-echo-server[servidores echo e clientes TCP e UDP ] implementados com aquela API de nível mais baixo. +O código de _tcp_mojifinder.py_ se vale da https://fpy.li/21-40[API Streams] de alto nível do `asyncio`, que fornece um servidor pronto para ser usado, de forma que basta implemetar uma função de processamento, que pode ser um callback simples ou uma corrotina. Há também uma +https://fpy.li/bb[API de Transportes e Protocolos] +de baixo nível, inspirada pelas abstrações transporte e protocolo do framework _Twisted_. +Veja a documentação do `asyncio` para mais informações, incluindo os +https://fpy.li/bc[servidores echo e clientes TCP e UDP ] implementados com aquela API de nível mais baixo. Nosso próximo tópico é `async for` e os objetos que a fazem funcionar.((("", startref="tcp21")))((("", startref="APRwrit21")))((("", startref="APwrite21")))((("", startref="Stcp22")))((("", startref="Sasyncio21"))) @@ -1045,8 +1049,8 @@ async def go(): Nesse exemplo, a consulta vai devolver uma única linha, mas em um cenário realista é possível receber milhares de linhas na resposta a um `SELECT`. Para respostas grandes, o cursor não será carregado com todas as linhas de uma vez só. -Assim é importante que `async for row in cur:` não bloqueie o loop de eventos enquanto o cursor pode estar esperando por linhas adicionais. -Ao implementar o cursor como um iterador assíncrono, _aiopg_ pode devolver o controle para o loop de eventos a cada chamada a `+__anext__+`, e continuar mais tarde, quando mais linhas cheguem do PostgreSQL. +Assim é importante que `async for row in cur:` não bloqueie o laço de eventos enquanto o cursor pode estar esperando por linhas adicionais. +Ao implementar o cursor como um iterador assíncrono, _aiopg_ pode devolver o controle para o laço de eventos a cada chamada a `+__anext__+`, e continuar mais tarde, quando mais linhas cheguem do PostgreSQL. [[async_gen_func_sec]] @@ -1064,7 +1068,7 @@ Vamos ver a implementação de _domainlib.py_ logo, mas primeiro examinaremos co [[python_async_console_sec]] ===== Experimentando com o console assíncrono de Python -https://fpy.li/21-44[Desde o Python 3.8], é possível rodar o interpretador com a opção de linha de comando `-m asyncio`, para obter um "async REPL": um console de Python que importa `asyncio`, fornece um loop de eventos ativo, e aceita `await`, `async for`, e `async with` no prompt principal—que em qualquer outro contexto são erros de sintaxe quando usados fora de corrotinas nativas.footnote:[Isso é ótimo para experimentação, como o console do Node.js. Agradeço a Yury Selivanov por mais essa excelente contribuição para Python assíncrono.] +https://fpy.li/21-44[Desde o Python 3.8], é possível rodar o interpretador com a opção de linha de comando `-m asyncio`, para obter um "async REPL": um console de Python que importa `asyncio`, fornece um laço de eventos ativo, e aceita `await`, `async for`, e `async with` no prompt principal—que em qualquer outro contexto são erros de sintaxe quando usados fora de corrotinas nativas.footnote:[Isso é ótimo para experimentação, como o console do Node.js. Agradeço a Yury Selivanov por mais essa excelente contribuição para Python assíncrono.] Para experimentar com o _domainlib.py_, vá ao diretório _21-async/domains/asyncio/_ na sua cópia local do https://fpy.li/code[repositório de código do _Python Fluente_]. Aí rode:: @@ -1142,7 +1146,7 @@ TypeError: 'async_generator' object is not iterable ==== <1> Chamar uma corrotina nativa devolve um objeto corrotina. <2> Chamar um gerador assíncrono devolve um objeto `async_generator`. -<3> Não podemos usar um loop `for` regular com geradores assíncronos, porque eles implementam `+__aiter__+` em vez de `+__iter__+`. +<3> Não podemos usar um laço `for` regular com geradores assíncronos, porque eles implementam `+__aiter__+` em vez de `+__iter__+`. Geradores assíncronos são acionados por `async for`, que pode ser um comando bloqueante (como visto em <>), e também podem aparecer em compreensões assíncronas, que veremos mais tarde. @@ -1170,7 +1174,7 @@ include::../code/21-async/domains/asyncio/domainlib.py[] [NOTE] ==== -O loop `for` no <> poderia ser mais conciso: +O laço `for` no <> poderia ser mais conciso: [source, python] ---- @@ -1234,7 +1238,7 @@ Geradores têm um uso adicional, não relacionado à iteração: ele podem ser u [[async_gen_context_mngr_sec]] ===== Geradores assíncronos como gerenciadores de contexto -Escrever((("context managers", "asynchronous generators as"))) nossos próprios gerenciadores de contexto assíncronos não é uma tarefa de programação frequente, mas se você precisar escrever um, considere usar o decorador https://docs.python.org/pt-br/3/library/contextlib.html#contextlib.asynccontextmanager[`@asynccontextmanager`] (EN), incluído no módulo `contextlib` no Python 3.7. +Escrever((("context managers", "asynchronous generators as"))) nossos próprios gerenciadores de contexto assíncronos não é uma tarefa de programação frequente, mas se você precisar escrever um, considere usar o decorador https://fpy.li/b6[`@asynccontextmanager`] (EN), incluído no módulo `contextlib` no Python 3.7. Ele é muito similar ao decorador `@contextmanager` que estudamos na <>. Um exemplo interessante da combinação de `@asynccontextmanager` com `loop.run_in_executor` aparece no livro de Caleb Hattingh, @@ -1249,7 +1253,7 @@ from contextlib import asynccontextmanager @asynccontextmanager async def web_page(url): # <1> - loop = asyncio.get_running_loop() # <2> + laço = asyncio.get_running_loop() # <2> data = await loop.run_in_executor( # <3> None, download_webpage, url) yield data # <4> @@ -1261,7 +1265,7 @@ async with web_page('google.com') as data: # <6> ==== <1> A função decorada precisa ser um gerador assíncrono. <2> Uma pequena atualização no código de Caleb: usar o `get_running_loop`, mais leve, no lugar de `get_event_loop`. -<3> Suponha que `download_webpage` é uma função bloqueante que usa a biblioteca _requests_; vamos rodá-la em uma thread separada, para evitar o bloqueio do loop de eventos. +<3> Suponha que `download_webpage` é uma função bloqueante que usa a biblioteca _requests_; vamos rodá-la em uma thread separada, para evitar o bloqueio do laço de eventos. <4> Todas as linhas antes dessa expressão `yield` vão se tornar o método corrotina `+__aenter__+` do gerenciador de contexto assíncrono criado pelo decorador. O valor de `data` será vinculado à variável `data` após a cláusula `as` no comando `async with` abaixo. <5> As linhas após o `yield` se tornarão o método corrotina `+__aexit__+`. Aqui outra chamada bloqueante é delegada para um executor de threads. <6> Usa `web_page` com `async with`. @@ -1269,7 +1273,7 @@ async with web_page('google.com') as data: # <6> Isso é muito similar ao decorador sequencial `@contextmanager`. Por favor, consulte a <> para mais detalhes, inclusive o tratamento de erro na linha do `yield`. Para outro exemplo usando `@asynccontextmanager`, veja a -https://docs.python.org/pt-br/3/library/contextlib.html#contextlib.asynccontextmanager[documentação do `contextlib`]. +https://fpy.li/b6[documentação do `contextlib`]. Por fim, vamos terminar nossa jornada pelas funções geradoras assíncronas comparado-as com as corrotinas nativas. @@ -1354,7 +1358,7 @@ Usar `await` em uma compreensão de lista é similar a usar `asyncio.gather`. Mas `gather` dá a você um maior controle sobre o tratamento de exceções, graças ao seu argumento opcional `return_exceptions`. Caleb Hattingh recomenda sempre definir `return_exceptions=True` (o default é `False`). Veja a -https://docs.python.org/pt-br/3/library/asyncio-task.html#asyncio.gather[documentação de `asyncio.gather`] +https://fpy.li/b7[documentação de `asyncio.gather`] para mais informações. ==== @@ -1408,7 +1412,7 @@ Esses artefatos são muitas vezes usados com o _asyncio_ mas, na verdade, eles s === Programação assíncrona além do asyncio: Curio -Os elementos da linguagem((("asynchronous programming", "Curio project", id="APRcurio21")))((("Curio project", id="curio21"))) `async/await` de Python não estão presos a nenhum loop de eventos ou biblioteca específicos.footnote:[Em contraste com o Javascript, onde `async/await` são atrelados ao loop de eventos que é inseparável do ambiente de runtime, isto é, um navegador, o Node.js ou o Deno.] +Os elementos da linguagem((("asynchronous programming", "Curio project", id="APRcurio21")))((("Curio project", id="curio21"))) `async/await` de Python não estão presos a nenhum laço de eventos ou biblioteca específicos.footnote:[Em contraste com o Javascript, onde `async/await` são atrelados ao laço de eventos que é inseparável do ambiente de runtime, isto é, um navegador, o Node.js ou o Deno.] Graças à API extensível fornecida por métodos especiais, qualquer um suficientemente motivado pode escrever seu ambiente de runtime e suo framework assíncronos para acionar corrotinas nativas, geradores assíncronos, etc. Foi isso que David Beazley fez em seu projeto https://fpy.li/21-49[_Curio_]. @@ -1424,7 +1428,7 @@ O <> mostra o script _blogdom.py_ (<>) reescrito p include::../code/21-async/domains/curio/blogdom.py[] ---- ==== -<1> `probe` não precisa obter o loop de eventos, porque... +<1> `probe` não precisa obter o laço de eventos, porque... <2> ...`getaddrinfo` é uma função nível superior de `curio.socket`, não um método de um objeto `loop`—como ele é no `asyncio`. <3> Um `TaskGroup` é um conceito central no _Curio_, para monitorar e controlar várias corrotinas, e para garantir que elas todas sejam executadas e terminadas corretamente. <4> `TaskGroup.spawn` é como você inicia uma corrotina, gerenciada por uma instância específica de `TaskGroup`. A corrotina é envolvida em uma `Task`. @@ -1435,7 +1439,7 @@ Para expandir esse último ponto: se você olhar nos exemplo de código de `asyn [source, python] ---- - loop = asyncio.get_event_loop() + laço = asyncio.get_event_loop() loop.run_until_complete(main()) loop.close() ---- @@ -1458,7 +1462,7 @@ print('Results:', g.results) Grupos((("structured concurrency")))((("concurrency models", "structured concurrency"))) de tarefas (_task groups_) suportam https://fpy.li/21-51[_concorrência estruturada_]: uma forma de programação concorrente que restringe todas a atividade de um grupo de tarefas assíncronas a um único ponto de entrada e saída. -Isso é análogo à programaçào estruturada, que eliminou o comando `GOTO` e introduziu os comandos de bloco para limitar os pontos de entrada e saída de loops e sub-rotinas. +Isso é análogo à programaçào estruturada, que eliminou o comando `GOTO` e introduziu os comandos de bloco para limitar os pontos de entrada e saída de laços e sub-rotinas. Quando usado como um gerenciador de contexto assíncrono, um `TaskGroup` garante que na saída do bloco, todas as tarefas criadas dentro dele estão ou finalizadas ou canceladas e qualquer exceção foi levantada. [NOTE] @@ -1468,11 +1472,11 @@ A https://fpy.li/21-53[seção "Motivation"](EN) menciona as "creches" (_nurseri "Implementar uma API de geração de tarefas melhor no _asyncio_, inspirada pelas _creches_ do Trio, foi a principal motivação dessa PEP." ==== -Outro importante recurso do _Curio_ é um suporte melhor para programar com corrotinas e threads na mesma base de código—uma necessidade de qualquer programa assíncrono não-trivial. +Outro recurso importante do _Curio_ é um suporte melhor para programar com corrotinas e threads na mesma base de código—uma necessidade de qualquer programa assíncrono não-trivial. Iniciar uma thread com `await spawn_thread(func, …)` devolve um objeto `AsyncThread` com uma interface de `Task`. As threads podem chamar corrotinas, graças à função especial https://fpy.li/21-54[`AWAIT(coro)`]—escrita inteiramente com maiúsculas porque `await` agora é uma palavra-chave. -O _Curio_ também oferece uma `UniversalQueue` que pode ser usada para coordenar o trabalho entre threads, corrotinas _Curio_ e corrotinas `asyncio`. -Isso mesmo, o _Curio_ tem recursos que permitem que ele rode em uma thread junto com `asyncio` em outra thread, no mesmo processo, se comunicando através da `UniversalQueue` e de `UniversalEvent`. +O _Curio_ também oferece uma `UniversalQueue` que pode ser usada para coordenar o trabalho entre threads, corrotinas _Curio_ e corrotinas _asyncio_. +Isso mesmo, o _Curio_ tem recursos que permitem que ele rode em uma thread junto com _asyncio_ em outra thread, no mesmo processo, se comunicando através da `UniversalQueue` e de `UniversalEvent`. A API para essas classes "universais" é a mesma dentro e fora de corrotinas, mas em uma corrotina é preciso preceder as chamadas com `await`. Em outubro de 2021, quando estou escrevendo esse capítulo, a _HTTPX_ é a primeira biblioteca HTTP cliente https://fpy.li/21-55[compatível com o _Curio_], @@ -1482,9 +1486,9 @@ No repositório do _Curio_ há um conjunto impressionante de https://fpy.li/21-5 O design do _Curio_ tem tido grande influência. o framework https://fpy.li/21-58[_Trio_], iniciada por Nathaniel J. Smith, foi muito inspirada pelo _Curio_. -O _Curio_ pode também ter alertado os contribuidores de Python a melhorarem a usabilidade da API `asyncio`. -Por exemplo, em suas primeiras versões, os usuários do `asyncio` muitas vezes eram obrigados a obter e ficar passando um objeto `loop`, porque algumas funções essenciais eram ou métodos de `loop` ou exigiam um argumento `loop`. -Em versões mais recentes de Python, acesso direto ao loop não é mais tão necessário e, de fato, várias funções que aceitavam um `loop` opcional estão agora descontinuando aquele argumento. +O _Curio_ pode também ter alertado os contribuidores de Python a melhorarem a usabilidade da API _asyncio_. +Por exemplo, em suas primeiras versões, os usuários do _asyncio_ muitas vezes eram obrigados a obter e ficar passando um objeto `loop`, porque algumas funções essenciais eram ou métodos de `loop` ou exigiam um argumento `loop`. +Em versões mais recentes de Python, acesso direto ao laço não é mais tão necessário e, de fato, várias funções que aceitavam um `loop` opcional estão agora descontinuando aquele argumento. Anotações de tipo para tipos assíncronos é o nosso próximo tópico.((("", startref="APRcurio21")))((("", startref="curio21"))) @@ -1540,7 +1544,7 @@ ____ Se um parâmetro de tipo formal define um tipo para um dado que sai do objeto, ele pode ser covariante. ____ -Segundo: `AsyncGenerator` e `Coroutine` são contra-variantes do segundo ao último parâmetros. Aquele é o tipo do argumento do método de baixo nível `.send()`, que o loop de eventos chama para acionar geradores assíncronos e corrotinas. Dessa forma, é um tipo de "entrada". +Segundo: `AsyncGenerator` e `Coroutine` são contra-variantes do segundo ao último parâmetros. Aquele é o tipo do argumento do método de baixo nível `.send()`, que o laço de eventos chama para acionar geradores assíncronos e corrotinas. Dessa forma, é um tipo de "entrada". Assim, pode ser contra-variante, pelo Regra de Variância #2 [quote] @@ -1619,12 +1623,12 @@ Aprendi da forma mais difícil que não existem "sistemas limitados por E/S." Você pode ter _funções_ limitadas por E/S. Talvez a maioria das funções no seu sistema sejam limitadas por E/S; isto é, elas passam mais tempo esperando por E/S do que realizando operações na memória. -Enquanto esperam, cedem o controle para o loop de eventos, +Enquanto esperam, cedem o controle para o laço de eventos, que pode então acionar outras tarefas pendentes. Mas, inevitavelmente, qualquer sistema não-trivial terá partes limitadas pela CPU. Mesmo sistemas triviais revelam isso, sob stress. No <>, conto a história de dois programas assíncronos sofrendo com -funções limitadas pela CPU freando loop de eventos, +funções limitadas pela CPU freando laço de eventos, com severos impactos no desempenho do sistema como um todo. Dado que qualquer sistema não-trivial terá funções limitadas pela CPU, @@ -1646,7 +1650,7 @@ Aqui estão algumas opções para quando você identifica gargalos de uso da CPU A fila de tarefas externa deveria ser escolhida e integrada o mais rápido possível, no início do projeto, para que ninguém na equipe hesite em usá-la quando necessário. -A opção deixar como está entra na categoria de https://pt.wikipedia.org/wiki/D%C3%ADvida_tecnol%C3%B3gica[dívida tecnológica]. +A opção deixar como está entra na categoria de https://fpy.li/b8[dívida tecnológica]. Programação concorrente é um tópico fascinante, e eu gostaria de escrever mais sobre isso. Mas não é o foco principal deste livro, e este já é um dos capítulos mais longos, então vamos encerrar por aqui. @@ -1661,7 +1665,7 @@ Ou você reescreve todo o código, de forma que nada nele bloqueie [o processame ____ Escolhi((("asynchronous programming", "overview of"))) essa epígrafe para esse capítulo por duas razões. -Em um nível mais alto, ela nos lembra de evitar o bloqueio do loop de eventos, delegando tarefas lentas para uma unidade de processamento diferente, desde uma simples thread indo até uma fila de tarefas distribuída. +Em um nível mais alto, ela nos lembra de evitar o bloqueio do laço de eventos, delegando tarefas lentas para uma unidade de processamento diferente, desde uma simples thread indo até uma fila de tarefas distribuída. Em um nível mais baixo, ela também é um aviso: no momento em que você escreve seu primeiro `async def`, seu programa vai inevitavelmente ver surgir mais e mais `async def`, `await`, `async with`, e `async for`. E o uso de bibliotecas não-assíncronas subitamente se tornará um desafio. @@ -1704,11 +1708,11 @@ https://fpy.li/21-67["What is the core difference between asyncio and trio?" (_Q no StackOverflow. Para aprender mais sobre o pacote _asyncio_, já mencionei os melhores recursos escritos que conheço no início do capítulo: -a https://docs.python.org/pt-br/3/library/asyncio.html[documentação oficial], após a fantástica https://fpy.li/21-69[reorganização] (EN) iniciada por Yury Selivanov em 2018, e o livro de Caleb Hattingh, +a https://fpy.li/b9[documentação oficial], após a fantástica https://fpy.li/21-69[reorganização] (EN) iniciada por Yury Selivanov em 2018, e o livro de Caleb Hattingh, pass:[Using Asyncio in Python] (O'Reilly). -Na documentação oficial, não deixe de ler https://docs.python.org/pt-br/3/library/asyncio-dev.html["Desenvolvendo com asyncio"], que documenta o modo de depuração do _asyncio_ e também discute erros e armadilhas comuns, e como evitá-los. +Na documentação oficial, não deixe de ler https://fpy.li/ba["Desenvolvendo com asyncio"], que documenta o modo de depuração do _asyncio_ e também discute erros e armadilhas comuns, e como evitá-los. Para uma introdução muito acessível, de 30 minutos, à programação assíncrona em geral e também ao _asyncio_, assista a palestra @@ -1753,14 +1757,14 @@ Nada é perfeito, e programação concorrente é sempre difícil. [role="soapbox-title"] Como uma função lerda quase estragou as benchmarks do uvloop -Em((("asynchronous programming", "Soapbox discussion")))((("Soapbox sidebars", "uvloop")))((("uvloop"))) 2016, Yury Selivanov lançou o https://fpy.li/21-83[_uvloop_], "um substituto rápido e direto para o loop de eventos embutido do _asyncio_ event loop." Os números de referência (_benchmarks_) apresentados no https://fpy.li/21-84[post de blog] de Selivanov anunciando a biblioteca, em 2016, eram muito impressionantes. Ele escreveu: "ela é pelo menos 2x mais rápida que o nodejs e gevent, bem como qualquer outro framework assíncrona de Python. O desempenho do asyncio com o uvloop é próximo àquele de programas em Go." +Em((("asynchronous programming", "Soapbox discussion")))((("Soapbox sidebars", "uvloop")))((("uvloop"))) 2016, Yury Selivanov lançou o https://fpy.li/21-83[_uvloop_], "um substituto rápido e direto para o laço de eventos embutido do _asyncio_." Os números de referência (_benchmarks_) apresentados no https://fpy.li/21-84[post de blog] de Selivanov anunciando a biblioteca, em 2016, eram muito impressionantes. Ele escreveu: "ela é pelo menos 2x mais rápida que o nodejs e gevent, bem como qualquer outro framework assíncrona de Python. O desempenho do _asyncio_ com o _uvloop_ é próximo àquele de programas em Go." Entretanto, o post revela que a _uvloop_ é capaz de competir com o desempenho do Go sob duas condições: - . Que o Go seja configurado para usar uma única thread. Isso faz o runtime do Go se comportar de forma similar ao _asyncio_: a concorrência é alcançada através de múltiplas corrotinas acionadas por um loop de eventos, todos na mesma thread.footnote:[Usar uma única thread era o default até o lançamento do Go 1.5. Anos antes, o Go já tinha ganho uma merecida reputação por permitir a criação de sistemas em rede de alta concorrência. Mais uma evidência que a concorrência não exige múltiplas threads ou múltiplos núcleos de CPU.] + . Que o Go seja configurado para usar uma única thread. Isso faz o runtime do Go se comportar de forma similar ao _asyncio_: a concorrência é alcançada através de múltiplas corrotinas acionadas por um laço de eventos, todos na mesma thread.footnote:[Usar uma única thread era o default até o lançamento do Go 1.5. Anos antes, o Go já tinha ganho uma merecida reputação por permitir a criação de sistemas em rede de alta concorrência. Mais uma evidência que a concorrência não exige múltiplas threads ou múltiplos núcleos de CPU.] . Que o código Python 3.5 use a biblioteca https://fpy.li/21-85[_httptools_] além do próprio _uvloop_. -Selivanov explica que escreveu _httptools_ após testar o desempenho da _uvloop_ com a https://fpy.li/21-86[_aiohttp_]—uma das primeiras bibliotecas HTTP completas construídas sobre o `asyncio`: +Selivanov explica que escreveu _httptools_ após testar o desempenho da _uvloop_ com a https://fpy.li/21-86[_aiohttp_]—uma das primeiras bibliotecas HTTP completas construídas sobre o _asyncio_: [quote] ____ @@ -1770,8 +1774,8 @@ ____ Agora reflita sobre isso: os testes de desempenho HTTP de Selivanov consistiam de um simples servidor eco escrito em diferentes linguagens e usando diferentes bibliotecas, testados pela ferramenta de benchmarking https://fpy.li/21-87[_wrk_]. A maioria dos desenvolvedores consideraria um simples servidor eco um "sistema limitado por E/S", certo? Mas no fim, a análise de cabeçalhos HTTP é vinculada à CPU, e tinha uma implementação Python lenta na _aiohttp_ quando Selivanov realizou os testes, em 2016. -Sempre que uma função escrita em Python estava processando os cabeçalhos, o loop de eventos era bloqueado. O impacto foi tão significativo que Selivanov se deu ao trabalho extra de escrever o _httptools_. -Sem a otimização do código de uso intensivo de CPU, os ganhos de desempenho de um loop de eventos mais rápido eram perdidos. +Sempre que uma função escrita em Python estava processando os cabeçalhos, o laço de eventos era bloqueado. O impacto foi tão significativo que Selivanov se deu ao trabalho extra de escrever o _httptools_. +Sem a otimização do código de uso intensivo de CPU, os ganhos de desempenho de um laço de eventos mais rápido eram perdidos. [role="soapbox-title"] @@ -1792,7 +1796,7 @@ O projeto era ambicioso: já estava em desenvolvimento há mais de um ano, mas ainda não estava em produção.footnote:[Independente de escolhas técnicas, esse foi talvez o maior erro daquela projeto: as partes interessadas não forçaram uma abordagem MVP—entregar o "Mínimo Produto Viável" o mais rápido possível e acrescentar novos recursos em um ritmo estável.] Com o tempo, os desenvolvedores notaram que o desempenho do sistema como um todo estava diminuindo, e estavam tendo muita dificuldade em localizar os gargalos. O que estava acontecendo: a cada nova funcionalidade, -mais código intensivo em CPU desacelerava o loop de eventos do _Twisted_. +mais código intensivo em CPU desacelerava o laço de eventos do _Twisted_. O papel de Python como um linguagem de "cola" significava que havia muita interpretação de dados, serialização, desserialização, e muitas conversões entre formatos. Não havia um gargalo único: @@ -1805,7 +1809,7 @@ Os apoiadores internos não estavam preparados para fazer aquele investimento ad e o projeto foi cancelado semanas depois deste diagnóstico. Quando contei essa história para Glyph Lefkowitz—fundador do projeto _Twisted_—ele me disse que uma de suas prioridades, no início de qualquer projeto envolvendo programação assíncrona, -é decidir que ferramentas serão usadas para executar de tarefas intensivas em CPU -sem atrapalhar o loop de eventos. +é decidir que ferramentas serão usadas para executar tarefas intensivas em CPU +sem atrapalhar o laço de eventos. Essa conversa com Glyph foi a inspiração para a <>. **** diff --git a/online/cap22.adoc b/online/cap22.adoc index 6d855a9..c85ced5 100644 --- a/online/cap22.adoc +++ b/online/cap22.adoc @@ -124,7 +124,7 @@ o método `keys` é um atributo do `dict` `__data`. Uma instância de `FrozenJSON` contém um atributo de instância privado `+__data+`, armazenado sob o nome `++_FrozenJSON__data++`, como explicado na <>. Tentativas de recuperar atributos por outros nomes vão disparar `+__getattr__+`. -Esse método irá primeiro olhar se o `dict` `+self.__data+` contém um atributo (não uma chave!) com aquele nome; isso permite que instâncias de `FrozenJSON` tratem métodos de `dict` tal como `items`, delegando para `+self.__data.items()+`. Se `+self.__data+` não contiver uma atributo como o `name` dado, `+__getattr__+` usa `name` como chave para recuperar um item de `+self.__data+`, e passa aquele item para `FrozenJSON.build`. Isso permite navegar por estruturas aninhadas nos dados JSON, já que cada mapeamento aninhado é convertido para outra instância de `FrozenJSON` pelo método de classe `build`. +Primeiro, esse método verá se o `dict` `+self.__data+` contém um atributo (não uma chave!) com aquele nome; isso permite que instâncias de `FrozenJSON` tratem métodos de `dict` tal como `items`, delegando para `+self.__data.items()+`. Se `+self.__data+` não contiver uma atributo como o `name` dado, `+__getattr__+` usa `name` como chave para recuperar um item de `+self.__data+`, e passa aquele item para `FrozenJSON.build`. Isso permite navegar por estruturas aninhadas nos dados JSON, já que cada mapeamento aninhado é convertido para outra instância de `FrozenJSON` pelo método de classe `build`. Observe que `FrozenJSON` não transforma ou armazena o conjunto de dados original. Conforme navegamos pelos dados, `+__getattr__+` cria continuamente instâncias de `FrozenJSON`. @@ -253,7 +253,9 @@ Nossa próxima tarefa é reestruturar os dados para preparar a recuperação aut [[computed_props_sec]] === Propriedades computadas -Vimos inicialmente o decorador `@property` no <>, na <>. No <>, usei duas propriedades no `Vector2d` apenas para tornar os atributos `x` e `y` apenas para leitura. +Vimos inicialmente o decorador `@property` na <>. +No <> do <>, +usei duas propriedades no `Vector2d` apenas para tornar os atributos `x` e `y` apenas para leitura. Aqui vamos ver propriedades que calculam valores, levando a uma discussão sobre como armazenar tais valores. Os((("computed properties", "properties that compute values")))((("dynamic attributes and properties", "computed properties", id="DAPcomputp22"))) registros na lista `'events'` dos dados JSON da OSCON contêm números de série inteiros apontando para registros nas listas `'speakers'` e `'venues'`. @@ -333,10 +335,10 @@ A definição de `Record` no <> é tão simples que você pode e [NOTE] ==== A biblioteca padrão de Python oferece classes similares a `Record`, onde cada instância tem um conjunto arbitrário de atributos criados a partir de argumentos nomeados passados a `+__init__+`: -https://docs.python.org/pt-br/3/library/types.html#types.SimpleNamespace[`types.SimpleNamespace`], -https://docs.python.org/pt-br/3/library/argparse.html#argparse.Namespace[`argparse.Namespace`] (EN), +https://fpy.li/bd[`types.SimpleNamespace`], +https://fpy.li/be[`argparse.Namespace`] (EN), and -https://docs.python.org/pt-br/3/library/multiprocessing.html#multiprocessing.managers.Namespace[`multiprocessing.managers.Namespace`] (EN). +https://fpy.li/bf[`multiprocessing.managers.Namespace`] (EN). Escrevi a classe `Record`, mais simples, para destacar a ideia essencial: `+__init__+` atualizando o `+__dict__+` da instância. ==== @@ -413,7 +415,7 @@ A segunda linha do método `venue` no <> devolve pass:[self​.__class__.fetch(key)]. Por que não podemos simplesmente invocar `self.fetch(key)`? A forma simples funciona com esse conjunto específico de dados da OSCON porque não há registro de evento com uma chave `'fetch'`. -Mas, se um registro de evento possuísse uma chave chamada `'fetch'`, então dentro daquela instância específica de `Event`, a referência `self.fetch` apontaria para o valor daquele campo, em vez do método de classe `fetch` que `Event` herda de `Record`. +Mas, se um registro de evento tivesse uma chave chamada `'fetch'`, então dentro daquela instância específica de `Event`, a referência `self.fetch` apontaria para o valor daquele campo, em vez do método de classe `fetch` que `Event` herda de `Record`. Esse é um bug sutil, e poderia facilmente escapar aos testes, pois depende do conjunto de dados. @@ -441,7 +443,7 @@ include::../code/22-dyn-attr-prop/oscon/schedule_v2.py[tags=SCHEDULE2_LOAD] <4> Se o objeto recém-obtido é uma classe, e é uma subclasse de `Record`... <5> ...vincula o nome `factory` a ele. Isso significa que `factory` pode ser qualquer subclasse de `Record`, dependendo do `record_type`. <6> Caso contrário, vincula o nome `factory` a `Record`. -<7> O loop `for`, que cria a `key` e armazena os registros, é o mesmo de antes, exceto que... +<7> O laço `for`, que cria a `key` e armazena os registros, é o mesmo de antes, exceto que... <8> ...o objeto armazenado em `records` é construído por `factory`, e pode ser `Record` ou uma subclasse, como `Event`, selecionada de acordo com o `record_type`. Observe que o único `record_type` que tem uma classe customizada é `Event`, mas se classes chamadas `Speaker` ou `Venue` existirem, `load` vai automaticamente usar aquelas classes ao criar e armazenar registros, em vez da classe default `Record`. @@ -470,16 +472,23 @@ include::../code/22-dyn-attr-prop/oscon/schedule_v3.py[tags=SCHEDULE3_SPEAKERS] do `+__dict__+` da instância, para evitar uma chamada recursiva à propriedade `speakers`. <2> Devolve uma lista de todos os registros com chaves correspondendo aos números em `spkr_serials`. -Dentro do método `speakers`, tentar ler `self.speakers` irá invocar a própria propriedade, gerando rapidamente um `RecursionError`. -Entretanto, se lemos os mesmos dados via `+self.__dict__['speakers']+`, o algoritmo normal de Python para busca e recuperação de atributos é ignorado, a propriedade não é chamada e a recursão é evitada. -Por essa razão, ler ou escrever dados diretamente no `+__dict__+` de um objeto é um truque comum em metaprogramação no Python. +Dentro do método `speakers`, uma tentativa de ler `self.speakers` invocará a +mesma propriedade, gerando rapidamente um `RecursionError`. Entretanto, +acessando via `+self.__dict__['speakers']+`, evitamos o algoritmo de Python para +busca de atributos, a propriedade não é chamada e evitamos a recursão. Por esta +razão, ler ou escrever dados diretamente no `+__dict__+` de um objeto é um +truque comum em metaprogramação no Python. [WARNING] ==== -O interpretador avalia `+obj.my_attr+` olhando primeiro a classe de `obj`. -Se a classe possuir uma propriedade de nome `my_attr`, aquela propriedade oculta um atributo de instância com o mesmo nome. -Isso será demonstrado por exemplos na <>, -e o <> vai revelar que uma propriedade é implementada como um descritor—uma abstração mais geral e poderosa. + +O interpretador avalia `+obj.my_attr+` olhando primeiro a classe de `obj`. Se +existe uma propriedade com o nome `my_attr`, aquela propriedade oculta um +atributo de instância com o mesmo nome. Isso será demonstrado com exemplos na +<>, e o <> revelará que uma +propriedade é implementada como um descritor (_descriptor_), +uma abstração mais geral e poderosa. + ==== Quando programava a compreensão de lista no <>, meu cérebro réptil de programador pensou: "Isso talvez seja custoso". Na verdade não é, porque os eventos no conjuntos de dados da OSCON contêm poucos palestrantes, então programar algo mais complexo seria uma otimização prematura. @@ -562,7 +571,7 @@ O decorador `property` é uma API de alto nível para criar um _descritor domina O <> inclui um explicação completa sobre descritores _dominantes_ e _não dominantes_. Por hora, vamos deixar de lado a implementação subjacente e nos concentrar nas diferenças entre `cached_property` e `property` do ponto de vista de um usuário. -Raymond Hettinger os explica muito bem na https://docs.python.org/pt-br/3/library/functools.html#functools.cached_property[Documentação de Python]: +Raymond Hettinger os explica muito bem na https://fpy.li/bg[Documentação de Python]: [quote] ____ @@ -571,7 +580,7 @@ Uma propriedade regular bloqueia a escrita em atributos, a menos que um _setter_ O decorador `cached_property` só funciona para consultas e apenas quando um atributo de mesmo nome não existe. Quando funciona, `cached_property` escreve no atributo de mesmo nome. Leituras e escritas subsequentes do/no atributo tem precedência sobre o método decorado com `cached_property` e ele funciona como um atributo normal. -O valor em cache pode ser excluído apagando-se o atributo. Isso permite que o método `cached_property` rode novamente.footnote:[Fonte: documentação de https://docs.python.org/pt-br/3/library/functools.html#functools.cached_property[@functools.cached_property]. Sei que autor dessa explicação é Raymond Hettinger porque ele a escreveu em resposta a um problema que eu mesmo reportei: https://fpy.li/22-11[bpo42781—functools.cached_property docs should explain that it is non-overriding (_a documentação de functools.cached_property deveria explicar que ele é não-dominante_)] (EN). Hettinger é um grande colaborador da documentação oficial de Python e da biblioteca padrão. Ele também escreveu o excelente https://fpy.li/22-12[Descriptor HowTo Guide (_Guia de Utilização de Descritores_)] (EN), um recurso fundamental para o <>.] +O valor em cache pode ser excluído apagando-se o atributo. Isso permite que o método `cached_property` rode novamente.footnote:[Fonte: documentação de https://fpy.li/bg[@functools.cached_property]. Sei que autor dessa explicação é Raymond Hettinger porque ele a escreveu em resposta a um problema que eu mesmo reportei: https://fpy.li/22-11[bpo42781—functools.cached_property docs should explain that it is non-overriding (_a documentação de functools.cached_property deveria explicar que ele é não-dominante_)] (EN). Hettinger é um grande colaborador da documentação oficial de Python e da biblioteca padrão. Ele também escreveu o excelente https://fpy.li/22-12[Descriptor HowTo Guide (_Guia de Utilização de Descritores_)] (EN), um recurso fundamental para o <>.] ____ Voltando((("@cached_property"))) à nossa classe `Event`: o comportamento específico de `@cached_property` o torna inadequado para decorar `speakers`, porque aquele método depende de um atributo existente também chamado `speakers`, contendo os números de série dos palestrantes do evento. @@ -586,12 +595,15 @@ se o método decorado já depender de um atributo de instância de mesmo nome. * Ele impede a otimização de compartilhamento de chaves do `+__dict__+` da instância, pois cria um atributo de instância após o `+__init__+`. ==== -Apesar dessas limitações, `@cached_property` supre uma necessidade comum de uma maneira simples, e é seguro para usar com threads. -Seu https://fpy.li/22-13[código Python] é um exemplo do uso de uma https://docs.python.org/pt-br/3/library/threading.html#rlock-objects[_trava recursiva_ (_reentrant lock_)]. +Apesar dessas limitações, `@cached_property` supre uma necessidade comum de um modo simples, +e é seguro para usar com threads. +Seu https://fpy.li/22-13[código Python] é um exemplo do uso de uma +https://fpy.li/bp[_reentrant lock_] (trava reentrante ou trava recursiva).footnote:[Veja +https://fpy.li/bq[«Mutex recursivo»] na Wikipédia.] A -https://docs.python.org/pt-br/3.10/library/functools.html#functools.cached_property[documentação] de `@cached_property` +https://fpy.li/bh[documentação] de `@cached_property` recomenda uma solução altenativa que podemos usar com `speakers`: Empilhar decoradores `@property` e `@cache`, como exibido no <>. @@ -1042,7 +1054,7 @@ Essas cinco funções embutidas executam leitura, escrita e introspecção de at `dir([object])`:: Lista((("dir([object]) function")))((("functions", "dir([object]) function"))) a maioria dos atributos de um objeto. A -https://docs.python.org/pt-br/3/library/functions.html#dir[documentação oficial] +https://fpy.li/bj[documentação oficial] diz que o objetivo de `dir` é o uso interativo, então ele não fornece uma lista completa de atributos, mas um conjunto de nomes "interessantes". `dir` pode inspecionar objetos implementados com ou sem um `+__dict__+`. O próprio atributo `+__dict__+` não é exibido por `dir`, mas as chaves de `+__dict__+` são listadas. @@ -1054,7 +1066,7 @@ Vários atributos especiais de classes, como Um ótimo exemplo de uso de `gettatr` aparece no https://fpy.li/22-19[método `Cmd.onecmd`], no pacote `cmd` da biblioteca padrão, onde ela é usada para obter e executar um comando definido pelo usuário. -`hasattr(object, name)`:: Devolve `True` se o atributo nomeado existir em `object`, ou puder ser obtido de alguma forma através dele (por herança, por exemplo). A https://docs.python.org/pt-br/3/library/functions.html#hasattr[documentação] explica: "Isto é implementado chamando getattr(object, name) e vendo se [isso] levanta um AttributeError ou não." +`hasattr(object, name)`:: Devolve `True` se o atributo nomeado existir em `object`, ou puder ser obtido de alguma forma através dele (por herança, por exemplo). A https://fpy.li/bk[documentação] explica: "Isto é implementado chamando getattr(object, name) e vendo se [isso] levanta um AttributeError ou não." `setattr(object, name, value)`:: Atribui((("functions", "setattr(object, name, value) function"))) o `value` ao atributo de `object` nomeado, se o `object` permitir essa operação. Isso pode criar um novo atributo ou sobrescrever um atributo existente. @@ -1068,7 +1080,7 @@ Quando implementados em uma classe definida pelo usuário, os métodos especiais Acessos((("getattr function")))((("setattr function")))((("functions", "hasattr function")))((("hasattr function")))((("functions", "getattr function")))((("functions", "setattr funcion"))) a atributos, usando tanto a notação de ponto ou as funções embutidas `getattr`, `hasattr` e `setattr` disparam os métodos especiais adequados, listados aqui. A leitura e escrita direta de atributos no `+__dict__+` da instância não dispara esses métodos especiais--e essa é a forma habitual de evitá-los se isso for necessário. -A seção https://docs.python.org/pt-br/3.10/reference/datamodel.html#special-method-lookup["3.3.11. Pesquisa de método especial"] do capítulo "Modelo de dados" adverte: +A seção https://fpy.li/bm["3.3.11. Pesquisa de método especial"] do capítulo "Modelo de dados" adverte: [quote] ____ @@ -1120,11 +1132,17 @@ Por fim, demos uma rápida passada pelo tratamento da exclusão de atributos com === Leitura Complementar -A((("dynamic attributes and properties", "further reading on"))) documentação oficial para as funções embutidas de tratamento de atributos e introspecção é o https://docs.python.org/pt-br/3/library/functions.html[Capítulo 2, "Funções embutidas"] da _Biblioteca Padrão de Python_. Os métodos especiais relacionados e o atributo especial `+__slots__+` estão documentados em _A Referência da Linguagem Python_, em https://docs.python.org/pt-br/3/reference/datamodel.html#customizing-attribute-access["3.3.2. Personalizando o acesso aos atributos"]. A semântica de como métodos especiais são invocados ignorando as instâncias está explicada em https://docs.python.org/pt-br/3/reference/datamodel.html#special-method-lookup["3.3.11. Pesquisa de método especial"]. No capítulo 4 da _Biblioteca Padrão de Python_, "Tipos embutidos", https://docs.python.org/pt-br/3/library/stdtypes.html#special-attributes["Atributos especiais"] trata dos atributos `+__class__+` e `+__dict__+`. +A((("dynamic attributes and properties", "further reading on"))) documentação oficial para as funções embutidas de tratamento de atributos e introspecção é o https://fpy.li/bn[Capítulo 2, "Funções embutidas"] da _Biblioteca Padrão de Python_. Os métodos especiais relacionados e o atributo especial `+__slots__+` estão documentados em _A Referência da Linguagem Python_, em +https://fpy.li/br["3.3.2. Personalizando o acesso aos atributos"]. +A semântica de como métodos especiais são invocados ignorando as instâncias está explicada em +https://fpy.li/bs["3.3.11. Pesquisa de método especial"]. +No capítulo 4 da _Biblioteca Padrão de Python_, "Tipos embutidos", +https://fpy.li/bt["Atributos especiais"] +trata dos atributos `+__class__+` e `+__dict__+`. -O pass:[Python Cookbook] (EN), 3ª ed., de David Beazley e Brian K. Jones (O’Reilly), tem várias receitas relacionadas aos tópicos deste capítulo, mas eu destacaria três mais marcantes: A "Recipe 8.8. Extending a Property in a Subclass" (_Receita 8.8. Estendendo uma Propriedade em uma Subclasse_) trata da espinhosa questão de sobrescrever métodos dentro de uma propriedade herdada de uma superclasse; a "Recipe 8.15. Delegating Attribute Access" (_Receita 8.15. Delegando o Acesso a Atributos_) implementa uma classe proxy, demonstrando a maioria dos métodos especiais da <> deste livro; e a fantástica "Recipe 9.21. Avoiding Repetitive Property Methods" (_Receita 9.21. Evitando Métodos de Propriedade Repetitivos_), que foi a base da função fábrica de propriedades apresentada no <>. +O https://fpy.li/pycook3[Python Cookbook], 3ª ed., de David Beazley e Brian K. Jones (O’Reilly), tem várias receitas relacionadas aos tópicos deste capítulo, mas eu destacaria três mais marcantes: A "Recipe 8.8. Extending a Property in a Subclass" (_Receita 8.8. Estendendo uma Propriedade em uma Subclasse_) trata da espinhosa questão de sobrescrever métodos dentro de uma propriedade herdada de uma superclasse; a "Recipe 8.15. Delegating Attribute Access" (_Receita 8.15. Delegando o Acesso a Atributos_) implementa uma classe proxy, demonstrando a maioria dos métodos especiais da <> deste livro; e a fantástica "Recipe 9.21. Avoiding Repetitive Property Methods" (_Receita 9.21. Evitando Métodos de Propriedade Repetitivos_), que foi a base da função fábrica de propriedades apresentada no <>. -O pass:[Python in a Nutshell,] 3ª ed., de Alex Martelli, Anna Ravenscroft e Steve Holden (O'Reilly), é rigoroso e objetivo. Eles dedicam apenas três páginas a propriedades, mas isso se dá porque o livro segue um estilo de apresentação axiomático: as 15 ou 16 páginas precedentes fornecem uma descrição minuciosa da semântica das classes de Python, a partir do zero, incluindo descritores, que são como as propriedades são efetivamente implementadas debaixo dos panos. Assim, quando Martelli et al. chegam à propriedades, eles concentram várias ideias profundas naquelas três páginas—incluindo o trecho que selecionei para abrir este capítulo. +O https://fpy.li/pynut3[Python in a Nutshell], 3ª ed., de Alex Martelli, Anna Ravenscroft e Steve Holden (O'Reilly), é rigoroso e objetivo. Eles dedicam apenas três páginas a propriedades, mas isso se dá porque o livro segue um estilo de apresentação axiomático: as 15 ou 16 páginas precedentes fornecem uma descrição minuciosa da semântica das classes de Python, a partir do zero, incluindo descritores, que são como as propriedades são efetivamente implementadas debaixo dos panos. Assim, quando Martelli et al. chegam à propriedades, eles concentram várias ideias profundas naquelas três páginas—incluindo o trecho que selecionei para abrir este capítulo. Bertrand Meyer—citado na definição do Princípio do Acesso Uniforme no início do capítulo—foi um pioneiro da metodologia Programação por Contrato (_Design by Contract_), projetou a linguagem Eiffel e escreveu o excelente _Object-Oriented Software Construction_, 2ª ed. (Pearson). Os primeiros seis capítulos fornecem uma das melhores introduções conceituais à análise e design orientados a objetos que tenho notícia. O capítulo 11 apresenta a Programação por Contrato, e o capítulo 35 traz as avaliações de Meyer de algumas das mais influentes linguagens orientadas a objetos: Simula, Smalltalk, CLOS (the Common Lisp Object System), Objective-C, {cpp}, e Java, com comentários curtos sobre algumas outras. Apenas na última página do livro o autor revela que a "notação" extremamente legível usada como pseudo-código no livro é Eiffel. diff --git a/online/cap23.adoc b/online/cap23.adoc index 3ca5550..475abf6 100644 --- a/online/cap23.adoc +++ b/online/cap23.adoc @@ -5,7 +5,7 @@ [quote, Raymond Hettinger, guru de Python e um de seus mantenedores] ____ -Aprender sobre descritores não apenas dá acesso a um conjunto maior de ferramentas, cria também uma maior compreensão sobre o funcionamento de Python e uma apreciação pela elegância de seu design.footnote:[Raymond Hettinger, https://docs.python.org/pt-br/3/howto/descriptor.html["HowTo - Guia de descritores"].] +Aprender sobre descritores não apenas dá acesso a um conjunto maior de ferramentas, cria também uma maior compreensão sobre o funcionamento de Python e uma apreciação pela elegância de seu design.footnote:[Raymond Hettinger, https://fpy.li/bv["HowTo - Guia de descritores"].] ____ Descritores((("descriptors")))((("attribute descriptors", "purpose of"))) são uma forma de reutilizar a mesma lógica de acesso em múltiplos atributos. Por exemplo, tipos de campos em ORMs ("Object Relational Mapping" - _Mapeamento Objeto-Relacional_), como o ORM do Django e o SQLAlchemy, são descritores, gerenciando o fluxo de dados dos campos em um registro de banco de dados para atributos de objetos de Python, e vice-versa. @@ -462,8 +462,12 @@ Vamos ver agora como descritores são usados para implementar métodos no Python [[methods_are_descriptors_sec]] === Métodos são descritores -Uma((("attribute descriptors", "methods as descriptors", id="ADmethodsas23"))) função dentro de uma classe se torna um método vinculado quando invocada em uma instância, porque todas as funções definidas pelo usuário possuem um método `+__get__+`, e portanto operam como descritores quando associados a uma classe. -O <> demonstra a leitura do método `spam`, da classe `Managed`, apresentada no <>. +Uma((("attribute descriptors", "methods as descriptors", id="ADmethodsas23"))) +função dentro de uma classe se torna um método vinculado quando invocada em uma +instância, porque todas as funções definidas pelo usuário têm um método +`+__get__+`, e portanto operam como descritores quando associados a uma classe. +O <> demonstra a leitura do método `spam`, da classe +`Managed`, apresentada no <>. [[descriptorkinds_demo5]] @@ -541,7 +545,7 @@ O decorador `@functools.cached_property` na verdade produz um descritor não dom Métodos não especiais pode ser ocultados por atributos de instância:: Como funções e métodos implementam apenas `+__get__+`, eles são descritores não dominantes. Uma atribuição simples, como `my_obj.the_method = 7`, significa que acessos posteriores a `the_method` através daquela instância irão obter o número ++7++—sem afetar a classe ou outras instâncias. Essa questão, entretanto, não interfere com os métodos especiais. O interpretador só procura métodos especiais na própria classe. Em outras palavras, `repr(x)` é executado como `+x.__class__.__repr__(x)+`, então um atributo `+__repr__+`, definido em `x`, não tem qualquer efeito em `repr(x)`. Pela mesma razão, a existência de um atributo chamado `+__getattr__+` em uma instância não vai subverter o algoritmo normal de acesso a atributos. -O fato de métodos não especiais poderem ser sobrepostos tão facilmente pode soar frágil e propenso a erros. Mas eu, pessoalmente, em mais de 20 anos programando em Python, nunca tive problemas com isso. Por outro lado, se você estiver criando muitos atributos dinâmicos, onde os nomes dos atributos vêm de dados que você não controla (como fizemos na parte inicial desse capítulo), então você precisa estar atenta para isso, e talvez implementar alguma filtragem ou reescrita (_escaping_) dos nomes dos atributos dinâmicos, para preservar sua sanidade. +O fato de métodos não especiais poderem ser sobrescritos tão facilmente pode soar frágil e propenso a erros. Mas eu, pessoalmente, em mais de 20 anos programando em Python, nunca tive problemas com isso. Por outro lado, se você estiver criando muitos atributos dinâmicos, onde os nomes dos atributos vêm de dados que você não controla (como fizemos na parte inicial desse capítulo), então você precisa estar atenta para isso, e talvez implementar alguma filtragem ou reescrita (_escaping_) dos nomes dos atributos dinâmicos, para preservar sua sanidade. [role="man-height"] [NOTE] @@ -593,7 +597,10 @@ Como observado na <>, vários exemplos deste capítulo === Para saber mais -Além((("attribute descriptors", "further reading on"))) da referência obrigatória ao capítulo https://docs.python.org/pt-br/3/reference/datamodel.html["Modelo de dados"], o https://docs.python.org/pt-br/3/howto/descriptor.html["HowTo - Guia de descritores"], de Raymond Hettinger, é um recurso valioso--e parte da https://docs.python.org/pt-br/3/howto/[coleção de HOWTOS] na documentação oficial de Python. +Além((("attribute descriptors", "further reading on"))) da referência obrigatória ao capítulo +https://fpy.li/2j["Modelo de dados"], o https://fpy.li/bv["HowTo - Guia de descritores"], +de Raymond Hettinger, é um recurso valioso--e parte da excelente +https://fpy.li/bw[coleção de HOWTOS] na documentação oficial de Python. Como sempre, em se tratando de assuntos relativos ao modelo de objetos de Python, o _Python in a Nutshell_, 3ª ed. (O'Reilly), de Martelli, Ravenscroft, e Holden é competente e objetivo. Martelli também tem uma apresentação chamada "Python's Object Model" (_O Modelo de Objetos de Python_), tratando com profundidade de propriedades e descritores (veja os https://fpy.li/23-5[slides] (EN) e o https://fpy.li/23-6[video] (EN)). diff --git a/online/cap24.adoc b/online/cap24.adoc index 592cc46..656350b 100644 --- a/online/cap24.adoc +++ b/online/cap24.adoc @@ -49,11 +49,11 @@ Isso se aplica tanto a código em produção quando a livros. Vamos começar revisando os atributos e métodos definidos no Modelo de Dados de Python para todas as classes. -[[anatomy_of_classes]] +[[anatomy_of_classes_sec]] === Classes como objetos Como((("class metaprogramming", "classes as objects"))) acontece com a maioria das entidades programáticas de Python, classes também são objetos. -Toda classe tem alguns atributos definidos no Modelo de Dados de Python, documentados na seção https://docs.python.org/pt-br/3/library/stdtypes.html#special-attributes["4.13. Atributos Especiais"] do capítulo "Tipos Embutidos" da _Biblioteca Padrão de Python_. +Toda classe tem alguns atributos definidos no Modelo de Dados de Python, documentados na seção https://fpy.li/bt["4.13. Atributos Especiais"] do capítulo "Tipos Embutidos" da _Biblioteca Padrão de Python_. Três((("__class__")))((("__name__")))((("__mro__"))) destes atributos já apareceram várias vezes no livro: `+__class__+`, `+__name__+`, and `+__mro__+`. Outros atributos de classe padrão são: @@ -225,15 +225,14 @@ Poderíamos ter dado qualquer outro nome ao atributo de classe `+__slots__+`, ma [WARNING] ==== -Instâncias de classes criadas por `record_factory` não são serializáveis--isto é, elas não podem ser exportadas com a função `dump` do módulo `pickle`. Resolver este problema está além do escopo desse exemplo, que tem por objetivo mostrar a classe `type` funcionando em um caso de uso simples. Para uma solução completa, estude o código-fonte de pass:[collections.namedtuple]; procure pela palavra "pickling". +Instâncias de classes criadas por `record_factory` não são serializáveis--isto é, elas não podem ser exportadas com a função `dump` do módulo `pickle`. Resolver este problema está além do escopo deste exemplo, que tem por objetivo mostrar a classe `type` funcionando em um caso de uso simples. Para uma solução completa, estude o código-fonte de `collections.namedtuple`; procure pela palavra "pickling". ==== -Vamos ver agora como emular fábricas de classes mais modernas, como `typing.NamedTuple`, que recebe uma classe definida pelo usuário, escrita com o comando `class`, e a melhora automaticamente, acrescentando funcionalidade.((("", startref="CMfacfun24"))) +Vamos ver agora como emular fábricas de classes mais modernas, como `typing.NamedTuple`, que recebe uma classe definida pelo usuário, escrita com a instrução `class`, e a melhora automaticamente, acrescentando funcionalidade.((("", startref="CMfacfun24"))) [[enhancing_with_init_subclass_sec]] -=== Apresentando pass:[__init_subclass__] +=== Apresentando `+__init_subclass__+` Tanto((("class metaprogramming", "__init_subclass__", id="CMinitsub24", secondary-sortas="init")))((("__init_subclass__", id="initsub24"))) `+__init_subclass__+` quanto `+__set_name__+` foram propostos na https://fpy.li/pep487[PEP 487—Simpler customization of class creation (_PEP 487—Uma customização mais simples da criação de classes_)]. @@ -262,7 +261,7 @@ O <> mostra como usar `Checked` para criar uma classe `Movie`. include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=MOVIE_DEFINITION] ---- ==== -<1> `Movie` herda de `Checked`—que definiremos mais tarde, no <>. +<1> `Movie` herda de `Checked`—que definiremos mais tarde, no <>. <2> Cada atributo é anotado com um construtor. Aqui usei tipos embutidos. <3> Instâncias de `Movie` devem ser criadas usando argumentos nomeados. <4> Em troca, temos um `+__repr__+` agradável. @@ -352,13 +351,13 @@ E assim eu aqui declaro que, nesse exemplo simples, este um recurso, não um bug Na <>, vimos o conveniente método especial `+__set_name__+` para descritores. Não precisamos disso na classe `Field`, porque os descritores não são instanciados no código-fonte cliente; o usuário declara tipos que são construtores, como visto na classe `Movie` (no <>). Em vez disso, as instâncias do descritor `Field` são criadas durante a execução, pelo método -`+Checked.__init_subclass__+`, que veremos no <>. +`+Checked.__init_subclass__+`, que veremos no <>. ==== -Vamos agora nos concentrar na classe `Checked`, que dividi em duas listagens. O <> mostra a parte inicial da classe, incluindo os métodos mais importantes para esse exemplo. +Vamos agora nos concentrar na classe `Checked`, que dividi em duas listagens. O <> mostra a parte inicial da classe, incluindo os métodos mais importantes para esse exemplo. O restante dos métodos está no <>. -[[checked_class_top_ex]] +[[ex_checked_class_top]] .initsub/checkedlib.py: os métodos mais importante da classe `Checked` ==== [source, python] @@ -368,7 +367,7 @@ include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=CHECKED_TO ==== <1> Escrevi este método de classe para ocultar a chamada a `typing.get_type_hints` do resto da classe. Se precisasse suportar apenas versões de Python ≥ 3.10, invocaria `inspect.get_annotations` em vez disso. Reveja a <> para uma discussão dos problemas com essas funções. <2> `+__init_subclass__+` é chamado quando uma subclasse da classe atual é definida. Ele recebe aquela nova subclasse como seu primeiro argumento—e por isso nomeei o argumento `subclass` em vez do habitual `cls`. Para mais informações sobre isso, veja <>. -<3> `+super().__init_subclass__()+` não é estritamente necessário, mas deve ser invocado para ajudar outras classes que implementem `+.__init_subclass__()+` na mesma árvore de herança. Veja a <>. +<3> `+super().__init_subclass__()+` não é estritamente necessário, mas deve ser invocado para ajudar outras classes que implementem `+.__init_subclass__()+` na mesma árvore de herança. Veja a <>. <4> Itera sobre `name` e `constructor` em cada campo... <5> ...criando um atributo em `subclass` com aquele `name` vinculado a um descritor `Field`, parametrizado com `name` e `constructor`. <6> Para cada `name` nos campos da classe... @@ -386,11 +385,11 @@ O primeiro argumento que Python passa para `+__init_subclass__+` é uma classe. Entretanto, essa nunca é a classe onde `+__init_subclass__+` é implementado, mas sim uma subclasse recém-definida daquela classe. Isso é diferente de `+__new__+` e de qualquer outro método de classe que eu conheço. Assim, acho que `+__init_subclass__+` não é um método de classe no sentido usual, e é errado nomear seu primeiro argumento `cls`. A -pass:[documentação de __init_suclass__] chama o argumento de `cls`, mas explica: "...chamado sempre que se cria uma subclasse da classe que o contém. `cls` é então a nova subclasse..."footnote:[NT: Em 17 de setembro de 2023, a primeira frase está traduzida de forma confusa na documentação em português. Optamos por traduzir aqui diretamente da documentação em inglês.]. +https://fpy.li/c2[documentação de `__init_suclass__`] chama o argumento de `cls`, mas explica: "...chamado sempre que se cria uma subclasse da classe que o contém. `cls` é então a nova subclasse..."footnote:[NT: Em 17 de setembro de 2023, a primeira frase está traduzida de forma confusa na documentação em português. Optamos por traduzir aqui diretamente da documentação em inglês.]. **** Vamos examinar os métodos restantes da classe `Checked`, -continuando do <>. +continuando do <>. Observe que prefixei os nomes dos métodos `+_fields+` e `+_asdict+` com `+_+`, pela mesma razão pela qual isso é feito na API de `collections.namedtuple`: reduzir a possibilidade de colisões de nomes com nomes de campos definidos pelo usuário. [[checked_class_bottom_ex]] @@ -476,7 +475,7 @@ include::../code/24-class-metaprog/checked/decorator/checkeddeco.py[tags=CHECKED ==== <1> Lembre-se que classes são instâncias de `type`. Essas dicas de tipo sugerem fortemente que este é um decorador de classes: ele recebe uma classe e devolve uma classe. <2> `+_fields+` agora é uma função de alto nível definida mais tarde no módulo (no <>). -<3> Substituir cada atributo devolvido por `+_fields+` por uma instância do descritor `Field` é o que `+__init_subclass__+` fazia no <>. Aqui há mais trabalho a ser feito... +<3> Substituir cada atributo devolvido por `+_fields+` por uma instância do descritor `Field` é o que `+__init_subclass__+` fazia no <>. Aqui há mais trabalho a ser feito... <4> Cria um método de classe a partir de `+_fields+`, e o adiciona à classe decorada. O comentário `type: ignore` é necessário, porque o Mypy reclama que `type` não tem um atributo `_fields`. <5> Funções ao nível do módulo, que se tornarão métodos de instância da classe decorada. <6> Adiciona cada um dos `instance_methods` a `cls`. @@ -491,7 +490,7 @@ Essa convenção para a nomenclatura faz sentido por duas razões: O restante de _checkeddeco.py_ está listado no <>. Aquelas funções no nível do módulo contém o mesmo código dos métodos correspondentes na classe `Checked` de _checkedlib.py_. -Elas foram explicadas no <> e no <>. +Elas foram explicadas no <> e no <>. Observe que a função `+_fields+` exerce dois papéis em _checkeddeco.py_. Ela é usada como uma função regular na primeira linha do decorador `checked` e será também injetada como um método de classe na classe decorada. @@ -645,8 +644,8 @@ Se você abrir o console novamente e importar _evaldemo.py_, a saída aparece no A implementação de `+type.__new__+` está escrita em C. O comportamento que acabei de descrever está documentado na seção -https://docs.python.org/pt-br/3/reference/datamodel.html#creating-the-class-object["Criando o objeto classe"], no capítulo -https://docs.python.org/pt-br/3/reference/datamodel.html["Modelo de Dados"] da referência de Python. +https://fpy.li/bx["Criando o objeto classe"], no capítulo +https://fpy.li/2j["Modelo de Dados"] da referência de Python. Observe que a função `main()` de _evaldemo.py_ (no <>) não foi executada durante a sessão no console (no <>), portanto nenhuma instância de `Klass` foi criada. Todas as ações que vimos foram acionadas por operações de "importação": @@ -1121,7 +1120,7 @@ include::../code/24-class-metaprog/checked/metaclass/checkedlib.py[tags=CHECKED_ A última parte de _metaclass/checkedlib.py_ é a classe base `Checked`, a partir da qual os usuários dessa biblioteca criarão subclasses para melhorar suas classes, como `Movie`. O código desta versão de `Checked` é o mesmo da `Checked` em _initsub/checkedlib.py_ -(listada no <> e no <>), com três modificações: +(listada no <> e no <>), com três modificações: . O acréscimo de um `+__slots__+` vazio, para sinalizar a `+CheckedMeta.__new__+` que esta classe não precisa de processamento especial. . A remoção de `+__init_subclass__+`, cujo trabalho agora é feito por `+CheckedMeta.__new__+`. @@ -1319,11 +1318,14 @@ Metaclasses, bem((("class metaprogramming", "useful applications of metaclasses" - Mapeamento objeto-relacional - Persistência baseada em objetos - Implementar métodos especiais a nível de classe -- Implementar recursos de classes encontrados em outras linguagens, tal como https://fpy.li/24-17[traits (_traços_)] (EN) e https://pt.wikipedia.org/wiki/Programa%C3%A7%C3%A3o_orientada_a_aspecto[programação orientada a aspecto] +- Implementar recursos de classes encontrados em outras linguagens, como +https://fpy.li/24-17[traits (_traços_)] e +https://fpy.li/c3[programação orientada a aspecto] Em alguns casos, a metaprogramação de classes também pode ajudar em questões de desempenho, executando tarefas no momento da importação que de outra forma seriam executadas repetidamente durante a execução. -Para finalizar, vamos nos lembrar do conselho final de Alex Martelli em seu ensaio <>: +Para finalizar, vamos nos lembrar do conselho final de Alex Martelli em seu ensaio <> +(<>): [quote] ____ @@ -1377,12 +1379,15 @@ Obrigado, Guido van Rossum e todos que a fazem ser assim. Caleb Hattingh—um dos revisores técnicos((("class metaprogramming", "further reading on"))) desse livro—escreveu o pacote https://fpy.li/24-20[_autoslot_], fornecendo uma metaclasse para a criação automática do atributo `+__slots__+` em uma classe definida pelo usuário, através da inspeção do bytecode de `+__init__+` e da identificação de todas as atribuições a atributos de `self`. Além de útil, esse pacote é um excelente exemplo para estudo: são apenas 74 linhas de código em _autoslot.py_, incluindo 20 linhas de comentários que explicam as partes mais difíceis. -As referências essenciais deste capítulo na documentação de Python são https://docs.python.org/pt-br/3/reference/datamodel.html#customizing-class-creation["3.3.3. Personalizando a criação de classe"] no capítulo "Modelos de Dados" da _Referência da Linguagem Python_, que cobre `+__init_subclass__+` e metaclasses. A https://docs.python.org/pt-br/3/library/functions.html#type[documentação da classe ++type++] na página "Funções Embutidas", e https://docs.python.org/pt-br/3/library/stdtypes.html#special-attributes["4.13. Atributos especiais"] do capítulo "Tipos embutidos" na _Biblioteca Padrão de Python_ também são leituras fundamentais. +As referências essenciais deste capítulo na documentação de Python são https://fpy.li/by["3.3.3. Personalizando a criação de classe"] no capítulo "Modelos de Dados" da _Referência da Linguagem Python_, que cobre `+__init_subclass__+` e metaclasses. A +https://fpy.li/c4[documentação da classe `type`] +na página "Funções Embutidas", e +https://fpy.li/bt["4.13. Atributos especiais"] do capítulo "Tipos embutidos" na _Biblioteca Padrão de Python_ também são leituras fundamentais. -Na _Biblioteca Padrão de Python_, a https://docs.python.org/pt-br/3/library/types.html[documentação do módulo `types`] trata de duas funções introduzidas no Python 3.3, que simplificam a metaprogramação de classes: `types.new_class` and `types.prepare_class`. +Na _Biblioteca Padrão de Python_, a https://fpy.li/bz[documentação do módulo `types`] trata de duas funções introduzidas no Python 3.3, que simplificam a metaprogramação de classes: `types.new_class` and `types.prepare_class`. Decoradores de classes foram formalizados na https://fpy.li/24-25[PEP 3129—Class Decorators (_Decoradores de Classes_)] (EN), escrita por Collin Winter, com a implementação de referência desenvolvida por Jack Diederich. A palestra "Class Decorators: Radically Simple" (_Decoradores de Classes: Radicalmente Simples_. Aqui o https://fpy.li/24-26[video] (EN)), na PyCon 2009, também de Jack Diederich, é uma rápida introdução a esse recurso. -Além de `@dataclass`, um exemplo interessante—e mais simples—de decorador de classes na bilbioteca padrão de Python é https://docs.python.org/pt-br/3/library/functools.html#functools.total_ordering[`functools.total_ordering`] (EN), que gera métodos especiais para comparação de objetos. +Além de `@dataclass`, um exemplo interessante—e mais simples—de decorador de classes na bilbioteca padrão de Python é https://fpy.li/7q[`functools.total_ordering`] (EN), que gera métodos especiais para comparação de objetos. Para metaclasses, a principal referência na documentação de Python é a https://fpy.li/pep3115[PEP 3115—Metaclasses in Python 3000 (_Metaclasses no Python 3000_)], diff --git a/online/xrefs-exemplos.ipynb b/online/xrefs-exemplos.ipynb new file mode 100644 index 0000000..3b59019 --- /dev/null +++ b/online/xrefs-exemplos.ipynb @@ -0,0 +1,336 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "108fd8ff-ce22-4c85-9482-4cf2a33ff396", + "metadata": {}, + "source": [ + "# Referências cruzadas para exemplos em outros capítulos\n", + "\n", + "O Asciidoctor não numera exemplos no formato `12.3` onde `12` é o número do capítulo e `3` é o número do exemplo no capítulo. Existe um [bug](https://github.com/asciidoctor/asciidoctor-pdf/issues/188) e uma [discussão](https://asciidoctor.zulipchat.com/#narrow/channel/279642-users/topic/Example.20numbering.20in.20chapter-example.20format/with/521557604) a respeito.\n", + "\n", + "Atualmente há duas formas de numerar exemplos, figuras, etc: numeração única do início ao fim, ou reiniciar a numeração em cada capítulo.\n", + "\n", + "No primeiro caso, a numeração fica frágil: se um exemplo for separado em dois para facilitar a paginação do livro impresso, isso vai mudar a numeração de todos os exemplos seguintes até o final do livro. No segundo caso, as referências geradas ficam ambíguas: se eu cito o exemplo 2 do capítulo 3 no capítulo 4, o texto gerado aparece apenas como \"Exemplo 2\".\n", + "\n", + "Quando as referências cruzam os volumes, eu já tive que resolver este problema com um script Python, pois neste caso o Asciidoctor nem tem como gerar o texto ambíguo, ele deixa uma referência crua, como `ex_vector2d`. Meu script inclui o número do capítulo e do volume: \"Exemplo 2 do Capítulo 3 (vol.1)\".\n", + "\n", + "Neste notebook vou esboçar uma solução para o caso dos exemplos dentro do mesmo volume, que será útil tanto no livro impresso como nas versões eletrônicas." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "f93406c5-14f0-448f-b4e1-46663108b6e3", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import subprocess\n", + "from pathlib import Path\n", + "\n", + "def find_git_root():\n", + " path = Path(os.path.abspath(''))\n", + " while path != path.parent:\n", + " if (path / '.git').is_dir():\n", + " return path\n", + " path = path.parent\n", + " raise LookupError(f'no .git dir found in {path} or parents')\n", + "\n", + "GIT_ROOT = find_git_root()\n", + "\n", + "INVALID_MSG = 'asciidoctor: INFO: possible invalid reference: '\n", + "\n", + "def list_invalid_xrefs(ch: int) -> list[str]:\n", + " adoc = GIT_ROOT / f'online/cap{ch:02d}.adoc'\n", + " cmd = f'''asciidoctor -v {adoc} -o lixo'''\n", + " result = subprocess.run(cmd, shell=True, stderr=subprocess.PIPE, text=True)\n", + " seen = set()\n", + " xrefs = []\n", + " for line in result.stderr.splitlines():\n", + " assert line.startswith(INVALID_MSG), '? msg: ' + line\n", + " xref = line[len(INVALID_MSG):].strip()\n", + " if xref not in seen:\n", + " xrefs.append(xref)\n", + " seen.add(xref)\n", + "\n", + " return xrefs\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8af4982c-3a8d-45a2-a868-fa1f43b830cc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['ch_data_model',\n", + " 'ch_seq_methods',\n", + " 'ex_vector2d',\n", + " 'ch_op_overload',\n", + " 'ch_generators',\n", + " 'arrays_sec',\n", + " 'memoryview_sec',\n", + " 'what_is_hashable_sec',\n", + " 'ch_dynamic_attrs',\n", + " 'keyword_class_patterns_sec',\n", + " 'conseq_dict_internal_sec',\n", + " 'caching_properties_sec',\n", + " 'del_sec',\n", + " 'numpy_sec',\n", + " 'dataclass_further_sec',\n", + " 'ch_dataclass',\n", + " 'slice_aware_sec']" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list_invalid_xrefs(11)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2c204a17-1987-4dd4-80a0-c7d8d69a1507", + "metadata": {}, + "outputs": [], + "source": [ + "def ch_id(s):\n", + " return s.startswith('ch_')\n", + "\n", + "def sec_id(s):\n", + " return s.endswith('_sec')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "07990e1e-0523-4544-8777-b45cc6d5f519", + "metadata": {}, + "outputs": [], + "source": [ + "def list_possible_examples(ch):\n", + " return [ident for ident in list_invalid_xrefs(ch) if not ch_id(ident) and not sec_id(ident)]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "af23909a-61e3-4ca7-8bb1-494323159edb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "([], [])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tuple(list_possible_examples(i) for i in (9, 10))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "41bd7540-b754-4c2a-9b91-69bf80f19547", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['ex_vector2d']" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list_possible_examples(11) # capítulo revisado" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0e79b5bc-f879-4f29-8980-6a2e157c69f7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['ex_vector2d_v0',\n", + " 'ex_vector2d_v1',\n", + " 'ex_pythonic_deck',\n", + " 'ex_vector2d_v3',\n", + " 'metaprog_part',\n", + " 'ex_vector2d_v3_hash']" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list_possible_examples(12) # capítulo revisado" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "0b5c7eef-1baf-40ab-a2d2-b26a5f903bc6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['ex_pythonic_deck',\n", + " 'mapping_uml',\n", + " 'set_uml',\n", + " 'ex_bingo_callable',\n", + " 'ex_vector2d_v3_full',\n", + " 'ex_top_protocol_test']" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list_possible_examples(13) # capítulo revisado" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "967c0bd7-4496-45ac-ab0a-da2816d9bebb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['ex_strkeydict', 'waterfowl_essay']" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list_possible_examples(14) # capítulo revisado" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "0bf0944b-003a-476b-b009-aa5d9a6bf158", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['ex_tcp_mojifinder_main',\n", + " 'ex_checked_class_top',\n", + " 'ex_tombola_abc',\n", + " 'ex_lotto',\n", + " 'ex_randompick_protocol',\n", + " 'ex_primes_procs_top']" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list_possible_examples(15) # capítulo revisado" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ea15a036-b16b-4298-a3c1-a1fe36f8c27e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['ex_vector_v5',\n", + " 'ex_vector2d',\n", + " 'ex_vector2d_v0',\n", + " 'ex_vector2d_v3_full',\n", + " 'ex_tombola_bingo',\n", + " 'ex_tombola_abc']" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list_possible_examples(16) # capítulo revisado" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f4ef269c-4d66-48a2-90b9-bec7d76481dd", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "943cec10-99f3-4715-a3fc-3f3f17169188", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b941f44a-7f12-4045-921f-7e251cf832b4", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/print/README.md b/print/README.md index 20ac274..baf1ec4 100644 --- a/print/README.md +++ b/print/README.md @@ -1,5 +1,19 @@ # Procedimentos para preparar volumes para impressão +## Rever ISBN na página de copyright e na contra-capa + +Python Fluente, 2ª edição... + +|volume| variante | ISBN | +|------|-----------|-------------------| +| 1 | standard | 978-65-988962-1-8 | +| | dunder | 978-65-988962-0-1 | +| 2 | standard | 978-65-989778-3-2 | +| | dunder | 978-65-989778-2-5 | +| 3 | standard | 978-65-989778-1-8 | +| | dunder | 978-65-989778-6-3 | + + ## Formatação dos links Renderize este markdown para ver a *aparência* dos links. diff --git a/print/append_colophon.sh b/print/append_colophon.sh old mode 100644 new mode 100755 index 008fc7b..ec06c4a --- a/print/append_colophon.sh +++ b/print/append_colophon.sh @@ -1,3 +1,3 @@ #!/bin/bash set -e # exit when any command fails -pdfunite $1 colofao.pdf pyfl-2e.pdf +pdfunite $1 colofao.pdf pyfl2-volXXX-2016-XX-XX.pdf diff --git a/print/attrib-print-pt-br.adoc b/print/attrib-print-pt-br.adoc index a857b71..ba09254 100644 --- a/print/attrib-print-pt-br.adoc +++ b/print/attrib-print-pt-br.adoc @@ -29,3 +29,6 @@ :version-label: Versão // Substituições :dunder: __ +:rt-arrow: -> +:lte: <= +:iadd: += diff --git a/print/contra-capa.adoc b/print/contra-capa.adoc index 3ee9ced..54f4819 100644 --- a/print/contra-capa.adoc +++ b/print/contra-capa.adoc @@ -31,26 +31,31 @@ image::fpy.li-pf2q.png[] ### Volume 1: dados + funções -* Parte I. Estruturas de dados +#### Parte I. Estruturas de dados + 1. O modelo de dados de Python 2. Uma coleção de sequências 3. Dicionários e conjuntos -4. Texto em Unicode versus Bytes +4. Texto em Unicode versus bytes 5. Fábricas de classes de dados 6. Referências, mutabilidade, e memória -* Parte II.a. Funções como objetos +#### Parte II.a. Funções como objetos + +[start=7] 7. Funções como objetos de primeira classe 8. Dicas de tipo em funções ### Volume 2: classes + protocolos -* Parte II.b. Funções como objetos +#### Parte II.b. Funções como objetos + [start=9] 9. Decoradores e Clausuras 10. Padrões de projeto com funções -* Parte III. Classes e protocolos +#### Parte III. Classes e Protocolos + [start=11] 11. Um objeto pythônico 12. Métodos especiais para sequências @@ -61,7 +66,8 @@ image::fpy.li-pf2q.png[] ### Volume 3: controle + metaprogramação -* Parte IV. Controle de fluxo +#### Parte IV. Controle de fluxo + [start=17] 17. Iteradores, geradores e corrotinas clássicas 18. Instruções with, match, e blocos else @@ -69,9 +75,10 @@ image::fpy.li-pf2q.png[] 20. Executores concorrentes 21. Programação assíncrona -* Parte V. Metaprogramação +#### Parte V. Metaprogramação + [start=22] 22. Atributos dinâmicos e propriedades -23. Descritores de Atributos +23. Descritores de atributos 24. Metaprogramação de classes diff --git a/print/hide-uri-scheme/README.md b/print/hide-uri-scheme/README.md new file mode 100644 index 0000000..a37ebd6 --- /dev/null +++ b/print/hide-uri-scheme/README.md @@ -0,0 +1,7 @@ +# Issue request + +https://github.com/asciidoctor/asciidoctor/issues/4696 + +Discussion: + +https://asciidoctor.zulipchat.com/#narrow/channel/288690-users.2Fasciidoctor-pdf/topic/How.20to.20suppress.20https.3A.2F.2F.20prefix.20in.20links.20for.20print \ No newline at end of file diff --git a/print/hide-uri-scheme/build.sh b/print/hide-uri-scheme/build.sh new file mode 100755 index 0000000..3872f1c --- /dev/null +++ b/print/hide-uri-scheme/build.sh @@ -0,0 +1,2 @@ +#!/bin/bash +asciidoctor-pdf --theme default-for-print sample.adoc diff --git a/print/hide-uri-scheme/sample-desired.pdf b/print/hide-uri-scheme/sample-desired.pdf new file mode 100644 index 0000000..bd03f2e Binary files /dev/null and b/print/hide-uri-scheme/sample-desired.pdf differ diff --git a/print/hide-uri-scheme/sample.adoc b/print/hide-uri-scheme/sample.adoc new file mode 100644 index 0000000..718de7f --- /dev/null +++ b/print/hide-uri-scheme/sample.adoc @@ -0,0 +1,7 @@ += Hide All URI Schemes +:hide-uri-scheme: +:media: prepress + +A link without anchor text: https://asciidoctor.org + +A link with anchor text: https://asciidoctor.org[the home of AsciiDoctor] diff --git a/print/xrefs/filter_xrefs.py b/print/xrefs/experiments/filter_xrefs.py similarity index 100% rename from print/xrefs/filter_xrefs.py rename to print/xrefs/experiments/filter_xrefs.py diff --git a/print/xrefs/formatos.adoc b/print/xrefs/experiments/formatos.adoc similarity index 100% rename from print/xrefs/formatos.adoc rename to print/xrefs/experiments/formatos.adoc diff --git a/print/xrefs/list_targets.py b/print/xrefs/experiments/list_targets.py similarity index 100% rename from print/xrefs/list_targets.py rename to print/xrefs/experiments/list_targets.py diff --git a/print/xrefs/experiments/xref-other-vols.ipynb b/print/xrefs/experiments/xref-other-vols.ipynb new file mode 100644 index 0000000..1c399be --- /dev/null +++ b/print/xrefs/experiments/xref-other-vols.ipynb @@ -0,0 +1,207 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "9e0ff8c8-2e08-4144-bb1c-31ae032ad693", + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "base_online = Path('../../online/')\n", + "base_vol = Path('../../vol2/')\n", + "cap_n = 9\n", + "cap = f'cap{cap_n:02d}.adoc'\n", + "\n", + "def cap_vol(cap: int) -> int:\n", + " return ((cap - 1) // 8) + 1\n", + "\n", + "def cap_adoc(cap: int) -> str:\n", + " return f'cap{cap:02d}.adoc'\n", + "\n", + "def path_adoc(cap: int) -> Path:\n", + " vol = cap_vol(cap)\n", + " return Path(f'../../vol{vol}/') / cap_adoc(cap)\n", + "\n", + "print(path_adoc(9))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c21a3dd-ed66-4587-aa51-a443365d346d", + "metadata": {}, + "outputs": [], + "source": [ + "other_vols = {}\n", + "\n", + "for cap_n in list(range(1, 9)) + list(range(17, 25)):\n", + " with open(base_online / cap_adoc(cap_n)) as fp:\n", + " lines = fp.readlines()\n", + " for line in lines:\n", + " if line.startswith('[[ch_'):\n", + " ident = line.split(']]')[0][2:]\n", + " other_vols[ident] = (cap_vol(cap_n), cap_n)\n", + "\n", + "other_vols" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "546fd140-6977-469a-8e32-c7497f09dc27", + "metadata": {}, + "outputs": [], + "source": [ + "# duas xrefs:\n", + "# A maior parte do <> e do <>\n", + "\n", + "for cap_n in range(9, 17):\n", + " with open(path_adoc(cap_n)) as fp:\n", + " adoc = fp.read()\n", + " for ident, (vol, cap) in other_vols.items():\n", + " xref = f'<<{ident}>>'\n", + " adoc = adoc.replace(xref, f'https://fpy.li/{cap}[«Capítulo {cap}»] (vol.{vol})')\n", + "\n", + " with open(path_adoc(cap_n), 'wt', encoding='utf8') as fp:\n", + " fp.write(adoc)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "87de0cb0-4e90-43af-9eb0-f72874f2cc2a", + "metadata": {}, + "outputs": [], + "source": [ + "from xvol_xrefs import replace_xrefs_to_vols" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "552c2fa0-ff72-404b-a7b1-868273c38cb9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<>\t https://pythonfluente.com/2/#prop_validation_sec[«Seção 22.4»] (vol.3)\n", + "<>\t https://pythonfluente.com/2/#numeric_tower_warning_sec[«Seção 8.5.7.1»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#methods_are_descriptors_sec[«Seção 23.4»] (vol.3)\n", + "<>\t https://pythonfluente.com/2/#ex_vector2d[«Seção 23.4»] (vol.3)\n", + "<>\t https://pythonfluente.com/2/#arrays_sec[«Seção 2.10.1»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#memoryview_sec[«Seção 2.10.2»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#what_is_hashable_sec[«Seção 3.4.1»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#keyword_class_patterns_sec[«Seção 5.8.2»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#conseq_dict_internal_sec[«Seção 3.9»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#caching_properties_sec[«Seção 22.3.5»] (vol.3)\n", + "<>\t https://pythonfluente.com/2/#del_sec[«Seção 6.6»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#numpy_sec[«Seção 2.10.3»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#dataclass_further_sec[«Seção 5.10»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#ex_pythonic_deck[«Seção 5.10»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#protocols_in_fn_sec[«Seção 8.5.10»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#slice_objects_sec[«Seção 2.7.2»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#metaprog_part[«V—Metaprogramação»] (vol.3)\n", + "<>\t https://pythonfluente.com/2/#map_filter_reduce_sec[«Seção 7.3.1»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#operator_module_sec[«Seção 7.8.1»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#stdlib_generators_sec[«Seção 17.9»] (vol.3)\n", + "<>\t https://pythonfluente.com/2/#types_defined_by_ops_sec[«Seção 8.4»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#iter_func_sec[«Seção 17.3»] (vol.3)\n", + "<>\t https://pythonfluente.com/2/#mapping_uml[«Seção 17.3»] (vol.3)\n", + "<>\t https://pythonfluente.com/2/#set_uml[«Seção 17.3»] (vol.3)\n", + "<>\t https://pythonfluente.com/2/#set_ops_sec[«Seção 3.11.1»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#type_hint_abc_sec[«Seção 8.5.7»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#ex_bingo_callable[«Seção 8.5.7»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#defensive_argument[«Seção 8.5.7»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#consistent_with_sec[«Seção 8.5.1.1»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#noreturn_sec[«Seção 8.5.12»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#ex_top_protocol_test[«Seção 8.5.12»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#inconsistent_missing_sec[«Seção 3.5.3»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#ex_strkeydict[«Seção 3.5.3»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#anatomy_of_classes_sec[«Seção 24.2»] (vol.3)\n", + "<>\t https://pythonfluente.com/2/#arbitrary_arguments_sec[«Seção 8.6»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#bounded_typevar_sec[«Seção 8.5.9.2»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#mapping_type_sec[«Seção 8.5.6»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#builders_compared_tbl[«Seção 8.5.6»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#ex_tcp_mojifinder_main[«Seção 8.5.6»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#ex_checked_class_top[«Seção 8.5.6»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#callable_variance_sec[«Seção 8.5.11.1»] (vol.1)\n", + "<>\t https://pythonfluente.com/2/#generic_classic_coroutine_types_sec[«Seção 17.13.3»] (vol.3)\n", + "<>\t https://pythonfluente.com/2/#classic_coroutines_sec[«Seção 17.13»] (vol.3)\n", + "<>\t https://pythonfluente.com/2/#ex_primes_procs_top[«Seção 17.13»] (vol.3)\n", + "<>\t https://pythonfluente.com/2/#data_model_emulating_sec[«Seção 1.3.1»] (vol.1)\n" + ] + } + ], + "source": [ + "repls = replace_xrefs_to_vols()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "bd8f090f-e95d-4005-96ce-160387d3db63", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<> https://pythonfluente.com/2/#ex_vector2d\n", + "<> https://pythonfluente.com/2/#ex_pythonic_deck\n", + "<> https://pythonfluente.com/2/#metaprog_part\n", + "<> https://pythonfluente.com/2/#mapping_uml\n", + "<> https://pythonfluente.com/2/#set_uml\n", + "<> https://pythonfluente.com/2/#ex_bingo_callable\n", + "<> https://pythonfluente.com/2/#defensive_argument\n", + "<> https://pythonfluente.com/2/#ex_top_protocol_test\n", + "<> https://pythonfluente.com/2/#ex_strkeydict\n", + "<> https://pythonfluente.com/2/#builders_compared_tbl\n", + "<> https://pythonfluente.com/2/#ex_tcp_mojifinder_main\n", + "<> https://pythonfluente.com/2/#ex_checked_class_top\n", + "<> https://pythonfluente.com/2/#ex_primes_procs_top\n" + ] + } + ], + "source": [ + "for repl in repls:\n", + " xref, rest = repl.split('\\t')\n", + " url = rest.split('[')[0]\n", + " if not xref.endswith('_sec>>'):\n", + " print(xref, url)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd9a3167-fa6c-4f18-99ac-1fcf3bf19a3a", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/print/xrefs/experiments/xrefs.txt b/print/xrefs/experiments/xrefs.txt new file mode 100644 index 0000000..98e7cf4 --- /dev/null +++ b/print/xrefs/experiments/xrefs.txt @@ -0,0 +1,68 @@ +https://pythonfluente.com/2/#prop_validation_sec +https://pythonfluente.com/2/#numeric_tower_warning_sec +https://pythonfluente.com/2/#methods_are_descriptors_sec +https://pythonfluente.com/2/#methods_are_descriptors_sec +https://pythonfluente.com/2/#ex_vector2d +https://pythonfluente.com/2/#arrays_sec +https://pythonfluente.com/2/#memoryview_sec +https://pythonfluente.com/2/#what_is_hashable_sec +https://pythonfluente.com/2/#keyword_class_patterns_sec +https://pythonfluente.com/2/#conseq_dict_internal_sec +https://pythonfluente.com/2/#caching_properties_sec +https://pythonfluente.com/2/#del_sec +https://pythonfluente.com/2/#numpy_sec +https://pythonfluente.com/2/#numpy_sec +https://pythonfluente.com/2/#dataclass_further_sec +https://pythonfluente.com/2/#ex_pythonic_deck +https://pythonfluente.com/2/#ex_pythonic_deck +https://pythonfluente.com/2/#protocols_in_fn_sec +https://pythonfluente.com/2/#protocols_in_fn_sec +https://pythonfluente.com/2/#slice_objects_sec +https://pythonfluente.com/2/#metaprog_part +https://pythonfluente.com/2/#map_filter_reduce_sec +https://pythonfluente.com/2/#operator_module_sec +https://pythonfluente.com/2/#stdlib_generators_sec +https://pythonfluente.com/2/#stdlib_generators_sec +https://pythonfluente.com/2/#types_defined_by_ops_sec +https://pythonfluente.com/2/#protocols_in_fn_sec +https://pythonfluente.com/2/#protocols_in_fn_sec +https://pythonfluente.com/2/#protocols_in_fn_sec +https://pythonfluente.com/2/#ex_pythonic_deck +https://pythonfluente.com/2/#iter_func_sec +https://pythonfluente.com/2/#mapping_uml +https://pythonfluente.com/2/#set_uml +https://pythonfluente.com/2/#set_ops_sec +https://pythonfluente.com/2/#type_hint_abc_sec +https://pythonfluente.com/2/#ex_bingo_callable +https://pythonfluente.com/2/#ex_bingo_callable +https://pythonfluente.com/2/#ex_bingo_callable +https://pythonfluente.com/2/#defensive_argument +https://pythonfluente.com/2/#consistent_with_sec +https://pythonfluente.com/2/#protocols_in_fn_sec +https://pythonfluente.com/2/#protocols_in_fn_sec +https://pythonfluente.com/2/#types_defined_by_ops_sec +https://pythonfluente.com/2/#noreturn_sec +https://pythonfluente.com/2/#ex_top_protocol_test +https://pythonfluente.com/2/#numeric_tower_warning_sec +https://pythonfluente.com/2/#protocols_in_fn_sec +https://pythonfluente.com/2/#inconsistent_missing_sec +https://pythonfluente.com/2/#ex_strkeydict +https://pythonfluente.com/2/#ex_strkeydict +https://pythonfluente.com/2/#anatomy_of_classes_sec +https://pythonfluente.com/2/#inconsistent_missing_sec +https://pythonfluente.com/2/#arbitrary_arguments_sec +https://pythonfluente.com/2/#bounded_typevar_sec +https://pythonfluente.com/2/#mapping_type_sec +https://pythonfluente.com/2/#builders_compared_tbl +https://pythonfluente.com/2/#ex_tcp_mojifinder_main +https://pythonfluente.com/2/#ex_tcp_mojifinder_main +https://pythonfluente.com/2/#ex_checked_class_top +https://pythonfluente.com/2/#callable_variance_sec +https://pythonfluente.com/2/#callable_variance_sec +https://pythonfluente.com/2/#generic_classic_coroutine_types_sec +https://pythonfluente.com/2/#classic_coroutines_sec +https://pythonfluente.com/2/#generic_classic_coroutine_types_sec +https://pythonfluente.com/2/#ex_primes_procs_top +https://pythonfluente.com/2/#ex_primes_procs_top +https://pythonfluente.com/2/#data_model_emulating_sec +https://pythonfluente.com/2/#ex_vector2d diff --git a/print/xrefs/experiments/xrefs_sec.txt b/print/xrefs/experiments/xrefs_sec.txt new file mode 100644 index 0000000..df46b1c --- /dev/null +++ b/print/xrefs/experiments/xrefs_sec.txt @@ -0,0 +1,32 @@ +https://pythonfluente.com/2/#anatomy_of_classes_sec +https://pythonfluente.com/2/#arbitrary_arguments_sec +https://pythonfluente.com/2/#arrays_sec +https://pythonfluente.com/2/#bounded_typevar_sec +https://pythonfluente.com/2/#caching_properties_sec +https://pythonfluente.com/2/#callable_variance_sec +https://pythonfluente.com/2/#classic_coroutines_sec +https://pythonfluente.com/2/#conseq_dict_internal_sec +https://pythonfluente.com/2/#consistent_with_sec +https://pythonfluente.com/2/#data_model_emulating_sec +https://pythonfluente.com/2/#dataclass_further_sec +https://pythonfluente.com/2/#del_sec +https://pythonfluente.com/2/#generic_classic_coroutine_types_sec +https://pythonfluente.com/2/#inconsistent_missing_sec +https://pythonfluente.com/2/#iter_func_sec +https://pythonfluente.com/2/#keyword_class_patterns_sec +https://pythonfluente.com/2/#map_filter_reduce_sec +https://pythonfluente.com/2/#mapping_type_sec +https://pythonfluente.com/2/#memoryview_sec +https://pythonfluente.com/2/#methods_are_descriptors_sec +https://pythonfluente.com/2/#noreturn_sec +https://pythonfluente.com/2/#numeric_tower_warning_sec +https://pythonfluente.com/2/#numpy_sec +https://pythonfluente.com/2/#operator_module_sec +https://pythonfluente.com/2/#prop_validation_sec +https://pythonfluente.com/2/#protocols_in_fn_sec +https://pythonfluente.com/2/#set_ops_sec +https://pythonfluente.com/2/#slice_objects_sec +https://pythonfluente.com/2/#stdlib_generators_sec +https://pythonfluente.com/2/#type_hint_abc_sec +https://pythonfluente.com/2/#types_defined_by_ops_sec +https://pythonfluente.com/2/#what_is_hashable_sec diff --git a/print/xrefs/xvol_xrefs.py b/print/xrefs/experiments/xvol_xrefs.py similarity index 92% rename from print/xrefs/xvol_xrefs.py rename to print/xrefs/experiments/xvol_xrefs.py index 52015e5..20c2d23 100755 --- a/print/xrefs/xvol_xrefs.py +++ b/print/xrefs/experiments/xvol_xrefs.py @@ -47,7 +47,7 @@ def find_git_root(): def list_invalid_xrefs() -> list[str]: - adoc = find_git_root() / 'vol1/vol1.adoc' + adoc = find_git_root() / 'vol2/vol2-cor.adoc' cmd = f'''asciidoctor -v {adoc} -o lixo''' result = subprocess.run(cmd, shell=True, stderr=subprocess.PIPE, text=True) seen = set() @@ -146,6 +146,7 @@ def replace_xrefs_to_vols(): with open(html_path) as fp: html = fp.read() root = BeautifulSoup(html, 'html.parser') + replacements = [] for xref in list_invalid_xrefs(): if xref.startswith('ch_'): chapter = CHAPTER_NUMBER[xref] @@ -161,10 +162,16 @@ def replace_xrefs_to_vols(): chapter = numbers[0] volume = (chapter - 1) // 8 + 1 text = f'Seção {number_str}' - else: - raise ValueError(f'unexpected xref: {xref!r}') + + #else: + # raise ValueError(f'unexpected xref: {xref!r}') link = BASE_URL + '#' + xref - print(f'<<{xref}>>', f'{text} [vol.{volume}, fpy.li{SHORT_URLS[link]}]') + #print(f'<<{xref}>>', f'{text} [vol.{volume}, fpy.li{SHORT_URLS[link]}]') + # https://fpy.li/24[«Capítulo 24»] (vol.3) + repl = f'<<{xref}>>\t {link}[«{text}»] (vol.{volume})' + print(repl) + replacements.append(repl) + return replacements if __name__ == '__main__': diff --git a/print/xrefs/invalid-xrefs.txt b/print/xrefs/invalid-xrefs.txt deleted file mode 100644 index ede2ab6..0000000 --- a/print/xrefs/invalid-xrefs.txt +++ /dev/null @@ -1,106 +0,0 @@ -asciidoctor: INFO: possible invalid reference: ch_ifaces_prot_abc -asciidoctor: INFO: possible invalid reference: ch_ifaces_prot_abc -asciidoctor: INFO: possible invalid reference: ch_op_overload -asciidoctor: INFO: possible invalid reference: ch_generators -asciidoctor: INFO: possible invalid reference: ch_generators -asciidoctor: INFO: possible invalid reference: ch_op_overload -asciidoctor: INFO: possible invalid reference: ch_op_overload -asciidoctor: INFO: possible invalid reference: ch_op_overload -asciidoctor: INFO: possible invalid reference: ch_ifaces_prot_abc -asciidoctor: INFO: possible invalid reference: ch_op_overload -asciidoctor: INFO: possible invalid reference: ch_op_overload -asciidoctor: INFO: possible invalid reference: ch_op_overload -asciidoctor: INFO: possible invalid reference: ch_seq_methods -asciidoctor: INFO: possible invalid reference: ch_op_overload -asciidoctor: INFO: possible invalid reference: ch_seq_methods -asciidoctor: INFO: possible invalid reference: ch_ifaces_prot_abc -asciidoctor: INFO: possible invalid reference: ch_generators -asciidoctor: INFO: possible invalid reference: ch_generators -asciidoctor: INFO: possible invalid reference: ch_op_overload -asciidoctor: INFO: possible invalid reference: pattern_matching_case_study_sec -asciidoctor: INFO: possible invalid reference: pattern_matching_case_study_sec -asciidoctor: INFO: possible invalid reference: pattern_matching_case_study_sec -asciidoctor: INFO: possible invalid reference: pattern_matching_case_study_sec -asciidoctor: INFO: possible invalid reference: pattern_matching_case_study_sec -asciidoctor: INFO: possible invalid reference: ch_seq_methods -asciidoctor: INFO: possible invalid reference: classes_protocols_part -asciidoctor: INFO: possible invalid reference: how_slicing_works_sec -asciidoctor: INFO: possible invalid reference: sliceable_sequence_sec -asciidoctor: INFO: possible invalid reference: ch_op_overload -asciidoctor: INFO: possible invalid reference: ch_generators -asciidoctor: INFO: possible invalid reference: ch_op_overload -asciidoctor: INFO: possible invalid reference: ch_op_overload -asciidoctor: INFO: possible invalid reference: ch_op_overload -asciidoctor: INFO: possible invalid reference: virtual_subclass_sec -asciidoctor: INFO: possible invalid reference: ch_ifaces_prot_abc -asciidoctor: INFO: possible invalid reference: ch_ifaces_prot_abc -asciidoctor: INFO: possible invalid reference: ch_op_overload -asciidoctor: INFO: possible invalid reference: environment_class_ex -asciidoctor: INFO: possible invalid reference: subclass_builtin_woes_sec -asciidoctor: INFO: possible invalid reference: slots_sec -asciidoctor: INFO: possible invalid reference: typeddict_sec -asciidoctor: INFO: possible invalid reference: ch_more_types -asciidoctor: INFO: possible invalid reference: typeddict_sec -asciidoctor: INFO: possible invalid reference: ch_more_types -asciidoctor: INFO: possible invalid reference: ch_class_metaprog -asciidoctor: INFO: possible invalid reference: ch_class_metaprog -asciidoctor: INFO: possible invalid reference: ch_class_metaprog -asciidoctor: INFO: possible invalid reference: problems_annot_runtime_sec -asciidoctor: INFO: possible invalid reference: problems_annot_runtime_sec -asciidoctor: INFO: possible invalid reference: ch_more_types -asciidoctor: INFO: possible invalid reference: ch_more_types -asciidoctor: INFO: possible invalid reference: ch_descriptors -asciidoctor: INFO: possible invalid reference: ch_inheritance -asciidoctor: INFO: possible invalid reference: ch_inheritance -asciidoctor: INFO: possible invalid reference: ch_inheritance -asciidoctor: INFO: possible invalid reference: positional_pattern_implement_sec -asciidoctor: INFO: possible invalid reference: ch_async -asciidoctor: INFO: possible invalid reference: runtime_annot_sec -asciidoctor: INFO: possible invalid reference: multi_hashing_sec -asciidoctor: INFO: possible invalid reference: iterable_reducing_sec -asciidoctor: INFO: possible invalid reference: flexible_new_sec -asciidoctor: INFO: possible invalid reference: ch_generators -asciidoctor: INFO: possible invalid reference: ch_async -asciidoctor: INFO: possible invalid reference: ch_closure_decorator -asciidoctor: INFO: possible invalid reference: ch_closure_decorator -asciidoctor: INFO: possible invalid reference: ch_closure_decorator -asciidoctor: INFO: possible invalid reference: ch_design_patterns -asciidoctor: INFO: possible invalid reference: ch_closure_decorator -asciidoctor: INFO: possible invalid reference: ch_generators -asciidoctor: INFO: possible invalid reference: ch_async -asciidoctor: INFO: possible invalid reference: ch_async -asciidoctor: INFO: possible invalid reference: ch_more_types -asciidoctor: INFO: possible invalid reference: lis_parser_ex -asciidoctor: INFO: possible invalid reference: ch_ifaces_prot_abc -asciidoctor: INFO: possible invalid reference: overload_sec -asciidoctor: INFO: possible invalid reference: overload_sec -asciidoctor: INFO: possible invalid reference: ch_async -asciidoctor: INFO: possible invalid reference: ch_generators -asciidoctor: INFO: possible invalid reference: typeddict_sec -asciidoctor: INFO: possible invalid reference: typeddict_sec -asciidoctor: INFO: possible invalid reference: ch_ifaces_prot_abc -asciidoctor: INFO: possible invalid reference: numbers_abc_proto_sec -asciidoctor: INFO: possible invalid reference: runtime_checkable_proto_sec -asciidoctor: INFO: possible invalid reference: ch_generators -asciidoctor: INFO: possible invalid reference: ch_more_types -asciidoctor: INFO: possible invalid reference: variance_sec -asciidoctor: INFO: possible invalid reference: ch_ifaces_prot_abc -asciidoctor: INFO: possible invalid reference: ch_ifaces_prot_abc -asciidoctor: INFO: possible invalid reference: generic_iterable_types_sec -asciidoctor: INFO: possible invalid reference: ch_ifaces_prot_abc -asciidoctor: INFO: possible invalid reference: overload_sec -asciidoctor: INFO: possible invalid reference: typed_double_sec -asciidoctor: INFO: possible invalid reference: ch_ifaces_prot_abc -asciidoctor: INFO: possible invalid reference: typed_double_sec -asciidoctor: INFO: possible invalid reference: ch_ifaces_prot_abc -asciidoctor: INFO: possible invalid reference: pattern_matching_case_study_sec -asciidoctor: INFO: possible invalid reference: variance_sec -asciidoctor: INFO: possible invalid reference: ch_more_types -asciidoctor: INFO: possible invalid reference: ch_class_metaprog -asciidoctor: INFO: possible invalid reference: checked_class_bottom_ex -asciidoctor: INFO: possible invalid reference: ch_more_types -asciidoctor: INFO: possible invalid reference: ch_more_types -asciidoctor: INFO: possible invalid reference: more_type_hints_further_sec -asciidoctor: INFO: possible invalid reference: typing_peps_tbl -asciidoctor: INFO: possible invalid reference: max_overload_sec -asciidoctor: INFO: possible invalid reference: max_overload_sec diff --git a/print/xrefs/vol2-xrefs.txt b/print/xrefs/vol2-xrefs.txt new file mode 100644 index 0000000..bcfdd4f --- /dev/null +++ b/print/xrefs/vol2-xrefs.txt @@ -0,0 +1,42 @@ +<> https://fpy.li/8k[«Seção 22.4»] (vol.3) +<> https://fpy.li/8g[«Seção 8.5.7.1»] (vol.1) +<> https://fpy.li/8e[«Seção 23.4»] (vol.3) +<> https://fpy.li/8w[«Exemplo 2 do Capítulo 1»] (vol.1) +<> https://fpy.li/7v[«Seção 2.10.1»] (vol.1) +<> https://fpy.li/8d[«Seção 2.10.2»] (vol.1) +<> https://fpy.li/8t[«Seção 3.4.1»] (vol.1) +<> https://fpy.li/8a[«Seção 5.8.2»] (vol.1) +<> https://fpy.li/82[«Seção 3.9»] (vol.1) +<> https://fpy.li/7x[«Seção 22.3.5»] (vol.3) +<> https://fpy.li/86[«Seção 6.6»] (vol.1) +<> https://fpy.li/8h[«Seção 2.10.3»] (vol.1) +<> https://fpy.li/85[«Seção 5.10»] (vol.1) +<> https://fpy.li/8x[«Exemplo 1 do Capítulo 1»] (vol.1) +<> https://fpy.li/8m[«Seção 8.5.10»] (vol.1) +<> https://fpy.li/8p[«Seção 2.7.2»] (vol.1) +<> https://fpy.li/8b[«Seção 7.3.1»] (vol.1) +<> https://fpy.li/8j[«Seção 7.8.1»] (vol.1) +<> https://fpy.li/8q[«Seção 17.9»] (vol.3) +<> https://fpy.li/8s[«Seção 8.4»] (vol.1) +<> https://fpy.li/89[«Seção 17.3»] (vol.3) +<> https://fpy.li/8n[«Seção 3.11.1»] (vol.1) +<> https://fpy.li/8r[«Seção 8.5.7»] (vol.1) +<> https://fpy.li/8y[«Exemplo 8 do Capítulo 7»] (vol.1) +<> https://fpy.li/8z[«Seção 6.5.2»] (vol.1) +<> https://fpy.li/83[«Seção 8.5.1.1»] (vol.1) +<> https://fpy.li/8f[«Seção 8.5.12»] (vol.1) +<> https://fpy.li/92[«Exemplo 22 do Capítulo 8»] (vol.1) +<> https://fpy.li/88[«Seção 3.5.3»] (vol.1) +<> https://fpy.li/93[«Exemplo 9 do Capítulo 3»] (vol.1) +<> https://fpy.li/7s[«Seção 24.2»] (vol.3) +<> https://fpy.li/7t[«Seção 8.6»] (vol.1) +<> https://fpy.li/7w[«Seção 8.5.9.2»] (vol.1) +<> https://fpy.li/8c[«Seção 8.5.6»] (vol.1) +<> https://fpy.li/8v[«Seção 5.2.1»] (vol.1) +<> https://fpy.li/94[«Exemplo 12 do Capítulo 21»] (vol.3) +<> https://fpy.li/95[«Exemplo 5 do Capítulo 24»] (vol.3) +<> https://fpy.li/7y[«Seção 8.5.11.1»] (vol.1) +<> https://fpy.li/87[«Seção 17.13.3»] (vol.3) +<> https://fpy.li/7z[«Seção 17.13»] (vol.3) +<> https://fpy.li/96[«Exemplo 13 do Capítulo 19»] (vol.3) +<> https://fpy.li/84[«Seção 1.3.1»] (vol.1) diff --git a/print/xrefs/xrefs_other_vols.py b/print/xrefs/xrefs_other_vols.py new file mode 100755 index 0000000..427d565 --- /dev/null +++ b/print/xrefs/xrefs_other_vols.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 + +import subprocess +from pathlib import Path +from typing import NamedTuple + +from bs4 import BeautifulSoup + +def find_git_root(): + path = Path(__file__).resolve() + while path != path.parent: + if (path / '.git').is_dir(): + return path + path = path.parent + raise LookupError(f'no .git dir found in {path} or parents') + +GIT_ROOT = find_git_root() + +INVALID_MSG = 'asciidoctor: INFO: possible invalid reference: ' + +def list_invalid_xrefs(vol: int) -> list[str]: + adoc = GIT_ROOT / f'vol{vol}/vol{vol}-cor.adoc' + cmd = f'''asciidoctor -v {adoc} -o lixo''' + result = subprocess.run(cmd, shell=True, stderr=subprocess.PIPE, text=True) + seen = set() + xrefs = [] + for line in result.stderr.splitlines(): + assert line.startswith(INVALID_MSG), '? msg: ' + line + xref = line[len(INVALID_MSG):].strip() + if xref not in seen: + xrefs.append(xref) + seen.add(xref) + + return xrefs + + +def map_short_urls(): + htaccess_path = GIT_ROOT / 'links/FPY.LI.htaccess' + with open(htaccess_path) as fp: + lines = fp.readlines() + long_short = {} + for line in lines: + # RedirectTemp /7s https://pythonfluente.com/2/#anatomy_of_classes_sec + if line.startswith('RedirectTemp'): + _, short, long = line.split() + if long in long_short: + existing_short = long_short[long] + if len(existing_short) < len(short): + short = existing_short + elif existing_short <= short: + short = existing_short + long_short[long] = short + return long_short + + +def cap_vol(cap: int) -> int: + return ((cap - 1) // 8) + 1 + + +class XrefTarget(NamedTuple): + ident: str + vol: int + ch: int + lbl: str = '?' + + +def find_xref_target(ident) -> XrefTarget: + for ch in range(1, 25): + with open(GIT_ROOT / f'online/cap{ch:02d}.adoc') as fp: + adoc = fp.read() + if f'[[{ident}]]' in adoc: + return XrefTarget(ident, cap_vol(ch), ch) + raise LookupError(f'[[{ident}]] not found') + + +def load_html_root(): + html_path = find_git_root() / 'online/index.html' + with open(html_path) as fp: + html = fp.read() + return BeautifulSoup(html, 'html.parser') + + +def get_section_label(root: BeautifulSoup, xref: XrefTarget) -> str: + element = root.find(id=xref.ident) + if element: + text = element.get_text(strip=True).split()[0].rstrip('.') + assert text.startswith(f'{xref.ch}.'), text + '|' + repr(xref) + return f'Seção {text}' + raise LookupError(f'element {xref.ident!r} not found') + + +def get_example_label(root: BeautifulSoup, xref: XrefTarget) -> str: + element = root.find(id=xref.ident) + if element: + text = element.get_text(strip=True).split('.')[0] + return f'{text} do Capítulo {xref.ch}' + raise LookupError(f'element {xref.ident!r} not found') + + +def label_targets(xrefs: list[XrefTarget]) -> list[XrefTarget]: + html_path = find_git_root() / 'online/index.html' + with open(html_path) as fp: + html = fp.read() + root = BeautifulSoup(html, 'html.parser') + result = [] + for xref in xrefs: + label = 'XXX' + if xref.ident.endswith('_sec'): + label = get_section_label(root, xref) + if xref.ident.startswith('ex_'): + label = get_example_label(root, xref) + result.append(xref._replace(lbl=label)) + return result + +if __name__ == '__main__': + #print(find_git_root()) + long_short = map_short_urls() + xrefs = [find_xref_target(ident) for ident in list_invalid_xrefs(2)] + + for xref in label_targets(xrefs): + #print(xref) + short = long_short['https://pythonfluente.com/2/#' + xref.ident] + print(f'<<{xref.ident}>>\thttps://fpy.li{short}[«{xref.lbl}»] (vol.{xref.vol})') diff --git a/vol1/Copyright-cor.adoc b/vol1/Copyright-cor.adoc index 5a4811f..0ebc483 100644 --- a/vol1/Copyright-cor.adoc +++ b/vol1/Copyright-cor.adoc @@ -12,15 +12,15 @@ detentora dos direitos para publicação e venda desta obra. © 2025 Luciano Ramalho. + _Python Fluente, 2ª edição_ está publicado sob a licença -_Atribuição-NãoComercial-CompartilhaIgual 4.0 Internacional_ -«CC BY-NC-SA 4.0» [.small]#[fpy.li/ccby]#. + +CC BY-NC-ND 4.0 + +_Atribuição-NãoComercial-SemDerivações 4.0 Internacional_ [.small]#[fpy.li/ccby]#. + O autor mantém uma versão online em https://PythonFluente.com. Autor: Luciano Ramalho + -Título: Python Fluente, 2ª edição, volume 1: dados e funções + +Título: Python Fluente, 2ª edição, volume 1: Dados e Funções + Primeira edição: 2015 + -Edição atual: setembro/2025 + -{revisao}ª revisão: `pyfl-vol1-rev{revisao}-cor.pdf` +Edição atual: novembro/2025 + +Revisão: `pyfl2-vol1-cor-2026-01-16.pdf` Tradução da 2ª edição: Paulo Candido de Oliveira Filho + Ilustração de capa: Thiago Castor (xilogravura "Calango") + @@ -33,7 +33,7 @@ Publisher: Heinar Maracy @ Z•Edições ---- R135p Ramalho, Luciano. - Python Fluente, 2a edição, volume 1: dados e funções / + Python Fluente, 2ª edição, volume 1: Dados e Funções / Luciano Ramalho - São Paulo, SP: Z.Edições, 2025. 404 p.; il.; cor; 17cm x 24cm @@ -41,7 +41,7 @@ R135p Ramalho, Luciano. 1.Informática. 2.Linguagem de Programação. 3.Python. 4.Metaprogramação. - I.Título II.Dados e funções III.RAMALHO, Luciano. + I.Título II.Dados e Funções III.RAMALHO, Luciano. CDU: 004.438 CDD: 005.133 diff --git a/vol1/Copyright-pb.adoc b/vol1/Copyright-pb.adoc index b4f10b6..af49c90 100644 --- a/vol1/Copyright-pb.adoc +++ b/vol1/Copyright-pb.adoc @@ -12,15 +12,15 @@ detentora dos direitos para publicação e venda desta obra. © 2025 Luciano Ramalho. + _Python Fluente, 2ª edição_ está publicado sob a licença -_Atribuição-NãoComercial-CompartilhaIgual 4.0 Internacional_ -«CC BY-NC-SA 4.0» [.small]#[fpy.li/ccby]#. + +CC BY-NC-ND 4.0 + +_Atribuição-NãoComercial-SemDerivações 4.0 Internacional_ [.small]#[fpy.li/ccby]#. + O autor mantém uma versão online em https://PythonFluente.com. Autor: Luciano Ramalho + -Título: Python Fluente, 2ª edição, volume 1: dados e funções + +Título: Python Fluente, 2ª edição, volume 1: Dados e Funções + Primeira edição: 2015 + -Edição atual: setembro/2025 + -{revisao}ª revisão: `pyfl-vol1-rev{revisao}-pb.pdf` +Edição atual: novembro/2025 + +Revisão: `pyfl2-vol1-pb-2026-01-16.pdf` Tradução da 2ª edição: Paulo Candido de Oliveira Filho + Ilustração de capa: Thiago Castor (xilogravura "Calango") + @@ -33,7 +33,7 @@ Publisher: Heinar Maracy @ Z•Edições ---- R135p Ramalho, Luciano. - Python Fluente, 2a edição, volume 1: dados e funções / + Python Fluente, 2ª edição, volume 1: Dados e Funções / Luciano Ramalho - São Paulo, SP: Z.Edições, 2025. 404 p.; il.; 17cm x 24cm @@ -41,7 +41,7 @@ R135p Ramalho, Luciano. 1.Informática. 2.Linguagem de Programação. 3.Python. 4.Metaprogramação. - I.Título II.Dados e funções III.RAMALHO, Luciano. + I.Título II.Dados e Funções III.RAMALHO, Luciano. CDU: 004.438 CDD: 005.133 diff --git a/vol1/Prefacio.adoc b/vol1/Prefacio.adoc index e8f427d..372392f 100644 --- a/vol1/Prefacio.adoc +++ b/vol1/Prefacio.adoc @@ -16,7 +16,7 @@ ____ Eis um plano: se uma pessoa usar um recurso que você não entende, mate-a. É mais fácil que aprender algo novo, e em pouco tempo os únicos programadores sobreviventes -usarão apenas um subconjunto minúsculo e fácil de entender de Python 0.9.6 .footnote:[Mensagem para o grupo da Usenet comp.lang.python em 23 de dezembro de 2002: «_Acrimony in c.l.p_» [.small]#[fpy.li/p-1]# (EN).] +usarão apenas um subconjunto minúsculo e fácil de entender de Python 0.9.6 .footnote:[Mensagem para o grupo da Usenet comp.lang.python em 23 de dezembro de 2002: «_Acrimony in c.l.p_» [.small]#[fpy.li/p-1]#.] ____ "Python é uma linguagem fácil de aprender e poderosa." Essas((("Python", "appreciating language-specific features"))) são as primeiras palavras do «_tutorial oficial de Python 3.10_» [.small]#[fpy.li/p-2]#. @@ -374,7 +374,7 @@ Alex Martelli e Anna Ravenscroft foram os primeiros a ver o esquema desse livro, Seus livros me ensinaram Python idiomático e são modelos de clareza, precisão e profundidade em escrita técnica. Os mais de 6.200 posts de «Alex no Stack Overflow» [.small]#[fpy.li/p-7]# são uma fonte de boas ideias sobre a linguagem e seu uso apropriado. -Martelli e Ravenscroft foram também revisores técnicos deste livro, juntamente com Lennart Regebro e Leonardo Rochael. Todos nesta proeminente equipe de revisão técnica têm pelo menos 15 anos de experiência com Python, com muitas contribuições a projetos Python de alto impacto, em contato constante com outros desenvolvedores da comunidade. Em conjunto, eles me enviaram centenas de correções, sugestões, questões e opiniões, acrescentando imenso valor ao livro. Victor Stinner revisou o _{ch_async}_, trazendo seu conhecimento especializado, como um dos mantenedores do `asyncio`, para a equipe de revisão técnica. Foi um grande privilégio e um prazer colaborar com eles por estes muitos meses. +Martelli e Ravenscroft foram também revisores técnicos deste livro, juntamente com Lennart Regebro e Leonardo Rochael. Todos nesta proeminente equipe de revisão técnica têm pelo menos 15 anos de experiência com Python, com muitas contribuições a projetos Python de alto impacto, em contato constante com outros desenvolvedores da comunidade. Em conjunto, eles me enviaram centenas de correções, sugestões, questões e opiniões, acrescentando imenso valor ao livro. Victor Stinner revisou o _{ch_async}_, trazendo seu conhecimento especializado, como um dos mantenedores do _asyncio_, para a equipe de revisão técnica. Foi um grande privilégio e um prazer colaborar com eles por estes muitos meses. A editora Meghan Blanchette foi uma fantástica mentora, e me ajudou a melhorar a organização e o fluxo do texto do livro, me mostrando que partes estavam monótonas e evitando que eu atrasasse o projeto ainda mais. Brian MacDonald editou os capítulos da _Parte II_ quando Meghan estava ausente. Adorei trabalhar com eles e com todos na O'Reilly, incluindo a equipe de suporte e desenvolvimento do Atlas (Atlas é a plataforma de publicação de livros da O'Reilly, que usei para escrever esse livro). diff --git a/vol1/cap01.adoc b/vol1/cap01.adoc index c701de1..828fab4 100644 --- a/vol1/cap01.adoc +++ b/vol1/cap01.adoc @@ -194,7 +194,7 @@ Acabamos((("special methods", "advantages of using"))) de ver duas vantagens de os métodos especiais no contexto do Modelo de Dados de Python. * Os usuários de suas classes não precisam memorizar nomes arbitrários de métodos para operações comuns -("Como obter o número de ítens? É `.size()`, `.length()` ou outra coisa?") +("Como obter o número de itens? É `.size()`, `.length()` ou outra coisa?") * É mais fácil aproveitar a rica biblioteca padrão de Python e evitar reinventar a roda, como no caso da função `random.choice`. @@ -420,7 +420,7 @@ Então, por consistência, nossa API também usa `abs` para calcular a magnitude ---- Podemos((("* (star) operator")))((("multiplication, scalar")))((("star (*) operator"))) -também implementar o operador `*`, para realizar multiplicação escalar +também implementar o operador `*`, para realizar multiplicação por escalar (isto é, multiplicar um vetor por um número para obter um novo vetor de mesma direção e magnitude multiplicada): [source, python] @@ -462,7 +462,7 @@ Vou falar mais sobre esse tópico no «Capítulo 16» [.small]#[vol.2, fpy.l ==== Da forma como está implementado, o <> permite multiplicar um `Vector` por um número, mas não um número por um `Vector`, -violando a propriedade comutativa da multiplicação escalar. +violando a propriedade comutativa da multiplicação por escalar. Vamos consertar isso com o método especial `+__rmul__+` no «Capítulo 16» [.small]#[vol.2, fpy.li/16]#. ==== @@ -567,12 +567,6 @@ e `or` devolve um dos seus operandos no formato original: qualquer que seja o valor deste último. ==== -//// -PROD: last time I rendered this chapter in PDF, the NOTE before this comment was rendered -partially orverwriting the start of the following section. -I've seen this happening many times as wrote the book, and sometimes the issue goes away by itself. -Feel free to move the large [[collection_uml]] figure if needed. -//// [[collection_api]] ==== A API de Collection diff --git a/vol1/cap02.adoc b/vol1/cap02.adoc index 995e6bd..6a894e9 100644 --- a/vol1/cap02.adoc +++ b/vol1/cap02.adoc @@ -208,7 +208,7 @@ Qualquer um que saiba um pouco de Python consegue ler o <>. Entretanto, após aprender sobre as listcomps, acho o <> mais legível, porque deixa sua intenção explícita. -Um loop `for` pode ser usado para muitas coisas diferentes: +Um laço `for` pode ser usado para muitas coisas diferentes: percorrer uma sequência para contar ou encontrar itens, computar valores agregados (somas, médias), ou inúmeras outras tarefas. O código no <> está criando uma lista. @@ -220,7 +220,7 @@ Já vi código Python usando listcomps apenas para repetir um bloco de código p Se você não vai fazer alguma coisa com a lista criada, não deveria usar essa sintaxe. Além disso, tente manter o código curto. Se uma compreensão ocupa mais de duas linhas, -provavelmente seria melhor quebrá-la ou reescrevê-la como um bom e velho loop `for`. +provavelmente seria melhor quebrá-la ou reescrevê-la como um bom e velho laço `for`. Use o bom senso: em Python, como em português, não existem regras absolutas para escrever bem. @@ -366,12 +366,12 @@ O resultado tem seis itens. ---- ==== <1> Isso gera uma lista de tuplas ordenadas por cor, depois por tamanho. -<2> Observe que a lista resultante é ordenada como se os loops `for` +<2> Observe que a lista resultante é ordenada como se os laços `for` estivessem aninhados na mesma ordem em que aparecem na listcomp. <3> Para ter os itens ordenados por tamanho e então por cor, apenas rearranje as cláusulas `for`; quebrar a listcomp em duas linhas torna mais fácil ver como o resultado será ordenado. -No <> (<>), +No <> do <>, usei a seguinte expressão para inicializar um baralho de cartas com uma lista contendo 52 cartas de todos os 13 valores possíveis para cada um dos quatro naipes, ordenada por naipe e então por valor: @@ -423,10 +423,10 @@ O <> usa uma genexp com um produto cartesiano para gerar uma relação de camisetas de duas cores em três tamanhos. Diferente do <>, aquela lista de camisetas com seis itens nunca é criada na memória: -a expressão geradora alimenta o loop `for` produzindo um item por vez. +a expressão geradora alimenta o laço `for` produzindo um item por vez. Se as duas listas usadas no produto cartesiano tivessem mil itens cada uma, usar uma função geradora evitaria o custo de construir uma lista -com um milhão de itens apenas para passar ao loop `for`. +com um milhão de itens apenas para passar ao laço `for`. [[ex_genexp_cartesian]] .Produto cartesiano em uma expressão geradora @@ -451,7 +451,7 @@ nunca é criada neste exemplo. [NOTE] ==== -O «Capítulo 17» [.small]#[vol.3, fpy.li/17]# explica em detalhes o funcionamento de geradoras. +O «Capítulo 17» [.small]#[vol.3, fpy.li/17]# explica em detalhes o funcionamento de geradores. A ideia aqui é apenas mostrar o uso de expressões geradoras para inicializar sequências diferentes de listas, ou produzir uma saída que não precise ser mantida na memória. ==== @@ -640,7 +640,7 @@ Quando((("tuples", "versus lists", secondary-sortas="lists")))((("lists", "versu usamos uma tupla como uma variante imutável de `list`, é bom saber o quão similares são suas APIs. Como se pode ver na <>, `tuple` suporta todos os métodos de `list` que não envolvem adicionar ou remover itens, -com uma exceção—`tuple` não possui o método `+__reversed__+`. +com uma exceção—`tuple` não tem o método `+__reversed__+`. Entretanto, `reversed(my_tuple)` funciona sem esse método; ele serve apenas para otimizar. <<< @@ -865,7 +865,7 @@ O primeiro alvo seria `(record,)` e o segundo `((field,),)`. Nos dois casos, esquecer as vírgulas causa um bug silencioso.footnote:[Agradeço ao revisor técnico Leonardo Rochael por esse exemplo.] -Agora vamos estudar _pattern matching_, +Agora vamos estudar casamento de padrões, que suporta maneiras ainda mais poderosas para desempacotar sequências.((("", startref="Sunpack02")))((("", startref="iterun02"))) @@ -874,21 +874,21 @@ sequências.((("", startref="Sunpack02")))((("", startref="iterun02"))) === Pattern matching com sequências O((("match/case statement")))((("sequences", "pattern matching with", id="Spattern02")))((("pattern matching", "match/case statement"))) -novo recurso mais visível de Python 3.10 é o _pattern matching_ -(casamento de padrões) com a instrução `match/case`, proposta na +novo recurso mais visível de Python 3.10 é o +casamento de padrões (_pattern matching_) com a instrução `match/case`, proposta na «_PEP 634—Structural Pattern Matching: Specification_ (Casamento Estrutural de Padrões: Especificação)» [.small]#[fpy.li/pep634]#. [role="man-height2"] [NOTE] ==== Carol Willing, uma das desenvolvedoras principais de Python, -escreveu uma excelente introdução ao _pattern matching_ na seção -«Casamento de padrão estrutural» [.small]#[fpy.li/2r]#footnote:[NT: A tradução em português da documentação de -Python adotou o termo "casamento de padrões" no lugar de _pattern matching_. +escreveu uma excelente introdução ao casamento de padrões na seção +«Casamento de padrão estrutural» [.small]#[fpy.li/2r]#footnote:[NT: +Os tradutores da documentação de Python em português do Brasil +adotaram o termo "casamento de padrões" no lugar de _pattern matching_. O termo em inglês é usado nas comunidades brasileiras -de linguagens que implementam _pattern matching_ há muitos anos, como por exemplo Scala, -Elixir e Haskell. -Naturalmente mantivemos os títulos originais nos links externos.] +de linguagens que implementam casamento de padrões há muitos anos, +como por exemplo Scala, Elixir e Haskell.] em «O que há de novo no Python 3.10» [.small]#[fpy.li/2s]#. Você pode querer ler aquela revisão rápida. Neste livro, dividi o tratamento do casamento de padrões em diferentes capítulos, @@ -947,7 +947,7 @@ Uma melhoria fundamental do `match` sobre o `switch` é((("destructuring"))) a _desestruturação_—uma forma mais avançada de desempacotamento. Desestruturação é uma palavra nova no vocabulário de Python, mas é usada com frequência na documentação de linguagens -que suportam o _pattern matching_—como Scala e Elixir. +que suportam o casamento de padrões—como Scala e Elixir. Como um primeiro exemplo de desestruturação, o <> mostra parte do <> reescrito com `match/case`. @@ -1103,7 +1103,7 @@ incluindo um que ele chamou de O <> não é melhor que o <>. É apenas um exemplo para contrastar duas formas de fazer a mesma coisa. -O próximo exemplo mostra como o _pattern matching_ contribui para a criação de código claro, conciso e eficaz. +O próximo exemplo mostra como o casamento de padrões contribui para a criação de código claro, conciso e eficaz. [[pattern_matching_seq_interp_sec]] ==== Casando padrões de sequência em um interpretador @@ -1116,7 +1116,7 @@ da Universidade de Stanford, escreveu o um interpretador de um subconjunto do dialeto Scheme da linguagem de programação Lisp, em 132 belas linhas de código Python legível. Peguei o código-fonte de Norvig (publicado sob a licença MIT) e o atualizei para Python 3.10, -para exemplificar o _pattern matching_. +para exemplificar o casamento de padrões. Nessa seção, vamos comparar uma parte fundamental do código de Norvig—que usa `if/elif` e desempacotamento—com uma nova versão usando `match/case`. @@ -1167,7 +1167,7 @@ include::../code/02-array-seq/lispy/py3.9/lis.py[tags=EVAL_IF_MIDDLE] ==== Observe como cada instrução `elif` verifica o primeiro item da lista, e então desempacota a lista, ignorando o primeiro item. -O uso extensivo do desempacotamento sugere que Norvig é um fã do _pattern matching_, +O uso extensivo do desempacotamento sugere que Norvig é um fã do casamento de padrões, mas ele originalmente escreveu aquele código em Python 2 (apesar de agora ele funcionar com qualquer Python 3) Usando `match/case` em Python ≥ 3.10, podemos refatorar `evaluate`, como mostrado no <>. @@ -1196,7 +1196,7 @@ Sem o último `case`, para pegar tudo que tiver passado pelos anteriores, o bloco `match` não faz nada quando o sujeito não casa com algum `case`—e isso pode causar uma falha silenciosa. Norvig omitiu deliberadamente a checagem e o tratamento de erros em _lis.py_, para manter o código fácil de entender. -Com _pattern matching_, podemos acrescentar verificações e ainda manter o programa legível. +Com casamento de padrões, podemos acrescentar verificações e ainda manter o programa legível. Por exemplo, no padrão `'define'`, o código original não verifica se `name` é uma instância de `Symbol`—isso exigiria um bloco `if`, uma chamada a `isinstance`, e mais código. @@ -1276,10 +1276,10 @@ o segundo elemento deve ser um `Symbol` na forma original de `define`, mas deve ser uma sequência começando com um `Symbol` na sintaxe de `define` para definição de função. Agora pense em quanto trabalho teríamos para adicionar o suporte a essa segunda sintaxe de `define` -sem a ajuda do _pattern matching_ no <>. +sem a ajuda do casamento de padrões no <>. A instrução `match` faz mais que o `switch` das linguagens similares ao C. -O _pattern matching_ é um exemplo de programação declarativa: +O casamento de padrões é um exemplo de programação declarativa: o código descreve "o que" você quer casar, em vez de "como" casar. A forma do código segue a forma dos dados, como ilustra a <>. @@ -1297,7 +1297,7 @@ A forma do código segue a forma dos dados, como ilustra a <>. Em vez de encher seu código de fatias explícitas fixas, você pode nomeá-las. -Veja como isso torna legível o loop `for` no final do exemplo. +Veja como isso torna legível o laço `for` no final do exemplo. <<< [[flat_file_invoice]] @@ -1689,7 +1689,7 @@ Veja uma demonstração de `*=` com uma sequência mutável e depois com uma seq A concatenação repetida de sequências imutáveis é ineficiente, pois ao invés de apenas acrescentar novos itens, o interpretador tem que copiar toda a sequência alvo para criar um novo objeto com os novos itens concatenados.footnote:[`str` é uma exceção a essa descrição. -Como criar strings com `+=` em loops é muito comum em bases de código reais, +Como criar strings com `+=` em laços é muito comum em bases de código reais, o CPython foi otimizado para este caso de uso. Instâncias de `str` são alocadas na memória com espaço extra, então a concatenação não exige a cópia da string inteira a cada operação.] @@ -2560,7 +2560,7 @@ e o «_Python for Data Analysis_» [.small]#[fpy.li/2-30]#, de Wes McKin "A Numpy é toda sobre vetorização". Essa é a frase de abertura do livro de acesso aberto «_From Python to NumPy_ (Do Python ao NumPy)» [.small]#[fpy.li/2-31]#, de Nicolas P. Rougier. Operações vetorizadas aplicam funções matemáticas a todos os elementos de um array -sem um loop explícito escrito em Python. +sem um laço explícito escrito em Python. Elas podem operar em paralelo, usando instruções especiais de vetor presentes em CPUs modernas, tirando proveito de múltiplos núcleos ou delegando para a GPU, dependendo da biblioteca. O primeiro exemplo no livro de Rougier mostra um aumento de velocidade de 500 vezes, diff --git a/vol1/cap03.adoc b/vol1/cap03.adoc index f1caac8..b534755 100644 --- a/vol1/cap03.adoc +++ b/vol1/cap03.adoc @@ -25,12 +25,12 @@ os conjuntos que você pode ter encontrado em outras linguagens populares. Em especial, os conjuntos de Python implementam todas as operações fundamentais da teoria dos conjuntos, como união, interseção, testes de subconjuntos, etc. Com eles, podemos expressar algoritmos de forma mais declarativa, -evitando o excesso de loops e condicionais aninhados. +evitando o excesso de laços e condicionais aninhados. Aqui está um breve esquema do capítulo: * A sintaxe moderna((("dictionaries and sets", "topics covered"))) para criar e manipular `dicts` -e mapeamentos, incluindo desempacotamento aumentado e _pattern matching_ (casamento de padrões) +e mapeamentos, incluindo desempacotamento aumentado e casamento de padrões (_pattern matching_) * Métodos comuns dos tipos de mapeamentos * Tratamento especial para chaves ausentes * Variantes de `dict` na biblioteca padrão @@ -203,7 +203,7 @@ Se você precisa manter código rodando no Python 3.8 ou anterior, a seção tem um bom resumo das outras formas de mesclar mapeamentos. ==== -Agora vamos ver como o pattern matching se aplica aos mapeamentos.((("", startref="DASsyntax03"))) +Agora vamos ver como o casamento de padrões se aplica aos mapeamentos.((("", startref="DASsyntax03"))) [[pattern_matching_mappings_sec]] === Pattern matching com mapeamentos @@ -223,7 +223,7 @@ Veja `Py_TPFLAGS_MAPPING` [fpy.li/2z]] Vimos apenas padrões com sequências no <>, mas tipos diferentes de padrões podem ser combinados e aninhados. -Graças à desestruturação, o pattern matching é uma ferramenta poderosa para +Graças à desestruturação, o casamento de padrões é uma ferramenta poderosa para processar registros estruturados como sequências e mapeamentos aninhados, que frequentemente recebemos de APIs JSON ou bancos de dados que suportam registros semi-estruturados, @@ -293,8 +293,8 @@ Ice cream details: {'flavor': 'vanilla', 'cost': 199} Na <>, vamos estudar o `defaultdict` e outros mapeamentos onde buscas com chaves via `+__getitem__+` (isto é, `d[chave]`) funcionam porque itens ausentes são criados na hora. -No contexto do pattern matching, -um match é bem sucedido apenas se o sujeito já possui as chaves necessárias no início do bloco `match`. +No contexto do casamento de padrões, +um match é bem sucedido apenas se o sujeito já tem as chaves necessárias no início do bloco `match`. [TIP] ==== @@ -449,7 +449,7 @@ A <> mostra os métodos implementados por `dict` e pelas va A forma como `d.update(m)` lida com seu primeiro argumento, `m`, é um excelente exemplo((("duck typing"))) de _duck typing_ (_tipagem pato_): -ele primeiro verifica se `m` possui um método `keys` e, em caso afirmativo, +ele primeiro verifica se `m` tem um método `keys` e, em caso afirmativo, assume que `m` é um mapeamento. Caso contrário, `update()` reverte para uma iteração sobre `m`, presumindo que seus itens são pares `(chave, valor)`. @@ -706,7 +706,7 @@ Classes definidas pelo usuário derivadas de mapeamentos da biblioteca padrão p como explicado na próxima seção. ==== -[[inconsistent_missing]] +[[inconsistent_missing_sec]] ==== Uso inconsistente de `+__missing__+` na biblioteca padrão Considere os seguintes cenários, e como eles afetam a busca de chaves ausentes: @@ -1212,7 +1212,7 @@ Assim, dados dois conjuntos `a` e `b`, `a | b` devolve sua união, Quando bem utilizadas, as operações de conjuntos podem reduzir tanto a contagem de linhas quanto o tempo de execução de programas Python, ao mesmo tempo em que tornam o código mais legível e -mais fácil de entender—evitando loops e lógica condicional. +mais fácil de entender—evitando laços e lógica condicional. Por exemplo, imagine que você tem um grande conjunto de endereços de e-mail (o "palheiro", `haystack`) e um conjunto menor de endereços (as "agulhas", `needles`), @@ -1379,7 +1379,7 @@ Agora vamos revisar a vasta seleção de operações oferecidas pelos conjuntos. <<< -[[set_op_section]] +[[set_ops_sec]] ==== Operações de conjuntos [[set_uml]] @@ -1552,7 +1552,7 @@ Por outro lado, uma view devolvida por `dict_keys` sempre pode ser usada como um pois todas as chaves são _hashable_—por definição. ==== -Usar operações de conjunto com views pode evitar a necessidade de muitos loops e ifs +Usar operações de conjunto com views pode evitar a necessidade de muitos laços e ifs quando seu código precisa inspecionar o conteúdo de dicionários. Deixe a eficiente implementação de Python em C trabalhar para você! @@ -1564,7 +1564,7 @@ Com isso, encerramos esse capítulo. Dicionários((("dictionaries and sets", "overview of"))) são a pedra fundamental de Python. Ao longo dos anos, a sintaxe literal `{k1: v1, k2: v2}` -passou a suportar desempacotamento com `**` e pattern matching, bem como com compreensões de `dict`. +passou a suportar desempacotamento com `**` e casamento de padrões, bem como com compreensões de `dict`. Além do `dict` básico, a biblioteca padrão oferece mapeamentos práticos prontos para serem usados, como o `defaultdict`, o `ChainMap`, e o `Counter`, todos definidos no módulo `collections`. diff --git a/vol1/cap04.adoc b/vol1/cap04.adoc index c7f0d8b..e85c867 100644 --- a/vol1/cap04.adoc +++ b/vol1/cap04.adoc @@ -215,7 +215,7 @@ causando muitas dores de cabeça nos desenvolvedores que lidam com dados binári A decisão está documentada na «_PEP 461–Adding % formatting to bytes and bytearray_ (Acrescentando formatação com % a bytes e bytearray)» [.small]#[fpy.li/pep461]#. ] -As sequências binárias têm um método de classe que `str` não possui, chamado `fromhex`, +As sequências binárias têm um método de classe que `str` não tem, chamado `fromhex`, que cria uma sequência binária a partir da análise de pares de dígitos hexadecimais, separados opcionalmente por espaços: @@ -1638,7 +1638,7 @@ No <>, observe que o comando `if`, na função `find`, usa o método `.issubset()` para testar rapidamente se todas as palavras no conjunto `query` aparecem na lista de palavras criada a partir do nome do caractere. Graças à rica API de conjuntos de Python, -não precisamos de um loop `for` aninhado e de outro `if` para implementar essa verificação +não precisamos de um laço `for` aninhado e de outro `if` para implementar essa verificação [[ex_cfpy]] .cf.py: o utilitário de busca de caracteres diff --git a/vol1/cap05.adoc b/vol1/cap05.adoc index d494391..4e149f7 100644 --- a/vol1/cap05.adoc +++ b/vol1/cap05.adoc @@ -750,7 +750,7 @@ Vamos examinar uma instância de `DemoNTClass`: Para criar `nt`, precisamos passar pelo menos o argumento `a` para `DemoNTClass`. O construtor também aceita um argumento `b`, mas como este último tem um valor default (de `1.1`), ele é opcional. -Como esperado, o objeto `nt` possui os atributos `a` e `b`; +Como esperado, o objeto `nt` tem os atributos `a` e `b`; ele não tem um atributo `c`, mas Python obtém `c` da classe, como de hábito. Se você tentar atribuir valores para `nt.a`, `nt.b`, `nt.c`, ou mesmo para `nt.z`, @@ -1452,8 +1452,8 @@ id="DCBpattern05")))((("pattern matching", "pattern matching class instances", i são projetados para "casar" com instâncias de classes por tipo e—opcionalmente—por atributos. O sujeito de um padrão de classe pode ser uma instância de qualquer classe, não apenas instâncias de classes de dados.footnote:[Trato desse conteúdo aqui por ser o -primeiro capítulo sobre classes definidas pelo usuário, e acho que _pattern matching_ com classes -é um assunto muito importante para esperar até a <> do livro. +primeiro capítulo sobre classes definidas pelo usuário, e considero o casamento de padrões com classes +um assunto importante demais para esperar até a <> do livro. Minha filosofia: é mais importante saber como usar classes que como defini-las.] Há três variantes de padrões de classes: simples, nomeado e posicional. @@ -1567,7 +1567,7 @@ Também funciona quando a variável do padrão tem o mesmo nome, `country`: <<< Padrões de classe nomeados são bastante legíveis, -e funcionam com qualquer classe que possua atributos de instância públicos. +e funcionam com qualquer classe que tenha atributos de instância públicos. Mas eles são um tanto prolixos. Padrões de classe posicionais são mais convenientes em alguns casos, mas exigem suporte explícito da classe do sujeito, como veremos a seguir. @@ -1671,7 +1671,7 @@ frustrando um princípio básico da programação orientada a objetos: os dados e as funções que acessam os dados devem estar juntos na mesma classe. Classes sem uma lógica podem ser um sinal de uma lógica fora de lugar. -Na última seção, vimos como o _pattern matching_ +Na última seção, vimos como o casamento de padrões funciona com instâncias de qualquer classe como sujeitos—e não apenas das classes criadas com as fábricas apresentadas nesse capítulo. @@ -1757,7 +1757,7 @@ verbete para «_Guido_» [.small]#[fpy.li/5-30]# no [quote] ____ Diz a lenda que o atributo mais importante de Guido, além do próprio Python, -é a máquina do tempo de Guido, um aparelho que dizem que ele possui por causa da +é a máquina do tempo de Guido, um aparelho que dizem que ele tem por causa da frequência irritante com que pedidos de usuários por novos recursos recebem como resposta "Acabei de implementar isso noite passada..." ____ diff --git a/vol1/cap06.adoc b/vol1/cap06.adoc index 661652b..2f38dbc 100644 --- a/vol1/cap06.adoc +++ b/vol1/cap06.adoc @@ -567,7 +567,7 @@ então seu atributo `passengers` se refere a outra lista. Em geral, criar cópias profundas não é uma questão simples. Objetos podem conter referências cíclicas que fariam um algoritmo -ingênuo entrar em um loop infinito. +ingênuo entrar em um laço infinito. A função `deepcopy` memoriza os objetos já copiados, e trata referências cíclicas corretamente. Isto é demonstrado no <>. @@ -775,7 +775,7 @@ a implementação correta vincula uma cópia daquele argumento a `self.passenger A próxima seção explica por que copiar o argumento é uma boa prática. -[[defensive_argument]] +[[defensive_argument_sec]] ==== Programação defensiva com argumentos mutáveis Ao escrever uma função que recebe um argumento mutável, diff --git a/vol1/cap07.adoc b/vol1/cap07.adoc index 6db4e77..4e589e3 100644 --- a/vol1/cap07.adoc +++ b/vol1/cap07.adoc @@ -352,7 +352,7 @@ Introduzidas no Python 3.5. Funções geradoras assíncronas:: Funções((("asynchronous generators"))) ou métodos definidos com `async def`, contendo `yield` em seu corpo. Quando chamados, devolvem um gerador assíncrono para ser usado com `async for`. Introduzidas no Python 3.6. -Funções geradoras, funções de corrotinas nativas e geradoras assíncronas são diferentes de outros invocáveis: os valores devolvidos de tais funções nunca são dados da aplicação, mas objetos que exigem processamento adicional, seja para produzir dados da aplicação, seja para realizar algum trabalho útil. +Funções geradoras, funções de corrotinas nativas e funções geradoras assíncronas são diferentes de outros invocáveis: os valores devolvidos de tais funções nunca são dados da aplicação, mas objetos que exigem processamento adicional, seja para produzir dados da aplicação, seja para realizar algum trabalho útil. Funções geradoras devolvem iteradores. Ambos são tratados no «Capítulo 17» [.small]#[vol.3, fpy.li/17]#. Funções de corrotinas nativas e funções geradoras assíncronas devolvem objetos que só funcionam com a ajuda de um framework de programação assíncrona, tal como _asyncio_. @@ -519,7 +519,7 @@ Após esse mergulho nos recursos flexíveis de declaração de argumentos no Pyt === Pacotes para programação funcional -Apesar((("functions, as first-class objects", "packages for functional programming", id="FAFfp07")))((("functional programming", "packages for", id="fprogpack07"))) de Guido deixar claro que não projetou Python para ser uma linguagem de programação funcional, o estilo de programação funcional pode ser amplamente utilizado, graças a funções de primeira classe, _pattern matching_ e o suporte de pacotes como `operator` e `functools`, dos quais falaremos nas próximas duas seções. +Apesar((("functions, as first-class objects", "packages for functional programming", id="FAFfp07")))((("functional programming", "packages for", id="fprogpack07"))) de Guido deixar claro que não projetou Python para ser uma linguagem de programação funcional, o estilo de programação funcional pode ser amplamente utilizado, graças a funções de primeira classe, casamento de padrões e o suporte de pacotes como `operator` e `functools`, dos quais falaremos nas próximas duas seções. [[operator_module_sec]] @@ -831,7 +831,7 @@ Veja a «_PEP 3102—Keyword-Only Arguments_ (Argumentos somente nomeados)» [.s se quiser saber a justificativa e casos de uso desse recurso. Uma ótima introdução à programação funcional em Python é o «Programação Funcional COMO FAZER» [.small]#[fpy.li/46]#, de A. M. Kuchling. -O principal foco daquele texto, entretanto, é o uso de iteradores e geradoras, assunto do «Capítulo 17» [.small]#[vol.3, fpy.li/17]#. +O principal foco daquele texto, entretanto, é o uso de iteradores e geradores, assunto do «Capítulo 17» [.small]#[vol.3, fpy.li/17]#. A questão no StackOverflow «_Python: Why is functools.partial necessary?_ (Python: Por que functools.partial é necessária?)» diff --git a/vol1/cap08.adoc b/vol1/cap08.adoc index 381c9c4..505f5d5 100644 --- a/vol1/cap08.adoc +++ b/vol1/cap08.adoc @@ -276,7 +276,7 @@ def show_count(count: int, word: str) -> str: [TIP] ==== -Em vez de digitar opções de linha de comando como [.keep-together]#`--disallow-incomplete-defs`#, +Em vez de digitar opções de linha de comando como `--disallow-incomplete-defs`, você pode salvar sua configuração favorita da forma descrita na página «Mypy configuration file» [.small]#[fpy.li/8-8]# na documentação do Mypy. Você pode incluir configurações globais e configurações específicas para cada módulo. @@ -486,7 +486,7 @@ def double(x: abc.Sequence): return x * 2 ---- -Um checador de tipos irá rejeitar esse código. +Um checador de tipos rejeitará este código. Se você informar ao Mypy que `x` é do tipo `abc.Sequence`, ele vai marcar `x * 2` como erro, pois a «`Sequence` ABC» [.small]#[fpy.li/8-13]# não implementa ou herda o método `+__mul__+`. Durante a execução, o código vai funcionar com sequências concretas @@ -1151,7 +1151,7 @@ Podemos resumir esse processo em quatro etapas: . Tornar aquele comportamento o default a partir de Python 3.9: `list[str]` agora funciona sem que `future` precise ser importado. -. Descontinuar (_deprecate_) todos os tipos genéricos do módulo `typing`.footnote:[Uma de minhas contribuições para a documentação do módulo `typing` foi acrescentar dúzias de avisos de descontinuação, enquanto eu reorganizava as entradas abaixo de «Conteúdo do Módulo» [.small]#[fpy.li/4b]# em subseções, sob a supervisão de Guido van [.keep-together]#Rossum#.] Avisos de descontinuação não serão emitidos pelo interpretador Python, porque os checadores de tipos devem sinalizar os tipos descontinuados quando o programa sendo checado tiver como alvo Python 3.9 ou posterior. +. Descontinuar (_deprecate_) todos os tipos genéricos do módulo `typing`.footnote:[Uma de minhas contribuições para a documentação do módulo `typing` foi acrescentar dúzias de avisos de descontinuação, enquanto eu reorganizava as entradas abaixo de «Conteúdo do Módulo» [.small]#[fpy.li/4b]# em subseções, sob a supervisão de Guido van Rossum.] Avisos de descontinuação não serão emitidos pelo interpretador Python, porque os checadores de tipos devem sinalizar os tipos descontinuados quando o programa sendo checado tiver como alvo Python 3.9 ou posterior. . Remover aqueles tipos genéricos redundantes na primeira versão de Python lançada cinco anos após Python 3.9. No ritmo atual, esse deverá ser Python 3.14, também conhecido como Python Pi. **** @@ -1417,7 +1417,7 @@ A lista completa de classes que se tornaram genéricas aparece na seção «_Imp Para encerrar nossa discussão de ABCs em dicas de tipo, precisamos falar sobre as ABCs numéricas. <<< -[[numeric_tower_warning]] +[[numeric_tower_warning_sec]] ===== A queda da torre numérica O((("numbers package")))((("numeric tower"))) pacote @@ -1889,12 +1889,12 @@ include::../code/08-def-type-hints/comparable/top.py[tags=TOP] ---- ==== -Vamos testar `top`. O <> mostra parte de uma bateria de testes para uso com o `pytest`. +Vamos testar `top`. O <> mostra parte de uma bateria de testes para uso com o `pytest`. Ele tenta chamar `top` primeiro com um gerador de expressões que produz `tuple[int, str]`, e depois com uma lista de `object`. Com a lista de `object`, esperamos receber uma exceção de `TypeError`. <<< -[[top_protocol_test]] +[[ex_top_protocol_test]] ._top_test.py_: visão parcial da bateria de testes para `top` ==== [source, python] diff --git a/vol1/pyfl-vol1-rev12-pb.pdf b/vol1/pyfl-vol1-rev12-pb.pdf deleted file mode 100644 index 6622641..0000000 Binary files a/vol1/pyfl-vol1-rev12-pb.pdf and /dev/null differ diff --git a/vol1/pyfl-vol1-rev13-cor.pdf b/vol1/pyfl-vol1-rev13-cor.pdf deleted file mode 100644 index a9d1191..0000000 Binary files a/vol1/pyfl-vol1-rev13-cor.pdf and /dev/null differ diff --git a/vol1/titulos-vol1.adoc b/vol1/titulos-vol1.adoc index abe1292..ea53998 100644 --- a/vol1/titulos-vol1.adoc +++ b/vol1/titulos-vol1.adoc @@ -20,7 +20,7 @@ :ch_closure_decorator: Cap. 9—Decoradores e Clausuras (vol. 2) :ch_design_patterns: Cap. 10—Padrões de projeto com funções (vol. 2) // Parte III -:pa_cls_proto: Parte III—Classes e protocolos +:pa_cls_proto: Parte III—Classes e Protocolos :ch_pythonic_obj: Cap. 11—Um objeto pythônico (vol. 2) :ch_seq_methods: Cap. 12—Métodos especiais para sequências (vol. 2) :ch_ifaces_prot_abc: Cap. 13—Interfaces, protocolos, e ABCs (vol. 2) diff --git a/vol1/vol1-cor.adoc b/vol1/vol1-cor.adoc index d419f0c..c7a14fb 100644 --- a/vol1/vol1-cor.adoc +++ b/vol1/vol1-cor.adoc @@ -1,4 +1,4 @@ -= Python Fluente, 2ª edição: volume 1: Dados e Funções += Python Fluente, 2ª edição: volume 1: Dados e Funções :doctype: book :media: prepress :hide-uri-scheme: diff --git a/vol2/Copyright-cor.adoc b/vol2/Copyright-cor.adoc index 486d70f..9ce3faf 100644 --- a/vol2/Copyright-cor.adoc +++ b/vol2/Copyright-cor.adoc @@ -1,25 +1,26 @@ [colophon%discrete%notitle%nonfacing,toclevels=0] = Copyright -:isbn-cor: XXXX -:isbn-pb: XXXX +:isbn-cor: 978-65-989778-2-5 +:isbn-pb: 978-65-989778-3-2 -Tradução em português autorizada da obra + -_Fluent Python, 2nd Edition_ ISBN 978-1-492-05635-5 +Tradução autorizada em português de +_Fluent Python, 2nd Edition_ + +ISBN 978-1-492-05635-5 © 2022 Luciano Ramalho. + Esta tradução é publicada e vendida com a permissão da O'Reilly Media, Inc., detentora dos direitos para publicação e venda desta obra. © 2025 Luciano Ramalho. + _Python Fluente, 2ª edição_ está publicado sob a licença -_Atribuição-NãoComercial-CompartilhaIgual 4.0 Internacional_ -«CC BY-NC-SA 4.0» [.small]#[fpy.li/ccby]#. + +CC BY-NC-ND 4.0 + +_Atribuição-NãoComercial-SemDerivações 4.0 Internacional_ [.small]#[fpy.li/ccby]#. + O autor mantém uma versão online em https://PythonFluente.com. Autor: Luciano Ramalho + -Título: Python Fluente, 2ª edição, volume 2: classes e protocolos + +Título: Python Fluente, 2ª edição, volume 2: Classes e Protocolos + Primeira edição: 2015 + -Edição atual: setembro/2025 + -{revisao}ª revisão: `pyfl-vol2-rev{revisao}-cor.pdf` +Edição atual: janeiro/2026 + +Revisão: `pyfl2-vol2-cor-2026-01-15.pdf` Tradução da 2ª edição: Paulo Candido de Oliveira Filho + Ilustração de capa: Thiago Castor (xilogravura "Calango") + @@ -32,15 +33,15 @@ Publisher: Heinar Maracy @ Z•Edições ---- R135p Ramalho, Luciano. - Python Fluente, 2a edição, volume 2: classes e protocolos / + Python Fluente, 2ª edição, volume 2: Classes e Protocolos / Luciano Ramalho - São Paulo, SP - Z.Edições, 2025. 400 f.; il.; cor; 17 cm × 24 cm - ISBN: XXX-XX-XXXXXX-X-X + ISBN: 978-65-989778-2-5 1.Informática. 2.Linguagem de Programação. 3.Python. 4.Metaprogramação. - I.Título II.Dados e funções III.RAMALHO, Luciano. + I.Título II.Classes e Protocolos III.RAMALHO, Luciano. CDU: 004.438 CDD: 005.133 diff --git a/vol2/Copyright-pb.adoc b/vol2/Copyright-pb.adoc new file mode 100644 index 0000000..b75ac16 --- /dev/null +++ b/vol2/Copyright-pb.adoc @@ -0,0 +1,48 @@ +[colophon%discrete%notitle%nonfacing,toclevels=0] += Copyright +:isbn-pb: 978-65-989778-3-2 +:isbn-cor: 978-65-989778-2-5 + +Tradução autorizada em português de +_Fluent Python, 2nd Edition_ + +ISBN 978-1-492-05635-5 +© 2022 Luciano Ramalho. + +Esta tradução é publicada e vendida com a permissão da O'Reilly Media, Inc., +detentora dos direitos para publicação e venda desta obra. + +© 2025 Luciano Ramalho. + +_Python Fluente, 2ª edição_ está publicado sob a licença +CC BY-NC-ND 4.0 + +_Atribuição-NãoComercial-SemDerivações 4.0 Internacional_ [.small]#[fpy.li/ccby]#. + +O autor mantém uma versão online em https://PythonFluente.com. + +Autor: Luciano Ramalho + +Título: Python Fluente, 2ª edição, volume 2: Classes e Protocolos + +Primeira edição: 2015 + +Edição atual: janeiro/2026 + +Revisão: `pyfl2-vol2-pb-2026-01-15.pdf` + +Tradução da 2ª edição: Paulo Candido de Oliveira Filho + +Ilustração de capa: Thiago Castor (xilogravura "Calango") + +Design da capa: Luciano Ramalho, Zander Catta Preta @ Z•Edições + +Design do miolo: Luciano Ramalho, com Asciidoctor + +Ficha catalográfica: Edison Luís dos Santos + +Publisher: Heinar Maracy @ Z•Edições + +---- +R135p Ramalho, Luciano. + + Python Fluente, 2ª edição, volume 2: Classes e Protocolos / + Luciano Ramalho - São Paulo, SP - Z.Edições, 2025. + 364 p.; il.; 17 cm × 24 cm + + ISBN: 978-65-989778-3-2 + 1.Informática. 2.Linguagem de Programação. 3.Python. + 4.Metaprogramação. + + I.Título II.Classes e Protocolos III.RAMALHO, Luciano. + + CDU: 004.438 + CDD: 005.133 +---- diff --git a/vol2/README.md b/vol2/README.md new file mode 100644 index 0000000..3ba43b4 --- /dev/null +++ b/vol2/README.md @@ -0,0 +1,19 @@ +# Python Fluente, 2ª ed, volume 2 + +## Progresso + +Faço as primeiras tarefas nos arquivos `/online/cap??.adoc`. + +Depois copio cada arquivo para `/vol2/cap??.adoc` +e faço as demais tarefas nestas cópias especiais para impressão. + +| 9 | 10| 11| 12| 13| 14| 15| 16| local | tarefa | +|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|-------|-------| +|✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |`/online`|encurtar links externos| +|✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |`/online`|revisar estilo| +|✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |`/online`|revisar ortografia e gramática| +|✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |`/vol2`| refazer referências entre volumes| +|✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |`/vol2`| encurtar links entre volumes | +|✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |`/vol2`| exibir capítulo alvo em xrefs para exemplos de outros capítulos | +|✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |`/vol2`| rever paginação | + diff --git a/vol2/cap09.adoc b/vol2/cap09.adoc new file mode 100644 index 0000000..e5a1475 --- /dev/null +++ b/vol2/cap09.adoc @@ -0,0 +1,1780 @@ +[[ch_closure_decorator]] +== Decoradores e Clausuras +:example-number: 0 +:figure-number: 0 + +[quote, PEP 318—Decorators for Functions and Methods] +____ +Houve uma certa quantidade de reclamações sobre a escolha do nome "decorador" +para esse recurso. A mais frequente foi sobre o nome não ser consistente com seu +uso no livro da GoF.footnote:[GoF se refere ao livro __Design Patterns__ +(traduzido no Brasil como Padrões de Projeto). Seus +autores ficaram conhecidos como a _Gang of Four_ (Gangue dos Quatro).] O nome +++decorator++ provavelmente se origina de seu uso no âmbito dos +compiladores--uma árvore sintática é percorrida e anotada. +____ + +Decoradores de função((("decorators and closures", "purpose of"))) nos permitem +"marcar" funções no código-fonte, para aprimorar de alguma forma seu +comportamento. É um mecanismo muito poderoso. Por exemplo, o decorador +`@functools.cache` armazena um mapeamento de argumentos para resultados, e +depois usa esse mapeamento para evitar computar novamente o resultado quando a +função é chamada com argumentos já vistos. Isso pode acelerar muito uma +aplicação. + +Para dominar esse recurso, é preciso antes entender clausuras (_closures_)— +nome dado à estrutura onde uma função captura variáveis presentes no escopo onde +a função é definida, necessárias para a execução da função +futuramente.footnote:[NT: Adotamos a tradução "clausura" para "closure". +O termo em inglês é pronunciado "clôujure", e o nome da linguagem Clojure brinca +com esse fato. Alguns autores usam "fechamento", mas esta é uma tradução de +"closure" no contexto da teoria dos conjuntos que não tem relação com "closure" +na teoria de linguagens de programação. Gosto da palavra clausura por uma +analogia cultural: em alguns conventos, a clausura é um espaço fechado onde +freiras vivem em isolamento. Suas memórias são seu único vínculo com o exterior, +mas elas retratam o mundo do passado. Em programação, uma clausura é um espaço +isolado onde a função tem acesso a variáveis que existiam quando a própria +função foi criada, variáveis de um escopo que não existe mais, preservadas +apenas na memória clausura.] + +A palavra reservada mais obscura de Python é `nonlocal`, introduzida no Python +3.0. É perfeitamente possível ter uma vida produtiva e lucrativa programando em +Python sem jamais usá-la, seguindo uma dieta estrita de orientação a objetos +centrada em classes. Entretanto, caso queira implementar seus próprios +decoradores de função, precisa entender clausuras, e então a necessidade de +`nonlocal` fica evidente. + +Além de sua aplicação aos decoradores, clausuras também são essenciais para +qualquer tipo de programação utilizando _callbacks_, e para codar em um estilo +funcional quando isso fizer sentido. + +O((("decorators and closures", "topics covered"))) objetivo final deste +capítulo é explicar exatamente como funcionam os decoradores de função, desde +simples decoradores de registro até os complicados decoradores parametrizados. +Mas antes de chegar a esse objetivo, precisamos tratar de: + +<<< +* Como Python analisa a sintaxe de decoradores +* Como Python decide se uma variável é local +* Por que clausuras existem e como elas funcionam +* Qual problema é resolvido por `nonlocal` + +Após criar essa base, chegaremos aos decoradores: + +* Como implementar um decorador bem comportado +* Decoradores poderosos da biblioteca padrão: `@cache` e `@singledispatch` +* Como implementar um decorador parametrizado + + +=== Novidades neste capítulo + +Nesta((("decorators and closures", "significant changes to"))) edição, +apresento o decorador de _caching_ `functools.cache` do Python +3.9 antes do `functools.lru_cache`, que é mais antigo. +A <> apresenta também o uso de `lru_cache` +sem argumentos, uma novidade do Python 3.8. +Expandi a <> para incluir dicas de tipo, a sintaxe +recomendada para usar `functools.singledispatch` desde o Python 3.7. +A <> agora inclui o <>, +que usa uma classe e não uma clausura para implementar um decorador. + +Começamos com a introdução aos decoradores mais suave que consegui imaginar. + + +=== Introdução aos decoradores + +Um((("decorators and closures", "decorator basics", id="DACbasic09"))) decorador +é um invocável que recebe outra função como um argumento (a função decorada). + +Um decorador pode executar algum processamento com a função decorada, e pode +devolver a mesma função ou substituí-la por outra função ou objeto invocável.footnote:[Se +você substituir "função" por "classe" na sentença anterior, o resultado é uma +descrição resumida do papel de um decorador de classe, assunto que veremos no +https://fpy.li/24[«Capítulo 24»] (vol.3).] + +Em outras palavras, supondo a existência de uma função decoradora +chamada `decorate`, este código: + +<<< +[source, python] +---- +@decorate +def target(): + print('running target()') +---- + +tem o mesmo efeito de: + +[source, python] +---- +def target(): + print('running target()') + +target = decorate(target) +---- + +O resultado final é o mesmo: após a execução de qualquer um destes exemplos, +o nome `target` está vinculado a qualquer que seja a função devolvida por +`decorate(target)`—que tanto pode ser a função inicialmente chamada `target` +quanto uma outra função diferente. + +Para confirmar que a função decorada é substituída, veja a sessão de console no +<>. + +[[decorator_replaces]] +.Um decorador normalmente substitui uma função por outra, diferente +==== +[source, python] +---- +>>> def deco(func): +... def inner(): +... print('running inner()') +... return inner <1> +... +>>> @deco +... def target(): <2> +... print('running target()') +... +>>> target() <3> +running inner() +>>> target <4> +.inner at 0x10063b598> +---- +==== +<1> `deco` devolve seu objeto função `inner`. +<2> `target` é decorada por `deco`. +<3> Invocar a `target` decorada causa, na verdade, a execução de `inner`. +<4> A inspeção revela que `target` é agora uma referência a `inner`. + +Estritamente falando, decoradores são apenas açúcar sintático. Como vimos, é +sempre possível chamar um decorador como um invocável normal, passando outra +função como parâmetro. Algumas vezes isso inclusive é conveniente, especialmente +quando estamos fazendo _metaprogramação_—mudando o comportamento de um programa +durante a execução. + +Três fatos essenciais sobre decoradores: + +. Um decorador é uma função ou outro invocável. +. Um decorador pode, opcionalmente, substituir a função decorada por outra. +. Decoradores são executados assim que um módulo é carregado. + +Vamos agora nos concentrar nesse terceiro ponto.((("", startref="DACbasic09"))) + + +=== Quando Python executa decoradores + +Uma((("decorators and closures", "decorator execution")))((("import time versus +runtime"))) característica fundamental dos decoradores é serem executados logo +após a função decorada ser definida. Isso normalmente acontece no +momento da importação (_import time_), ou seja, quando um módulo é carregado pelo Python. +Observe _registration.py_ no <>. + +[[registration_ex]] +.O módulo registration.py +==== +[source, python] +---- +include::../code/09-closure-deco/registration.py[tags=REGISTRATION] +---- +==== +<1> `registry` vai armazenar referências para funções decoradas por `@register`. +<2> `register` recebe uma função como argumento. +<3> Exibe a função que está sendo decorada, para demonstração. +<4> Insere `func` em `registry`. +<5> É obrigatório devolver uma função; +aqui devolvemos a mesma função recebida como argumento. +<6> `f1` e `f2` são decoradas por `@register`. +<7> `f3` não é decorada. +<8> `main` exibe `registry`, depois chama `f1()`, `f2()`, e `f3()`. +<9> `main()` só é invocada se _registration.py_ for executado como um script. + +O resultado da execução de _registration.py_ é assim: + +---- +$ python3 registration.py +running register() +running register() +running main() +registry -> [, ] +running f1() +running f2() +running f3() +---- + +Observe que `register` roda (duas vezes) antes de qualquer outra função no +módulo. Quando `register` é chamada, ela recebe o objeto função a ser decorado +como argumento—por exemplo, ``. + +Após o carregamento do módulo, a lista `registry` contém referências para as +duas funções decoradas: `f1` e `f2`. Essas funções, bem como `f3`, são executadas +apenas quando chamadas explicitamente por `main`. + +Se _registration.py_ for importado (e não executado como um script), a saída é +essa: + +[source, python] +---- +>>> import registration +running register() +running register() +---- + +Nesse momento, se você inspecionar `registry`, verá isso: + +[source, python] +---- +>>> registration.registry +[, ] +---- + +O ponto central do <> é enfatizar que decoradores de função são +executados assim que o módulo é importado, mas as funções decoradas só rodam +quando são invocadas explicitamente. Isso ressalta a diferença entre o +_momento da importação_ e o _momento da execução_ +na operação de um módulo em Python. + +[[registration_deco_sec]] +=== Decoradores de registro + +Considerando((("decorators and closures", "registration +decorators")))((("registration decorators"))) a forma como decoradores são +normalmente usados em código do mundo real, o <> é incomum por +dois motivos: + +* A função do decorador é definida no mesmo módulo das funções decoradas. +Tipicamente, um decorador é definido em um módulo de uma biblioteca +e aplicado a funções de outros módulos de bibliotecas ou aplicações. +* O decorador `register` devolve a mesma função recebida como +argumento. Na prática, a maior parte dos decoradores define e devolve uma função +interna. + +Apesar do decorador `register` no <> devolver a função decorada +inalterada, ele não é inútil. Decoradores parecidos são usados por muitos +frameworks Python para adicionar funções a um registro central—por exemplo, um +registro mapeando padrões de URLs para funções que geram respostas HTTP. Tais +decoradores de registro podem ou não modificar as funções decoradas. + +Vamos ver um decorador de registro em ação na <> (<>). + +A maioria dos decoradores modifica a função decorada. Eles normalmente fazem +isso definindo e devolvendo uma função interna para substituir a função +decorada. Código que usa funções internas quase sempre depende de clausuras para +operar corretamente. Para entender as clausuras, precisamos dar um passo atrás e +revisar como o escopo de variáveis funciona no Python. + +=== Regras de escopo de variáveis + +No((("decorators and closures", "variable scope rules", +id="DACvars09")))((("variable scope rules", id="vsr09")))((("scope", +"variable scope rules", id="Svsr09"))) <>, definimos e testamos uma +função que lê duas variáveis: uma variável local `a`—definida como parâmetro de +função—e a variável `b`, que não é definida em lugar algum na função. + +[[ex_global_undef]] +.Função lendo uma variável local e uma variável global +==== +[source, python] +---- +>>> def f1(a): +... print(a) +... print(b) +... +>>> f1(3) +3 +Traceback (most recent call last): + File "", line 1, in + File "", line 3, in f1 +NameError: global name 'b' is not defined +---- +==== + +O erro obtido não é surpreendente. Continuando do <>, se +atribuirmos um valor a um `b` global e então chamarmos `f1`, funciona: + +[source, python] +---- +>>> b = 6 +>>> f1(3) +3 +6 +---- + +Agora vamos ver um exemplo que pode ser surpreendente. + +Leia com atenção a função `f2`, no <>. As primeiras duas linhas +são as mesmas da `f1` do <>, e então ela faz uma atribuição a +`b`. Mas para com um erro no segundo `print`, antes da atribuição ser executada. + +[[ex_local_unbound]] +.A variável `b` é local, porque um valor é atribuído a ela no corpo da função +==== +[source, python] +---- +>>> b = 6 +>>> def f2(a): +... print(a) +... print(b) +... b = 9 +... +>>> f2(3) +3 +Traceback (most recent call last): + File "", line 1, in + File "", line 3, in f2 +UnboundLocalError: local variable 'b' referenced before assignment +---- +==== + +Observe que a saída começa com `3`, provando que o comando `print(a)` foi +executado. Mas o segundo, `print(b)`, nunca roda. Quando vi isso pela primeira +vez, me espantei. Achei que o `6` seria exibido, pois há uma variável +global `b`, e a atribuição para a variável local `b` ocorre após `print(b)`. + +Mas quando Python compila o corpo da função, ele decide que `b` é +uma variável local, por ser atribuída dentro da função. O bytecode gerado +reflete essa decisão. O código tentará acessar `b` no escopo local. Mais tarde, quando a +chamada `f2(3)` acontece, o corpo de `f2` obtém e exibe o valor da variável +local `a`, mas ao tentar obter o valor da variável local `b`, descobre que `b` +não está vinculado a nada. + +Isso não é um bug, mas uma escolha de projeto: +Python não exige que você declare variáveis, +mas assume que uma variável que recebe uma atribuição no corpo de uma função +é uma variável local. +Isso é muito melhor que o comportamento de Javascript, que também não requer +declarações de variáveis, mas se você esquecer de declarar uma variável como +local (com `var`), pode acabar alterando uma variável global por acidente. + +Se queremos que o interpretador trate `b` como uma variável global e também +atribuir um novo valor a ela dentro da função, usamos a declaração `global`: + +[source, python] +---- +>>> b = 6 +>>> def f3(a): +... global b +... print(a) +... print(b) +... b = 9 +... +>>> f3(3) +3 +6 +>>> b +9 +---- + +Nos exemplos anteriores, vimos dois escopos em ação: + +O escopo global do módulo:: Composto((("scope", "module global scope"))) por +nomes atribuídos a valores fora de qualquer bloco de classe ou função. + +O escopo local da função `f3`:: Composto((("scope", "function local scope"))) por +nomes atribuídos a valores como parâmetros, ou diretamente no corpo da função. + +Há um outro escopo onde variáveis podem existir, chamado _nonlocal_ (não-local). +Ele ocorre quando há funções aninhadas; vamos tratar disso em breve. + +Agora que vimos como o escopo de variáveis funciona no Python, podemos +enfrentar as clausuras na <>. +Se tiver curiosidade sobre as diferenças no bytecode das funções no <<#ex_global_undef>> +e no <<#ex_local_unbound>>, veja o quadro a seguir.((("", +startref="DACvars09")))((("", startref="vsr09")))((("", startref="Svsr09"))) + +.Comparando bytecodes +**** + +O((("dis module")))((("bytecode, disassembling")))((("functions", "disassembling bytecode of"))) +módulo `dis` descompila o bytecode de funções. +Leia no <<#ex_f1_dis>> e no <<#ex_f2_dis>> os +bytecodes de `f1` e `f2`, do <<#ex_global_undef>> e do <<#ex_local_unbound>>, +respectivamente. + +<<< +[[ex_f1_dis]] +.Bytecode da função `f1` do <> +==== +[source, python] +---- +>>> from dis import dis +>>> dis(f1) + 2 0 LOAD_GLOBAL 0 (print) <1> + 3 LOAD_FAST 0 (a) <2> + 6 CALL_FUNCTION 1 (1 positional, 0 keyword pair) + 9 POP_TOP + + 3 10 LOAD_GLOBAL 0 (print) + 13 LOAD_GLOBAL 1 (b) <3> + 16 CALL_FUNCTION 1 (1 positional, 0 keyword pair) + 19 POP_TOP + 20 LOAD_CONST 0 (None) + 23 RETURN_VALUE +---- +==== +<1> Carrega o nome global `print`. +<2> Carrega o nome local `a`. +<3> Carrega o nome global `b`. + +Compare o bytecode de `f1`, visto no <> acima, com o bytecode de `f2` no <>. + +[[ex_f2_dis]] +.Bytecode da função `f2` do <> +==== +[source, python] +---- +>>> dis(f2) + 2 0 LOAD_GLOBAL 0 (print) + 3 LOAD_FAST 0 (a) + 6 CALL_FUNCTION 1 (1 positional, 0 keyword pair) + 9 POP_TOP + + 3 10 LOAD_GLOBAL 0 (print) + 13 LOAD_FAST 1 (b) <1> + 16 CALL_FUNCTION 1 (1 positional, 0 keyword pair) + 19 POP_TOP + + 4 20 LOAD_CONST 1 (9) + 23 STORE_FAST 1 (b) + 26 LOAD_CONST 0 (None) + 29 RETURN_VALUE +---- +==== +<1> Carrega o nome _local_ `b`. Isso mostra que o compilador considera `b` uma +variável local, mesmo com uma atribuição a `b` ocorrendo mais tarde, porque a +natureza da variável—se ela é ou não local—não pode mudar no corpo da função. + +A máquina virtual (VM) do CPython que executa o bytecode é uma máquina de pilha +(_stack machine_), então as operações `LOAD` e `POP` se referem à pilha. A descrição +mais detalhada dos opcodes de Python está além da finalidade desse livro, mas +eles estão documentados com o módulo, em +https://fpy.li/5x["dis—Disassembler de bytecode de Python"]. + +**** + +[[closures_sec]] +=== Clausuras + +Na((("decorators and closures", "closure basics", id="DACclos09")))((("anonymous +functions"))) blogosfera, as clausuras (_closures_) são às vezes confundidas com funções +anônimas. A confusão se deve à história paralela destes conceitos: +definir funções dentro de outras funções se torna mais comum e conveniente quando +existem funções anônimas. +E clausuras só fazem sentido a partir do momento em que você tem funções aninhadas. +Daí que muitos aprendem as duas ideias ao mesmo tempo. + +Na verdade, uma clausura é uma função—vamos chamá-la de `f`—com um escopo +estendido, incorporando variáveis acessadas no corpo de `f` que não são +variáveis globais nem variáveis locais de `f`. Tais variáveis devem vir do +escopo local de uma função que engloba `f`. + +Não interessa se a função é anônima ou não; o que importa é que ela pode +acessar variáveis não-globais definidas fora de seu corpo. + +É um conceito difícil de entender, melhor ilustrado por um exemplo. + +Imagine uma função `avg`, para calcular a média de uma série de valores que +cresce continuamente; por exemplo, o preço diário de um produto +ao longo de toda a sua história. A cada dia, um novo preço é acrescentado, +e a média é computada levando em conta todos os preços até então. + +<<< +Começando do zero, `avg` poderia ser usada assim: + +[source, python] +---- +>>> avg(10) +10.0 +>>> avg(11) +10.5 +>>> avg(12) +11.0 +---- + +Como `avg` é definida, e onde fica o histórico com os valores anteriores? + +Para começar, o <> mostra uma implementação baseada em uma classe. + +[[ex_average_oo]] +.average_oo.py: uma classe para calcular uma média cumulativa +==== +[source, python] +---- +class Averager(): + + def __init__(self): + self.series = [] + + def __call__(self, new_value): + self.series.append(new_value) + total = sum(self.series) + return total / len(self.series) +---- +==== + +A classe `Averager` cria instâncias invocáveis: + +[source, python] +---- +>>> avg = Averager() +>>> avg(10) +10.0 +>>> avg(11) +10.5 +>>> avg(12) +11.0 +---- + +O <>, a seguir, é uma implementação funcional, usando a função de +ordem superior `make_averager`. + +[[ex_average_fn]] +.average.py: uma função de ordem superior para a calcular uma média cumulativa +==== +[source, python] +---- +def make_averager(): + series = [] + + def averager(new_value): + series.append(new_value) + total = sum(series) + return total / len(series) + + return averager +---- +==== + +Quando invocada, `make_averager` devolve um objeto função `averager`. Cada vez +que um `averager` é invocado, ele insere o argumento recebido na série, e +calcula a média atual, como mostra o <>. + +[[ex_average_demo1]] +.Testando o <> +==== +[source, python] +---- +>>> avg = make_averager() +>>> avg(10) +10.0 +>>> avg(11) +10.5 +>>> avg(15) +12.0 +---- +==== + +Note as semelhanças entre os dois exemplos: chamamos `Averager()` ou +`make_averager()` para obter um objeto invocável `avg`, que atualizará a série +histórica e calculará a média atual. No <>, `avg` é uma instância +de `Averager`, no <> é a função interna `averager`. Nos dois +casos, basta chamar `avg(n)` para incluir `n` na série e obter a média +atualizada. + +É óbvio onde o `avg` da classe `Averager` armazena o histórico: no atributo de +instância `self.series`. Mas onde a função `avg` no <> encontra a +`series`? + +Observe que `series` é uma variável local de `make_averager`, pois a atribuição +`series = []` acontece no corpo daquela função. Mas quando `avg(10)` é chamada, +`make_averager` já retornou, e seu escopo local não existe mais. + +Dentro de `averager`, `series` é uma((("free variables")))((("variables", +"free"))) _variável livre_: uma variável que é mencionada mas não é +um parâmetro, e não tem uma atribuição no escopo local. +Esse termo técnico é essencial para entender +uma clausura. Veja a <>. + +[[closure_fig]] +.A clausura de `averager` estende o escopo daquela função para incluir a vinculação da variável livre `series`. +image::../images/diagrama9-1.png[Diagrama de uma clausura] + +Podemos inspecionar o objeto `averager` para ver como Python armazena os nomes +das variáveis locais e variáveis livres no atributo `+__code__+`, +que representa o corpo compilado da função. O <> demonstra isso. + +[[ex_average_demo2]] +.Inspecionando a função criada por `make_averager` no <> +==== +[source, python] +---- +>>> avg.__code__.co_varnames +('new_value', 'total') +>>> avg.__code__.co_freevars +('series',) +---- +==== + +O valor de `series` é armazenado no atributo `+__closure__+` da função +`avg`. Cada item em `+avg.__closure__+` corresponde a um nome em `+__code__+`, +e tem um atributo chamado `cell_contents`, com o valor vinculado. +Confira o <>: + +[[ex_average_demo3]] +.Continuando do <> +==== +[source, python] +---- +>>> avg.__closure__ +(,) +>>> avg.__closure__[0].cell_contents +[10, 11, 12] +---- +==== + +Resumindo: uma clausura é uma função que retém os vínculos das variáveis livres +que existem quando a função é definida, de forma que elas possam ser usadas mais tarde, +quando a função for invocada, mas o escopo de sua definição não puder mais ser acessado. + +Note que a única situação na qual uma função pode ter de lidar com variáveis +externas não-globais é quando ela estiver aninhada dentro de outra função, e +aquelas variáveis sejam parte do escopo local da função externa.((("", +startref="DACclos09"))) + +[[nonlocal_sec]] +=== A instrução `nonlocal` + +A((("decorators and closures", "nonlocal declarations", +id="DACnonlocal09")))((("nonlocal keyword", id="nonlocal09")))((("keywords", +"nonlocal keyword", id="Knon09"))) implementação anterior de `make_averager` funciona, +mas é ineficiente. No <>, armazenamos todos os valores na série +histórica e calculamos sua `sum` cada vez que `averager` é invocada. Uma +implementação melhor armazenaria apenas o total e a contagem de itens até aquele +momento, e calcularia a média com esses dois números. + +O <> é uma implementação errada, apenas para ilustrar. +Consegue ver onde o código quebra? + +[[ex_average_broken]] +.Função de ordem superior incorreta para calcular uma média cumulativa sem armazenar todo o histórico +==== +[source, python] +---- +def make_averager(): + count = 0 + total = 0 + + def averager(new_value): + count += 1 + total += new_value + return total / count + + return averager +---- +==== + +Ao testar o <>, o resultado é um erro: + +<<< + +[source, python] +---- +>>> avg = make_averager() +>>> avg(10) +Traceback (most recent call last): + ... +UnboundLocalError: local variable 'count' referenced before assignment +>>> +---- + +O problema é que a instrução `count += 1` significa o mesmo que +`count = count + 1`, quando `count` é um número ou qualquer tipo imutável. +Então, estamos realmente atribuindo um valor a `count` no corpo de `averager`, +e isso a torna uma variável local. O mesmo problema afeta a variável `total`. + +Não tivemos esse problema no <>, porque nunca atribuimos nada ao +nome `series`; apenas chamamos `series.append` e invocamos `sum` e `len` nele. +Nos valemos, então, do fato de listas serem mutáveis. +Mas com tipos imutáveis, como números, strings, tuplas, etc., só é possível ler, +nunca atualizar. Se você reatribuir, como em `count += 1`, +estará implicitamente criando uma variável local `count`. Ela não será mais uma +variável livre, e seu valor não será atualizado na clausura. + +A palavra reservada `nonlocal` foi introduzida no Python 3 para contornar esse +problema. Ela permite declarar uma variável livre, mesmo quando +a variável é atribuída dentro da função. Se um novo valor é atribuído a uma variável +`nonlocal`, o valor armazenado na clausura é atualizado. +O <> é a implementação correta da versão otimizada de `make_averager`. + +[[ex_average_fixed]] +.Calcula uma média cumulativa sem armazenar todo o histórico +==== +[source, python] +---- +def make_averager(): + count = 0 + total = 0 + + def averager(new_value): + nonlocal count, total + count += 1 + total += new_value + return total / count + + return averager +---- +==== + +Após estudar o `nonlocal`, podemos resumir como a consulta de variáveis funciona +no Python. + +[[var_lookup_logic_sec]] +==== A lógica do acesso a variáveis + +Quando((("variables", "lookup logic"))) uma função é definida, o compilador de +bytecode de Python determina como encontrar uma variável `x` que aparece na +função, baseado nas seguintes regras:footnote:[Agradeço ao revisor técnico +Leonardo Rochael por sugerir esse resumo.] + +* Se há uma declaração `global x`, então `x` está vinculada à variável global `x` +do módulo.footnote:[Python não tem um escopo global de programa, apenas escopos globais de módulos.] +* Se há uma declaração `nonlocal x`, então `x` está vinculada à variável local `x` na função circundante mais próxima de onde `x` for definida. +* Se `x` é um parâmetro ou tem um valor atribuído a si no corpo da função, então `x` é uma variável local. +* Se `x` é referenciada mas não atribuída, e não é um parâmetro: +** `x` será procurada nos escopos locais do corpos das funções circundantes (os escopos não-locais). +** Se `x` não for encontrada nos escopos circundantes, será lida do escopo global do módulo. +** Se `x` não for encontrada no escopo global, será lida de `+__builtins__.__dict__+`. + +Tendo visto as clausuras de Python, podemos agora implementar decoradores com funções +aninhadas.((("", startref="DACnonlocal09")))((("", startref="nonlocal09")))((("", startref="Knon09"))) + + +=== Implementando um decorador simples + +O <> é((("decorators and closures", "decorator implementation", +id="DACdecimp09"))) um decorador que cronometra cada invocação da função +decorada e exibe o tempo decorrido, os argumentos passados, e o resultado da +chamada. + +<<< + +[[ex_clockdeco0]] +._clockdeco0.py_: decorador que exibe o tempo de execução da função +==== +[source, python] +---- +include::../code/09-closure-deco/clock/clockdeco0.py[] +---- +==== +<1> Define a função interna `clocked` para aceitar qualquer número de argumentos posicionais. +<2> Essa linha só funciona porque a clausura de `clocked` engloba a variável livre `func`. +<3> Devolve a função interna para substituir a função decorada. + +O <> demonstra o uso do decorador `clock`. + +[[ex_clockdeco_demo]] +.Usando o decorador `clock` +==== +[source, python] +---- +include::../code/09-closure-deco/clock/clockdeco_demo.py[] +---- +==== + +O resultado da execução do <> é o seguinte: + +[source] +---- +$ python3 clockdeco_demo.py +**************************************** Calling snooze(.123) +[0.12363791s] snooze(0.123) -> None +**************************************** Calling factorial(6) +[0.00000095s] factorial(1) -> 1 +[0.00002408s] factorial(2) -> 2 +[0.00003934s] factorial(3) -> 6 +[0.00005221s] factorial(4) -> 24 +[0.00006390s] factorial(5) -> 120 +[0.00008297s] factorial(6) -> 720 +6! = 720 +---- + +==== Como isso funciona + +Lembre-se de que esse código: + +[source, python] +---- +@clock +def factorial(n): + return 1 if n < 2 else n*factorial(n-1) +---- + +na verdade faz isso: + +[source, python] +---- +def factorial(n): + return 1 if n < 2 else n*factorial(n-1) + +factorial = clock(factorial) +---- + +Então, nos dois exemplos, `clock` recebe a função `factorial` como seu argumento +`func` (veja o <>). Ela então cria e devolve a função `clocked`, +que o interpretador Python atribui a `factorial` (no primeiro exemplo, por baixo +dos panos). De fato, se você importar o módulo `clockdeco_demo` e verificar o +`+__name__+` de `factorial`, verá isso: + +[source, python] +---- +>>> import clockdeco_demo +>>> clockdeco_demo.factorial.__name__ +'clocked' +---- + +O nome `factorial` agora é uma referência para a função `clocked`. Daqui por +diante, cada vez que `factorial(n)` for invocada, `clocked(n)` será executada. +Essencialmente, `clocked` faz o seguinte: + +. Registra o tempo inicial `t0`. +. Chama a função `factorial` original, salvando o resultado. +. Computa o tempo decorrido. +. Formata e exibe os dados coletados. +. Devolve o resultado salvo no passo 2. + +Esse é o comportamento típico de um decorador: ele substitui a função decorada +com uma nova função que aceita os mesmos argumentos e (normalmente) devolve o +que quer que a função decorada deveria devolver, enquanto realiza também algum +processamento adicional. + +[TIP] +==== +Em _Padrões de Projetos_, de Gamma et al., a descrição curta do padrão decorador +começa assim: "Atribui dinamicamente responsabilidades adicionais a um objeto." +Decoradores de função se encaixam nessa descrição. Mas, no nível da +implementação, os decoradores de Python guardam pouca semelhança com o decorador +clássico descrito no _Padrões de Projetos_ original. +No _<>_ escrevi mais sobre esse assunto. +==== + +O decorador `clock` implementado no <> tem alguns defeitos: ele +não aceita argumentos nomeados, e encobre o `+__name__+` e o `+__doc__+` da +função decorada. + +O <> usa o decorador `functools.wraps` para +copiar os atributos relevantes de `func` para `clocked`. +Nesta nova versão, os argumentos nomeados também são tratados corretamente. + +<<< +[[ex_clockdeco2]] +._clockdeco.py_: um decorador `clock` melhorado +==== +[source, python] +---- +include::../code/09-closure-deco/clock/clockdeco.py[] +---- +==== + +O `functools.wraps` é apenas um dos decoradores prontos para uso da biblioteca +padrão. Na próxima seção, veremos o decorador mais útil oferecido por +`functools`: `cache`.((("", startref="DACdecimp09"))) + + +=== Decoradores na biblioteca padrão + +Python((("decorators and closures", "decorators in Python standard library", +id="DACstandard09"))) tem três funções embutidas projetadas para decorar +métodos: `property`, `classmethod` e `staticmethod`. Vamos discutir `property` +na https://fpy.li/8k[«Seção 22.4»] (vol.3) e os outros na <>. + +No <> vimos outro decorador importante: `functools.wraps`, um +auxiliar na criação de decoradores bem comportados. Três dos decoradores mais +interessantes da biblioteca padrão são `cache`, `lru_cache` e +`singledispatch`—todos do módulo `functools`. Falaremos deles a seguir. + +[[memoization_sec]] +==== Memoização com `functools.cache` + +O((("memoization", id="memoiz09")))((("functools module", "functools.cache +decorator", id="functool09"))) decorador `functools.cache` implementa +_memoização_:footnote:[Esclarecendo, isso não é um erro de ortografia: +https://fpy.li/9-2[_memoization_] é um termo da ciência da computação vagamente +relacionado a "memorização", mas não idêntico.] uma técnica de otimização que +armazena os resultados de invocações de uma função dispendiosa em um _cache_, +evitando repetir o processamento para argumentos previamente utilizados. + +Uma boa demonstração é aplicar `@cache` à função recursiva, e dolorosamente +lenta, que gera o __enésimo__ número da sequência de Fibonacci, como mostra o +<>. + +[[ex_fibo_demo]] +.Algoritmo recursivo e ridiculamente dispendioso para calcular o enésimo número na série de Fibonacci +==== +[source, python] +---- +include::../code/09-closure-deco/fibo_demo.py[] +---- +==== + +Aqui está o resultado da execução de _fibo_demo.py_. Exceto pela última linha, +toda a saída é produzida pelo decorador `clock`: + +[source, text] +---- +$ python3 fibo_demo.py +[0.00000042s] fibonacci(0) -> 0 +[0.00000049s] fibonacci(1) -> 1 +[0.00006115s] fibonacci(2) -> 1 +[0.00000031s] fibonacci(1) -> 1 +[0.00000035s] fibonacci(0) -> 0 +[0.00000030s] fibonacci(1) -> 1 +[0.00001084s] fibonacci(2) -> 1 +[0.00002074s] fibonacci(3) -> 2 +[0.00009189s] fibonacci(4) -> 3 +[0.00000029s] fibonacci(1) -> 1 +[0.00000027s] fibonacci(0) -> 0 +[0.00000029s] fibonacci(1) -> 1 +[0.00000959s] fibonacci(2) -> 1 +[0.00001905s] fibonacci(3) -> 2 +[0.00000026s] fibonacci(0) -> 0 +[0.00000029s] fibonacci(1) -> 1 +[0.00000997s] fibonacci(2) -> 1 +[0.00000028s] fibonacci(1) -> 1 +[0.00000030s] fibonacci(0) -> 0 +[0.00000031s] fibonacci(1) -> 1 +[0.00001019s] fibonacci(2) -> 1 +[0.00001967s] fibonacci(3) -> 2 +[0.00003876s] fibonacci(4) -> 3 +[0.00006670s] fibonacci(5) -> 5 +[0.00016852s] fibonacci(6) -> 8 +8 +---- + +O desperdício é óbvio: `fibonacci(1)` é chamada oito vezes, `fibonacci(2)` cinco vezes, etc. +Mas acrescentar apenas duas linhas, para usar `cache`, melhora muito o desempenho. Veja o <>. + +[[fibo_demo_cache_ex]] +.Implementação mais rápida, usando _caching_ +==== +[source, python] +---- +include::../code/09-closure-deco/fibo_demo_cache.py[] +---- +==== +<1> Essa linha funciona com Python 3.9 ou posterior. +Veja a <> para uma alternativa que suporta versões anteriores de Python. +<2> Esse é um exemplo de decoradores empilhados: +`@cache` é aplicado à função devolvida por `@clock`. + +[[stacked_decorators_tip]] +[TIP] +==== +Para((("stacked decorators"))) entender os decoradores empilhados (_stacked decorators_), +lembre-se de que `@` é açúcar sintático para aplicar a função decoradora à função +abaixo dela. Se houver mais de um decorador, eles se comportam como +invocações aninhadas. + +Este código... + +[source, python] +---- +@alpha +@beta +def my_fn(): + ... +---- + +...faz o mesmo que este: + +[source, python] +---- +my_fn = alpha(beta(my_fn)) +---- + +Em outras palavras, o decorador `beta` é aplicado primeiro, e a função devolvida +por ele é então passada para `alpha`. + +==== + +Usando o `cache` no <>, a função `fibonacci` é chamada +apenas uma vez para cada valor de `n`: + +[source, text] +---- +$ python3 fibo_demo_lru.py +[0.00000043s] fibonacci(0) -> 0 +[0.00000054s] fibonacci(1) -> 1 +[0.00006179s] fibonacci(2) -> 1 +[0.00000070s] fibonacci(3) -> 2 +[0.00007366s] fibonacci(4) -> 3 +[0.00000057s] fibonacci(5) -> 5 +[0.00008479s] fibonacci(6) -> 8 +8 +---- + +Em outro teste, para calcular `fibonacci(30)`, o <> fez as +31 chamadas necessárias em 0,00017s (tempo total), enquanto o <> +sem cache, demorou 12,09s em um notebook Intel Core i7, porque chamou +`fibonacci(1)` 832.040 vezes, num total de 2.692.537 chamadas! + +Todos os argumentos recebidos pela função decorada devem ser _hashable_, +pois o _cache_ usa um `dict` para armazenar os resultados, e as chaves +são formadas pelos argumentos posicionais e nomeados usados nas chamadas. + +Além de tornar viáveis esses algoritmos recursivos tolos, `@cache` brilha de +verdade em aplicações que precisam buscar informações de APIs remotas. + +[WARNING] +==== +O `functools.cache` pode consumir toda a memória disponível, se houver um número +muito grande de itens no cache. Eu o considero mais adequado para scripts +rápidos de linha de comando. Para processos de longa duração, recomendo usar +`functools.lru_cache` com um parâmetro `maxsize` adequado, como explicado na +próxima seção.((("", startref="DACstandard09")))((("", +startref="memoiz09")))((("", startref="functool09"))) +==== + + +[[lru_cache_sec]] +==== Usando o `functools.lru_cache` + +O((("functools module", "functools.lru_cache function"))) decorador +`functools.cache` é, na realidade, um mero invólucro em torno da antiga função +`functools.lru_cache`, que é mais flexível e também compatível com +versões anteriores ao Python 3.9. + +A maior vantagem de `@lru_cache` é a possibilidade de limitar seu uso de memória +através do parâmetro `maxsize`, que tem um default bastante pequeno: 128. +Isso significa que o cache pode armazenar no máximo 128 resultados. + +LRU((("Least Recently Used (LRU)"))) é a sigla de _Least Recently Used_ +("Usado Menos Recentemente"). Significa que registros que há algum +tempo não são lidos, são descartados para dar lugar a novos itens. + +Desde o Python 3.8, `lru_cache` pode ser aplicado de duas formas. +Esta é a forma mais simples: + +[source, python] +---- +@lru_cache +def função_dispendiosa(a, b): + ... +---- + +A outra forma é invocá-lo como uma função, +com `()` (funciona desde o Python 3.2): + +[source, python] +---- +@lru_cache() +def função_dispendiosa(a, b): + ... +---- + +Nos dois casos, os parâmetros default seriam utilizados. +São eles: + +`maxsize=128`:: + Estabelece o número máximo de registros a serem armazenados. + Após o cache estar cheio, o registro menos recentemente usado é descartado, + para dar lugar a cada novo item. + Para um desempenho ótimo, `maxsize` deve ser uma potência de 2. + Se você passar `maxsize=None`, a lógica LRU é desabilitada e o cache funciona mais rápido, + mas os itens nunca são descartados, podendo levar a um consumo excessivo de memória. + É assim que o `@functools.cache` funciona. + +`typed=False`:: + Determina se os resultados de diferentes tipos de argumentos devem ser armazenados separadamente. + Por exemplo, na configuração default, + argumentos inteiros e de ponto flutuante considerados iguais são armazenados apenas uma vez. + Assim, haverá apenas uma entrada para as chamadas `f(1)` e `f(1.0)`. + Se `typed=True`, aqueles argumentos produziriam registros diferentes, + possivelmente armazenando resultados distintos. + +Eis um exemplo de invocação de `@lru_cache` com parâmetros diferentes dos defaults: + +[source, python] +---- +@lru_cache(maxsize=2**20, typed=True) +def costly_function(a, b): + ... +---- + +Vamos agora examinar outro decorador poderoso: `functools.singledispatch`. + +[[single_dispatch_sec]] +==== Funções genéricas com despacho único + +Imagine((("single dispatch generic functions", +id="singlegen09")))((("functions", "single dispatch generic functions", +id="Fsingle09")))((("generic functions, single dispatch", id="genfunc09"))) que +estamos criando uma ferramenta para depurar aplicações web. Queremos gerar +código HTML para tipos diferentes de objetos Python. + +Poderíamos começar com uma função como essa: + +[source, python] +---- +import html + +def htmlize(obj): + content = html.escape(repr(obj)) + return f'
{content}
' +---- + +Isso funcionará para qualquer tipo de objeto, mas agora queremos estender a +função para gerar HTML específico para determinados tipos. Alguns exemplos +seriam: + +`str`:: Substituir os caracteres de mudança de linha na string por `'
\n'` e +usar tags `

` em vez de `

`.
+
+`int`:: Mostrar o número em formato decimal e hexadecimal (com um caso especial
+para `bool`).
+
+`list`:: Gerar uma lista em HTML, formatando cada item de acordo com seu tipo.
+
+`float` e `Decimal`:: Mostrar o valor como de costume, mas também na forma de
+fração (por que não?).
+
+O comportamento que desejamos aparece no <>.
+
+[[singledispatch_demo]]
+.`htmlize()` gera HTML adaptado para diferentes tipos de objetos
+====
+[source, python]
+----
+include::../code/09-closure-deco/htmlizer.py[tags=HTMLIZE_DEMO]
+----
+====
+<1> A função original é registrada para `object`,
+então ela serve para capturar e tratar todos os tipos de argumentos
+que não foram capturados pelas outras implementações.
+<2> Objetos `str` também passam por escape de HTML,
+mas são cercados por `

`, com quebras de linha `
` inseridas antes de cada `'\n'`. +<3> Um `int` é exibido nos formatos decimal e hexadecimal, dentro de um bloco `
`.
+<4> Cada item na lista é formatado de acordo com seu tipo,
+e a sequência inteira é apresentada como uma lista HTML.
+<5> Apesar de ser um subtipo de `int`, `bool` recebe um tratamento especial.
+<6> Mostra `Fraction` como uma fração.
+<7> Mostra `float` e `Decimal` com a fração equivalente aproximada.
+
+Como não temos no Python a sobrecarga de métodos ao estilo de Java, não podemos
+simplesmente criar variações de `htmlize` com assinaturas diferentes para cada
+tipo de dado que queremos tratar de forma distinta. Uma solução possível em
+Python seria transformar `htmlize` em uma função de despacho, com uma cadeia de
+`if/elif/…` ou `match/case/…` chamando funções especializadas como
+`htmlize_str`, `htmlize_int`, etc. Isso não é extensível pelos usuários de nosso
+módulo, e é desajeitado: com o tempo, a função `htmlize` ficaria muito longa,
+e o acoplamento entre ela e as funções especializadas seria forte demais.
+
+O decorador((("functools module", "functools.singledispatch decorator",
+id="functoolssingle09"))) `functools.singledispatch` permite que diferentes
+módulos contribuam para a solução geral, e que você forneça facilmente funções
+especializadas, mesmo para tipos pertencentes a pacotes externos que não possam
+ser editados. Se você decorar uma função simples com `@singledispatch`, ela se
+torna o ponto de entrada para uma _função genérica_: um grupo de funções que
+executam a mesma operação de formas diferentes, dependendo do tipo do primeiro
+argumento. Este é o significado do termo _single dispatch_ (despacho único).
+Se mais argumentos fossem usados para selecionar a função específica,
+teríamos despacho múltiplo (_multiple dispatch_), um recurso nativo em
+linguagens como Common Lisp, Julia e C#.
+
+O <> mostra como implementar o despacho único.
+
+[WARNING]
+====
+`functools.singledispatch` existe desde o Python 3.4, mas só passou a suportar
+a sintaxe com dicas de tipo no Python 3.7.
+As últimas duas funções no <>
+ilustram a sintaxe que funciona em todas as versões de Python desde a 3.4.
+====
+
+
+[[singledispatch_ex]]
+.`@singledispatch` cria uma `@htmlize.register` customizada, para juntar várias funções em uma função genérica
+====
+[source, python]
+----
+include::../code/09-closure-deco/htmlizer.py[tags=HTMLIZE]
+----
+====
+<1> `@singledispatch` marca a função base, que trata o tipo `object`.
+<2> Cada função especializada é decorada com `@«base».register`.
+<3> O tipo do primeiro argumento passado durante a execução determina
+quando essa definição de função em particular será utilizada.
+O nome das funções especializadas é irrelevante;
+`_` é uma boa escolha para deixar isso claro.footnote:[Infelizmente,
+o Mypy 0.770 reclama quando vê múltiplas funções com o mesmo nome.]
+<4> Registra uma nova função para cada tipo que precisa de tratamento especial,
+com uma dica de tipo correspondente no primeiro parâmetro.
+<5> As ABCs em `numbers` são úteis para uso em conjunto com
+`singledispatch`.footnote:[Apesar do alerta em https://fpy.li/8g[«Seção 8.5.7.1»] (vol.1),
+as ABCs de `numbers` não foram descontinuadas, e você as encontra em código de Python 3.]
+<6> `bool` é um _subtipo-de_ `numbers.Integral`,
+mas a lógica de `singledispatch` busca a implementação com o tipo correspondente mais específico,
+independente da ordem na qual eles aparecem no código.
+<7> Se você não quiser ou não puder adicionar dicas de tipo à função decorada,
+você pode passar o tipo para o decorador `@«base».register`.
+Essa sintaxe funciona em Python 3.4 ou posterior.
+<8> O decorador `@«base».register` devolve a função sem decoração,
+então é possível empilhá-los para registrar dois ou mais tipos na mesma
+implementação.footnote:[Talvez algum dia seja possível expressar isso com
+um único `@htmlize.register` sem parâmetros, e uma dica de tipo usando `Union`.
+Mas quando tentei, Python gerou um `TypeError`
+com uma mensagem dizendo que `Union` não é uma classe.
+Então, apesar da _sintaxe_ da PEP 484 ser suportada, a _semântica_ ainda não chegou lá.]
+
+Sempre que possível, registre as funções especializadas para tratar ABCs
+(classes abstratas), como `numbers.Integral` e `abc.MutableSequence`, ao invés
+das implementações concretas como `int` e `list`. Isso permite ao seu código
+suportar uma variedade maior de tipos compatíveis. Por exemplo, uma extensão de
+Python pode fornecer alternativas para o tipo `int` com número fixo de bits como
+subclasses de `numbers.Integral`.footnote:[NumPy, por exemplo, implementa vários
+tipos de https://fpy.li/9-3[números inteiros e de ponto flutuante] (EN) em
+formatos voltados para a arquitetura da máquina.]
+
+[TIP]
+====
+Usar ABCs ou `typing.Protocol` com `@singledispatch` permite que seu código
+suporte classes existentes ou futuras que sejam subclasses reais ou virtuais
+daquelas ABCs, ou que implementem aqueles protocolos. O uso de ABCs e o conceito
+de uma subclasse virtual são assuntos do <>.
+====
+
+Uma qualidade notável do mecanismo de `singledispatch` é que você pode registrar
+funções especializadas em qualquer lugar do sistema, em qualquer módulo. Se mais
+tarde você adicionar um módulo com um novo tipo definido pelo usuário, é fácil
+acrescentar uma nova função específica para tratar aquele tipo. É possível
+também escrever funções customizadas para classes que você não escreveu e não
+pode modificar.
+
+O `singledispatch` foi uma adição muito bem pensada à biblioteca padrão, e
+oferece outras facilidades que não cabem neste livro. Uma boa
+referência é a https://fpy.li/pep443[_PEP 443—Single-dispatch generic functions_]
+mas ela não menciona o uso de dicas de tipo, que foram criadas depois.
+A documentação do módulo `functools` foi melhorada e oferece um tratamento mais
+atualizado, com vários exemplos na seção referente ao
+https://fpy.li/5y[`singledispatch`].
+
+[NOTE]
+====
+
+O `@singledispatch` não foi criado para trazer para Python a sobrecarga de
+métodos no estilo de Java. Uma classe única com muitas variações sobrecarregadas
+de um método é melhor que uma única função com uma longa sequência de blocos
+`if/elif/elif/elif`. Mas as duas soluções concentram responsabilidade demais
+em uma única unidade de código—a classe ou a função.
+A vantagem de `@singledispatch` é seu suporte à extensão modular: cada módulo
+pode registrar uma função especializada para cada tipo suportado. Em um caso de
+uso realista, as implementações das funções genéricas não estariam todas no
+mesmo módulo, como ocorre no <>.
+
+====
+
+Vimos decoradores recebendo argumentos, como `@lru_cache(maxsize=1024)` e o
+`htmlize.register(float)` criado  por `@singledispatch` no
+<>. A próxima seção mostra como criar decoradores com
+parâmetros.((("", startref="singlegen09")))((("", startref="Fsingle09")))((("",
+startref="genfunc09")))((("", startref="functoolssingle09")))
+
+
+[[parameterized_dec_sec]]
+=== Decoradores parametrizados
+
+Ao((("decorators and closures", "parameterized decorators",
+id="DACparam09")))((("parameterized decorators", id="paramdec09"))) analisar um
+decorador no código-fonte, Python passa a função decorada como primeiro
+argumento para a função do decorador. Mas como fazemos um decorador aceitar
+outros argumentos? A resposta é: criar uma fábrica de decoradores que recebe
+aqueles argumentos e devolve um decorador, que é então aplicado à função a ser
+decorada. Complicado? Sim. Mas vamos começar com um exemplo baseado no
+decorador mais simples que vimos: `register` no <>.
+
+[[registration_ex_repeat]]
+.O módulo registration.py resumido, do <>, repetido aqui por conveniência
+====
+[source, python]
+----
+include::../code/09-closure-deco/registration_abridged.py[tags=REGISTRATION_ABRIDGED]
+----
+====
+
+==== Um decorador de registro parametrizado
+
+Para((("registration decorators", id="regdecor09"))) tornar mais fácil a
+habilitação ou desabilitação do registro executado por `register`, faremos esse
+último aceitar um parâmetro opcional `active` que, se `False`, não registra a
+função decorada. Conceitualmente, a nova função `register` não é um decorador,
+mas uma fábrica de decoradores. Quando chamada, ela devolve o decorador que será
+realmente aplicado à função alvo.
+
+[[registration_param_ex]]
+.Para aceitar parâmetros, o novo decorador `register` precisa ser invocado como uma função
+====
+[source, python]
+----
+include::../code/09-closure-deco/registration_param.py[tags=REGISTRATION_PARAM]
+----
+====
+<1> `registry` é agora um `set`, tornando mais rápido acrescentar ou remover funções.
+<2> `register` recebe um argumento nomeado opcional.
+<3> A função interna `decorate` é o verdadeiro decorador; observe como ela aceita uma função como argumento.
+<4> Registra `func` apenas se o argumento `active` (obtido da clausura) for `True`.
+<5> Se `active` é falso, remove a função (sem efeito se a função não está no `set`).
+<6> Como `decorate` é um decorador, tem que devolver uma função.
+<7> `register` é nossa fábrica de decoradores, então devolve `decorate`.
+<8> A fábrica `@register` precisa ser invocada como uma função, com os parâmetros desejados.
+<9> Mesmo se nenhum parâmetro for passado,
+ainda assim `register` deve ser invocada como uma função: `@register()`
+para criar e devolver o verdadeiro decorador, `decorate`.
+
+O ponto central aqui é que `register()` devolve `decorate`, que então é aplicado
+à função decorada.
+
+O código do <> está em um módulo _registration_param.py_.
+Se o importarmos, veremos o seguinte:
+
+[source, python]
+----
+>>> import registration_param
+running register(active=False)->decorate()
+running register(active=True)->decorate()
+>>> registration_param.registry
+[]
+----
+
+Veja como apenas a função `f2` aparece no `registry`; `f1` não aparece porque
+`active=False` foi passado para a fábrica de decoradores `register`, então o
+`decorate` aplicado a  `f1` não adiciona essa função a `registry`.
+
+Se, ao invés de usar a sintaxe `@`, usarmos `register` como uma função regular,
+a sintaxe necessária para decorar uma função `f` seria `register()(f)`, para
+inserir `f` ao `registry`, ou `register(active=False)(f)`, para não inseri-la
+(ou removê-la). Veja o <> para uma demonstração da
+adição e remoção de funções do `registry`.
+
+[[registration_param_demo]]
+.Usando o módulo registration_param listado no <>
+====
+[source, python]
+----
+>>> from registration_param import *
+running register(active=False)->decorate()
+running register(active=True)->decorate()
+>>> registry  # <1>
+{}
+>>> register()(f3)  # <2>
+running register(active=True)->decorate()
+
+>>> registry  # <3>
+{, }
+>>> register(active=False)(f2)  # <4>
+running register(active=False)->decorate()
+
+>>> registry  # <5>
+{}
+----
+====
+<1> Quando o módulo é importado, `f2` é inserida no `registry`.
+<2> A expressão `register()` devolve `decorate`, que então é aplicado a `f3`.
+<3> A linha anterior adicionou `f3` ao `registry`.
+<4> Essa chamada remove `f2` do `registry`.
+<5> Confirma que apenas `f3` permanece no `registry`.
+
+O funcionamento de decoradores parametrizados é bastante complexo, e esse que
+acabamos de discutir é mais simples que a maioria. Decoradores parametrizados em
+geral substituem a função decorada, e sua construção exige um nível adicional de
+aninhamento. Vamos agora explorar a arquitetura de uma dessas pirâmides de
+funções.((("", startref="regdecor09")))
+
+
+==== Um decorador parametrizado de cronometragem
+
+Nesta((("clock decorators", "parameterized", id="CDparam09"))) seção vamos
+revisitar o decorador `clock`, acrescentando um recurso: os usuários podem
+passar uma string para formatar o relatório sobre a função cronometrada.
+Veja o <>.
+
+[NOTE]
+====
+Para simplificar, o <> está baseado na implementação inicial
+de `clock` no <>, e não na versão melhorada do
+<> que usa `@functools.wraps`,
+evitando mais um nível de aninhamento de funções.
+====
+
+<<<
+
+[[clockdeco_param_ex]]
+.Módulo clockdeco_param.py: o decorador `clock` parametrizado
+====
+[source, python]
+----
+include::../code/09-closure-deco/clock/clockdeco_param.py[tags=CLOCKDECO_PARAM]
+----
+====
+<1> Formato padrão da saída citando variáveis locais da função `clocked`.
+<2> `clock` é a nossa fábrica de decoradores parametrizados.
+<3> `decorate` é o verdadeiro decorador.
+<4> `clocked` envolve a função decorada.
+<5> `_result` é o resultado real da função decorada.
+<6> `_args` armazena os verdadeiros argumentos de `clocked`, enquanto `args` é a `str` usada para exibição.
+<7> `result` é a `str` que representa `_result`, para uso no formato.
+<8> Usar `**locals()` aqui permite que qualquer variável local de `clocked` seja
+referenciada em `fmt`.footnote:[O revisor técnico Miroslav Šedivý observou:
+"Isso também quer dizer que analisadores de código-fonte (_linters_) vão
+reclamar de variáveis não utilizadas, pois eles tendem a ignorar o uso de
+`locals()`." Sim, esse é mais um exemplo de como ferramentas estáticas de
+checagem desencorajam o uso dos recursos dinâmicos de Python que me
+atraíram (e a incontáveis outros programadores) quando adotei a linguagem.
+Para deixar o _linter_ feliz, eu poderia escrever o nome de cada variável duas vezes na chamada:
+`fmt.format(elapsed=elapsed, name=name, args=args, result=result)`.
+Prefiro não fazer isso. Se você usa ferramentas
+estáticas de checagem, é importante saber quando ignorá-las.]
+<9> `clocked` vai substituir a função decorada, então ela deve devolver o mesmo que aquela função devolve.
+<10> `decorate` devolve `clocked`.
+<11> `clock` devolve `decorate`.
+<12> Nesse auto-teste, `clock()` é chamado sem argumentos,
+então o decorador aplicado usará o formato default, `str`.
+
+Se você rodar o <> no console, o resultado é o seguinte:
+
+[source]
+----
+$ python3 clockdeco_param.py
+[0.12412500s] snooze(0.123) -> None
+[0.12411904s] snooze(0.123) -> None
+[0.12410498s] snooze(0.123) -> None
+----
+
+Para exercitar a nova funcionalidade, veremos mais dois
+módulos que usam o `clockdeco_param`,
+o <<#ex_clockdecoparam_demo1>> e o
+<<#ex_clockdecoparam_demo2>>, e as saídas que eles produzem.
+
+[[ex_clockdecoparam_demo1]]
+.clockdeco_param_demo1.py
+====
+[source, python]
+----
+include::../code/09-closure-deco/clock/clockdeco_param_demo1.py[]
+----
+====
+
+<<<
+Saída do <>:
+
+[source]
+----
+$ python3 clockdeco_param_demo1.py
+snooze: 0.12414693832397461s
+snooze: 0.1241159439086914s
+snooze: 0.12412118911743164s
+----
+
+[[ex_clockdecoparam_demo2]]
+.clockdeco_param_demo2.py
+====
+[source, python]
+----
+include::../code/09-closure-deco/clock/clockdeco_param_demo2.py[]
+----
+====
+
+Saída do <>:
+
+[source]
+----
+$ python3 clockdeco_param_demo2.py
+snooze(0.123) dt=0.124s
+snooze(0.123) dt=0.124s
+snooze(0.123) dt=0.124s
+----
+
+[NOTE]
+====
+
+Lennart Regebro—um dos revisores técnicos da primeira edição—argumenta que seria
+melhor programar decoradores como classes implementando `+__call__+`, e não como
+funções, como os exemplos neste capítulo. Concordo que aquela abordagem é
+melhor para decoradores não-triviais. Mas para explicar a ideia básica desse
+recurso da linguagem, funções são mais fáceis de entender.
+Para conhecer técnicas mais robustas de criação de decoradores,
+veja as referências na <>, especialmente o blog
+de Graham Dumpleton e o módulo `wrapt`.
+
+====
+
+Agora veremos um exemplo no estilo recomendado por Regebro e Dumpleton.((("", startref="CDparam09")))
+
+==== Um decorador de cronometragem em forma de classe
+
+Como((("clock decorators", "class-based"))) um último exemplo,
+o <> mostra a implementação de um decorador parametrizado `clock`,
+programado como uma classe com `+__call__+`.
+Compare o <> com o <>.
+Qual você prefere?
+
+[[clockdeco_param_cls_ex]]
+.Módulo clockdeco_cls.py: decorador parametrizado `clock`, implementado como uma classe
+====
+[source, python]
+----
+include::../code/09-closure-deco/clock/clockdeco_cls.py[tags=CLOCKDECO_CLS]
+----
+====
+<1> Ao invés de uma função externa `clock`, a classe `clock` é nossa fábrica de
+decoradores parametrizados. Escrevi `clock` com `c` minúsculo para deixar claro que
+essa implementação substitui exatamente a função `clock` no
+<>.
+<2> O argumento passado em `clock(my_format)` é
+atribuído ao parâmetro `fmt` aqui. O construtor da classe devolve uma instância
+de `clock`, com `my_format` armazenado em `self.fmt`.
+<3> `+__call__+` torna a
+instância de `clock` invocável. Quando chamada, a instância substitui a função
+decorada com `clocked`.
+<4> `clocked` envolve a função decorada.
+
+Isso encerra nossa exploração dos decoradores de função. Veremos os decoradores
+de classe no https://fpy.li/24[«Capítulo 24»] (vol.3).((("", startref="DACparam09")))((("",
+startref="paramdec09")))
+
+
+=== Resumo do capítulo
+
+Percorremos((("decorators and closures", "overview of"))) um terreno difícil
+neste capítulo. Tentei tornar a jornada tão suave quanto possível, mas entramos
+nos domínios da meta-programação, onde nada é simples.
+
+Partimos de um decorador simples `@register`, sem uma função interna, e
+terminamos com um `@clock()` parametrizado envolvendo dois níveis de funções
+aninhadas.
+
+Decoradores de registro, apesar de serem essencialmente simples, têm aplicações
+reais nos frameworks Python. Vamos aplicar a ideia de registro em uma
+implementação do padrão de projeto Estratégia, no <>.
+
+Entender como os decoradores realmente funcionam exigiu falar da diferença entre
+_momento de importação_ e _momento de execução_. Então mergulhamos no escopo de
+variáveis, clausuras e a nova declaração `nonlocal`. Dominar as clausuras e
+`nonlocal` é valioso não apenas para criar decoradores, mas também para escrever
+programas orientados a eventos para GUIs ou E/S assíncrona com _callbacks_, e
+para adotar um estilo funcional quando fizer sentido.
+
+Decoradores parametrizados quase sempre implicam em pelo menos dois níveis de
+funções aninhadas, talvez mais se você quiser usar `@functools.wraps`, e
+produzir um decorador com um suporte melhor a técnicas mais avançadas. Uma
+dessas técnicas é o empilhamento de decoradores, que vimos no
+<>. Para decoradores mais sofisticados, uma implementação
+baseada em classes pode ser mais fácil de ler e manter.
+
+Como exemplos de decoradores parametrizados na biblioteca padrão, visitamos os
+poderosos `@cache` e `@singledispatch`, do módulo `functools`.
+
+<<<
+[[decorator_further]]
+=== Para saber mais
+
+O((("decorators and closures", "further reading on"))) item #26 do livro
+https://fpy.li/effectpy[_Effective Python_, 2nd ed.] (Addison-Wesley), de
+Brett Slatkin, trata das melhores práticas para decoradores de função, e
+recomenda sempre usar `functools.wraps`—que vimos no
+<>. Como eu queria manter o código o mais simples possível,
+não segui o excelente conselho de Slatkin em todos os exemplos.
+
+Graham Dumpleton tem, em seu blog, uma https://fpy.li/9-5[série de posts
+abrangentes] sobre técnicas para implementar decoradores bem comportados,
+começando com https://fpy.li/9-6[_How you implemented your Python decorator is
+wrong_ (A forma como você implementou seu decorador em Python está errada)].
+Seus conhecimentos sobre o tema também aparecem
+no módulo https://fpy.li/9-7[`wrapt`], que ele escreveu para simplificar a
+implementação de decoradores e invólucros (_wrappers_) dinâmicos de função,
+que suportam introspecção e se comportam de forma correta quando decorados
+novamente, quando aplicados a métodos e quando usados como descritores de
+atributos (o https://fpy.li/23[«Capítulo 23»] (vol.3) é sobre descritores).
+
+https://fpy.li/9-8[_Metaprogramming_], o capítulo 9 do
+_Python Cookbook_, 3ª ed. de David Beazley e Brian K. Jones (O'Reilly), tem
+várias receitas ilustrando desde decoradores elementares até alguns muito
+sofisticados, incluindo um que pode ser invocado como um decorador regular ou
+como uma fábrica de decoradores, por exemplo, `@clock` ou `@clock()`. É a
+_Recipe 9.6. Defining a Decorator That Takes an Optional Argument_
+(Definindo um Decorador Que Recebe um Argumento Opcional) daquele livro de
+receitas.
+
+Michele Simionato criou https://fpy.li/9-9[_decorator_],
+um pacote para "simplificar o uso de decoradores para o programador comum,
+e popularizar os decoradores através da apresentação de vários exemplos
+não-triviais", de acordo com sua documentação.
+
+Criada quando os decoradores ainda eram um recurso novo no Python, a página wiki
+https://fpy.li/9-10[_Python Decorator Library_] tem dezenas de exemplos. Como
+começou há muitos anos, algumas das técnicas apresentadas foram suplantadas, mas
+ela ainda é uma excelente fonte de inspiração.
+
+https://fpy.li/9-11[_Closures in Python_] é um post curto de Fredrik Lundh,
+explicando a terminologia das clausuras.
+
+<<<
+A https://fpy.li/9-12[_PEP 3104—Access to Names in Outer Scopes_] (Acesso a Nomes
+em Escopos Externos) descreve a introdução da declaração `nonlocal`.
+Ela também inclui uma excelente revisão de como essa questão foi resolvida
+em outras linguagens dinâmicas (Perl, Ruby, JavaScript, etc.)
+e os prós e contras das opções de design disponíveis para Python.
+
+Em um nível mais teórico, a
+https://fpy.li/9-13[_PEP 227—Statically Nested Scopes_]
+(Escopos estaticamente Aninhados_) documenta a introdução do
+escopo léxico como um opção no Python 2.1 e como padrão no Python 2.2,
+explicando a justificativa e as opções de design para a implementação de
+clausuras no Python.
+
+A https://fpy.li/9-14[_PEP 443_] traz a justificativa e uma descrição
+detalhada do mecanismo de funções genéricas de despacho único. Um post de Guido
+van Rossum de março de 2005
+https://fpy.li/9-15[_Five-Minute Multimethods in Python_]
+(Multi-métodos de cinco minutos em Python), mostra os passos
+para uma implementação de funções genéricas (também chamadas multi-métodos)
+usando decoradores. O código de multi-métodos de Guido é interessante, mas é
+apenas um exemplo didático. Para conhecer uma implementação de funções genéricas
+de despacho múltiplo moderna e pronta para uso em produção, veja a
+https://fpy.li/9-16[_Reg_] de Martijn Faassen–autor de
+https://fpy.li/9-17[_Morepath_], um framework web guiado por modelos
+e orientado a REST.
+
+[[closures_soapbox]]
+.Ponto de vista
+****
+
+[role="soapbox-title"]
+**Escopo dinâmico versus escopo léxico**
+
+O((("Soapbox sidebars", "dynamic scope versus lexical scope",
+id="SSdynamic09")))((("decorators and closures", "Soapbox discussion",
+id="DACsoap09")))((("scope", "dynamic scope versus lexical scope",
+id="Scynamic09"))) projetista de qualquer linguagem que contenha funções de
+primeira classe se depara com essa questão: sendo um objeto de primeira classe,
+uma função é definida dentro de um determinado escopo, mas pode ser invocada em
+outros escopos. O problema é: como avaliar as variáveis livres? A solução
+mais simples de implementar chama-se "escopo dinâmico".
+Isso significa que variáveis livres são avaliadas olhando para dentro
+do ambiente onde a função é invocada.
+
+Se Python tivesse escopo dinâmico e não tivesse clausuras, poderíamos improvisar
+`avg` (similar ao <>) desta forma:
+
+<<<
+
+[source, python]
+----
+>>> ### esta não é uma sessão real de Python! ###
+>>> avg = make_averager()
+>>> series = []  # <1>
+>>> avg(10)
+10.0
+>>> avg(11)  # <2>
+10.5
+>>> avg(12)
+11.0
+>>> series = [1]  # <3>
+>>> avg(5)
+3.0
+----
+<1> Antes de usar `avg`, precisamos definir por nós mesmos `series = []`,
+então precisamos saber que `averager` (dentro de `make_averager`)
+se refere a uma lista chamada `series`.
+<2> Por trás da cortina, `series` acumula os valores cuja média será calculada.
+<3> Quando `series = [1]` é executada, a lista anterior é perdida.
+Isso poderia ocorrer por acidente,
+ao computar duas médias cumulativas independentes ao mesmo tempo.
+
+O ideal é que funções sejam opacas, sua implementação invisível para os usuários.
+Mas com escopo dinâmico, se a função usa variáveis livres, o programador precisa
+saber do funcionamento interno da função, para poder preparar um
+ambiente onde ela execute corretamente. Após anos lutando com a linguagem de
+preparação de documentos LaTeX, o excelente livro _Practical LaTeX_ (LaTeX
+Prático), de George Grätzer (Springer), me ensinou que as variáveis no LaTeX
+usam escopo dinâmico. Por isso me confundiam tanto!
+
+O Lisp do Emacs também usa escopo dinâmico, pelo menos como default. Veja
+https://fpy.li/9-18[_Dynamic Binding_] (Vinculação Dinâmica) no manual do
+Emacs para uma breve explicação.
+
+O escopo dinâmico é mais fácil de implementar, e essa foi provavelmente a razão
+de John McCarthy ter tomado esse caminho quando criou o Lisp, a primeira
+linguagem a ter funções de primeira classe. O texto de Paul Graham,
+https://fpy.li/9-19[_The Roots of Lisp_] (As Raízes do Lisp) é uma explicação
+acessível do artigo original de John McCarthy sobre a linguagem Lisp,
+https://fpy.li/9-20[_Recursive Functions of Symbolic Expressions and Their Computation by
+Machine, Part I_] (Funções Recursivas de Expressões Simbólicas e Sua
+Computação por Máquina). O artigo de McCarthy é uma obra prima no
+nível da Nona Sinfonia de Beethoven. Paul Graham o traduziu
+do jargão matemático para um inglês mais compreensível e código executável.
+
+O comentário de Paul Graham explica como o escopo dinâmico é complexo. Citando
+_The Roots of Lisp_:
+
+[quote]
+____
+É um testemunho eloquente dos perigos do escopo dinâmico, que mesmo o primeiro
+exemplo de funções de ordem superior em Lisp estivesse errado por causa dele.
+Talvez, em 1960, McCarthy não estivesse inteiramente ciente das implicações do
+escopo dinâmico, que continuou presente nas implementações de Lisp por um tempo
+surpreendentemente longo—até Sussman e Steele desenvolverem o Scheme, em 1975. O
+escopo léxico não complica demais a definição de `eval`, mas pode tornar mais
+difícil escrever compiladores.
+____
+
+Atualmente, o escopo léxico é o padrão: variáveis livres são avaliadas
+considerando o ambiente onde a função foi definida. O escopo léxico complica a
+implementação de linguagens com funções de primeira classe, pois requer o
+suporte a clausuras. Por outro lado, o escopo léxico torna o código-fonte mais
+fácil de ler. A maioria das linguagens inventadas desde o Algol tem escopo
+léxico. Uma exceção notável é o JavaScript, onde a variável especial `this` é
+confusa, pois pode ter escopo léxico ou dinâmico, https://fpy.li/9-21[dependendo
+da forma como o código for escrito] (EN).
+
+Por muitos anos, o `lambda` de Python não implementava clausuras, contribuindo para
+a má fama deste recurso entre os fãs da programação funcional na blogosfera.
+Isso foi resolvido no Python 2.2 (de dezembro de 2001), mas a blogosfera nunca perdoa.
+Desde então, `lambda` é triste apenas devido à sua sintaxe
+limitada.((("", startref="Scynamic09")))((("", startref="SSdynamic09")))
+
+****
+<<<
+
+****
+**Os decoradores de Python e o padrão de projeto Decorator**
+
+Os decoradores de função((("Soapbox sidebars", "Python decorators and decorator
+design pattern"))) de Python se encaixam na descrição geral dos decoradores de
+Gamma et al. em _Padrões de Projeto_: "Acrescenta responsabilidades adicionais a
+um objeto de forma dinâmica. Decoradores fornecem uma alternativa flexível à
+criação de subclasses para estender funcionalidade."
+
+Ao nível da implementação, os decoradores de Python não lembram o padrão de
+projeto decorador clássico, mas é possível fazer uma analogia.
+No padrão de projeto, `Decorador` e `Componente` são classes abstratas. Uma
+instância de um decorador concreto envolve uma instância de um componente
+concreto para adicionar comportamentos a ela. Citando _Padrões de Projeto_:
+
+[quote]
+____
+
+O decorador se adapta à interface do componente decorado, assim sua presença é
+transparente para os clientes do componente. O decorador encaminha requisições
+para o componente e pode executar ações adicionais (tal como desenhar uma borda)
+antes ou depois do encaminhamento. A transparência permite aninhar decoradores
+de forma recursiva, possibilitando assim um número ilimitado de
+responsabilidades adicionais. (p. 175 da edição em inglês)
+____
+
+
+No Python, a função decoradora faz o papel de uma subclasse concreta de
+`Decorador`, e a função interna que ela devolve é uma instância do decorador. A
+função devolvida envolve a função a ser decorada, que é análoga ao componente no
+padrão de projeto. A função devolvida é transparente, pois se adapta à interface
+do componente (ao aceitar os mesmos argumentos). Adaptando a última frase
+da citação: "A transparência permite
+empilhar decoradores, possibilitando assim um número ilimitado de comportamentos
+adicionais".
+
+Veja que não estou sugerindo que decoradores de função devam ser usados para
+implementar o padrão decorador em programas Python. Pode até ser feito em
+situações específicas, mas em geral o padrão decorador é melhor implementado
+com classes representando o decorador e os componentes que ela vai envolver.((("",
+startref="DACsoap09")))
+
+****
+
+<<<
\ No newline at end of file
diff --git a/vol2/cap10.adoc b/vol2/cap10.adoc
new file mode 100644
index 0000000..94dde48
--- /dev/null
+++ b/vol2/cap10.adoc
@@ -0,0 +1,733 @@
+[[ch_design_patterns]]
+== Padrões de projetos com funções de primeira classe
+:example-number: 0
+:figure-number: 0
+
+[quote, Ralph Johnson, co-autor do clássico "Padrões de Projetos"]
+____
+Conformidade a padrões não é medida de virtude.footnote:[De um slide na
+palestra _Root Cause Analysis of Some Faults in Design Patterns_ (Análise das
+Causas Básicas de Alguns Defeitos em Padrões de Projetos), apresentada por
+Ralph Johnson no IME/CCSL da Universidade de São Paulo, em 15 de novembro de
+2014.]
+____
+
+Em((("functions, design patterns with first-class", "dynamic languages and")))
+engenharia de software, um
+https://fpy.li/5z[_padrão de projeto_] é uma receita genérica para solucionar
+um problema de design comum.
+Não é preciso conhecer padrões de projeto para acompanhar esse
+capítulo, vou explicar os padrões usados nos exemplos.
+
+O uso de padrões de projeto em programação foi popularizado pelo livro seminal
+_Padrões de Projetos: Soluções Reutilizáveis de Software Orientados a Objetos_
+(Addison-Wesley), de Erich Gamma, Richard Helm, Ralph Johnson e John
+Vlissides—também conhecidos como _the Gang of Four_ (A Gangue dos Quatro) ou pela
+sigla _GoF_. O
+livro é um catálogo de 23 padrões, aprensentados como arranjos de
+classes e exemplificados com código em {cpp}, mas considerados úteis
+também em outras linguagens orientadas a objetos.
+
+Apesar dos padrões de projeto serem independentes da linguagem, isso não
+significa que todo padrão se aplica a todas as linguagens. Por exemplo, o
+https://fpy.li/17[«Capítulo 17»] (vol.3) vai mostrar que não faz sentido implementr a receita do padrão
+https://fpy.li/10-2[Iterador (_Iterator_)] em Python, pois esse padrão está
+embutido na linguagem e pronto para ser usado, na forma de geradores—que não
+precisam de classes para funcionar, e exigem menos código que a receita
+do livro clássico.
+
+Na introdução da obra, os autores reconhecem que a linguagem
+usada na implementação determina quais padrões são relevantes:
+
+[quote]
+____
+A escolha da linguagem de programação é importante, pois ela influencia nosso
+ponto de vista. Nossos padrões supõe uma linguagem com recursos equivalentes aos
+de Smalltalk e do {cpp}—e essa escolha determina o que pode e o que não pode ser
+facilmente implementado. Se tivéssemos presumido uma linguagem procedural,
+poderíamos ter incluído padrões de projetos chamados "Herança", "Encapsulamento"
+e "Polimorfismo". Da mesma forma, alguns de nossos padrões são suportados
+diretamente por linguagens orientadas a objetos menos conhecidas. CLOS, por
+exemplo, tem multi-métodos, reduzindo a necessidade de um padrão como o
+Visitante.footnote:[_Visitor_, Citado da página 4 da edição em inglês de
+_Padrões de Projeto_.]
+____
+
+Em sua apresentação de 1996, https://fpy.li/norvigdp[_Design Patterns in Dynamic
+Languages_] (Padrões de Projetos em Linguagens Dinâmicas), Peter Norvig
+afirma que 16 dos 23 padrões do livro original se tornam
+"invisíveis ou mais simples" em uma linguagem dinâmica (slide 9). Ele se
+refere às linguagens Lisp e Dylan, mas vários recursos dinâmicos citados
+também existem em Python. Em especial, em uma linguagem que oferece
+funções de primeira classe, Norvig sugere repensar os padrões
+clássicos conhecidos como _Strategy_ (Estratégia), _Command_ (Comando), 
+_Template Method_ (Método Gabarito) e _Visitor_ (Visitante).
+
+O objetivo desse capítulo é mostrar como, em certos casos, funções podem
+realizar o mesmo trabalho de classes, com menos código e mais clareza.
+Vamos refatorar uma implementaçao de Estratégia usando funções como
+objetos, removendo muito código redundante.
+Vamos também discutir uma abordagem similar para simplificar o padrão Comando.
+
+=== Novidades neste capítulo
+
+Movi((("functions, design patterns with first-class", "significant changes to")))
+este capítulo para o final da Parte II, para poder então aplicar o
+decorador de registro na <>, e também usar dicas de tipo nos
+exemplos. A maior parte das dicas de tipo usadas nesse capítulo são simples,
+e ajudam na legibilidade.
+
+[[strategy_case_study]]
+=== Estudo de caso: refatorando Estratégia
+
+Estratégia((("functions, design patterns with first-class", "refactoring
+strategies", id="FDPrefactor10"))) é um bom exemplo de um padrão de projeto que
+pode ser mais simples em Python, usando funções como objetos de primeira classe.
+Na próxima seção vamos descrever e implementar Estratégia usando a estrutura
+"clássica" descrita em _Padrões de Projetos_. Se você estiver familiarizado com
+o padrão original, pode pular direto para <>, onde
+refatoramos o código usando funções, eliminando várias linhas.
+
+==== Estratégia clássica
+
+O((("refactoring strategies", "classic", id="RSclassic10")))((("classic refactoring strategy",
+id="classicref10")))((("Strategy pattern", id="stratpat10")))((("UML class diagrams",
+"Strategy design pattern"))) diagrama
+de classes UML na <> retrata um arranjo de classes exemplificando
+o padrão Estratégia.
+
+[[strategy_uml]]
+.Diagrama de classes UML para calcular descontos em pedidos, com o padrão de projeto Estratégia.
+image::../images/flpy_1001.png[align="center",pdfwidth=10cm]
+
+O padrão Estratégia é resumido assim em _Padrões de Projetos_:
+
+[quote]
+____
+Define uma família de algoritmos, encapsula cada um deles, e os torna
+intercambiáveis. Estratégia permite que o algoritmo varie de forma independente
+dos clientes que o usam.
+____
+
+Um exemplo claro de Estratégia, aplicado ao domínio do ecommerce, é o cálculo de
+descontos em pedidos de acordo com os atributos do cliente ou pela inspeção dos
+itens do pedido.
+
+Considere uma loja online com as seguintes regras para descontos:
+
+* Clientes com 1.000 ou mais pontos de fidelidade recebem um desconto global de 5% por pedido.
+* Um desconto de 10% é aplicado a cada item com 20 ou mais unidades no mesmo pedido.
+* Pedidos com 10 ou mais itens diferentes têm um desconto global de 7%.
+
+Para simplificar, vamos assumir que apenas um desconto pode ser aplicado a cada pedido.
+
+O diagrama de classes UML para o padrão Estratégia aparece na <>. Seus participantes são:
+
+Contexto (_Context_):: Oferece um serviço delegando parte do processamento para
+componentes intercambiáveis, que implementam algoritmos alternativos.
+Neste exemplo, o contexto é uma classe `Order`, configurada para aplicar um
+desconto promocional de acordo com um algoritmo entre vários possíveis.
+
+Estratégia (_Strategy_):: A interface comum dos componentes que implementam
+diferentes algoritmos. No nosso exemplo, esse papel cabe a uma classe abstrata
+chamada `Promotion`.
+
+Estratégia concreta (_Concrete strategy_):: Cada uma das subclasses concretas de
+Estratégia. `FidelityPromo`, `BulkPromo`, e `LargeOrderPromo` são as três
+estratégias concretas implementadas.
+
+O código no <> segue o modelo da <>. Como
+descrito em _Padrões de Projetos_, a estratégia concreta é escolhida pelo
+cliente da classe de contexto. No nosso exemplo, antes de instanciar um pedido,
+o sistema deveria, de alguma forma, selecionar a estratégia de desconto
+promocional e passá-la para o construtor de `Order`. A seleção da estratégia
+está fora do escopo do padrão.
+
+[[ex_classic_strategy]]
+.Implementação da classe `Order` com estratégias de desconto intercambiáveis
+====
+[source, python]
+----
+include::../code/10-dp-1class-func/classic_strategy.py[tags=CLASSIC_STRATEGY]
+----
+====
+
+Observe que no  <>, programei `Promotion` como uma classe
+base abstrata (ABC), para usar o decorador `@abstractmethod` e deixar o padrão
+mais explícito.
+
+O <> apresenta os doctests usados para demonstrar e
+verificar a operação de um módulo implementando as regras descritas
+anteriormente.
+
+[[ex_classic_strategy_tests]]
+.Usos da classe `Order` com a aplicação de diferentes promoções
+====
+[source, python]
+----
+include::../code/10-dp-1class-func/classic_strategy.py[tags=CLASSIC_STRATEGY_TESTS]
+----
+====
+<1> Dois clientes: `joe` tem 0 pontos de fidelidade, `ann` tem 1.100.
+<2> Um carrinho de compras com três itens.
+<3> A promoção `FidelityPromo` não dá qualquer desconto para `joe`.
+<4> `ann` recebe um desconto de 5% porque tem pelo menos 1.000 pontos.
+<5> O `banana_cart` contém 30 unidade do produto `"banana"` e 10 maçãs.
+<6> Graças à `BulkItemPromo`, `joe` recebe um desconto de $1,50 no preço das bananas.
+<7> O `long_cart` tem 10 itens diferentes, cada um custando $1,00.
+<8> `joe` recebe um desconto de 7% no pedido total, por causa da `LargerOrderPromo`.
+
+O <> funciona, mas podemos implementar a mesma funcionalidade
+com menos linhas de código usando funções como objetos.
+Vejamos como.((("", startref="stratpat10")))((("", startref="classicref10")))((("",
+startref="RSclassic10")))
+
+[[pythonic_strategy]]
+==== Estratégia baseada em funções
+
+Cada((("refactoring strategies", "function-oriented",
+id="RSfunction10")))((("function-oriented refactoring strategy",
+id="funcorient01"))) estratégia concreta no <> é uma classe
+com um método: `discount`. Além disso, as instâncias de estratégia não tem
+estado (nenhum atributo de instância). Você poderia dizer que elas se
+parecem muito com funções simples, e estaria certa. O <> é uma
+refatoração do <>, trocando as estratégias concretas
+por funções simples e removendo a ABC `Promo`. São necessários
+apenas alguns pequenos ajustes na classe `Order`.footnote:[Precisei
+reimplementar `Order` com `@dataclass` devido a um bug no Mypy. Você pode
+ignorar esse detalhe, pois essa classe funciona também com `NamedTuple`,
+exatamente como no <>. Quando `Order` é uma `NamedTuple`, o
+Mypy 0.910 encerra com erro ao checar a dica de tipo para `promotion`. Tentei
+acrescentar `# type ignore` àquela linha específica, mas o erro persistia.
+Entretanto, se `Order` for criada com `@dataclass`, o Mypy trata corretamente a
+mesma dica de tipo. O https://fpy.li/10-3[Issue #9397] não havia sido resolvido
+em 19 de julho de 2021, quando essa nota foi escrita. Espero que o problema
+tenha sido solucionado quando você estiver lendo isso. NT: Aparentemente foi
+resolvido. O Issue #9397 gerou o
+https://fpy.li/62[Issue #12629], fechado com indicação
+de solucionado em agosto de 2022, o último comentário indicando que a opção de
+linha de comando `--enable-recursive-aliases` do Mypy evita os erros
+relatados).]
+
+[[ex_strategy]]
+.A classe `Order` com as estratégias implementadas como funções
+====
+[source, python]
+----
+include::../code/10-dp-1class-func/strategy.py[tags=STRATEGY]
+----
+====
+
+<1> Essa dica de tipo diz: `promotion` pode ser `None`, ou pode ser um invocável
+que recebe uma `Order` como argumento e devolve um `Decimal`.
+<2> Para calcular o desconto, invocamos `self.promotion`,
+passando `self` como argumento. Veja a razão disso logo abaixo.
+<3> Nenhuma classe abstrata.
+<4> Cada estratégia é uma função.
+
+.Por que `self.promotion(self)`?
+[TIP]
+====
+Na classe `Order`, `promotion` não é um método. É um atributo de instância que
+por acaso é invocável. Então a primeira parte da expressão, `self.promotion`,
+busca aquele invocável. Mas, ao invocá-lo, precisamos fornecer uma instância de
+`Order`, que neste caso é `self`. Por isso `self` aparece duas vezes na
+expressão.
+
+A https://fpy.li/8e[«Seção 23.4»] (vol.3) vai explicar o mecanismo que vincula
+automaticamente métodos a instâncias. Mas isso não se aplica a `promotion`,
+pois este atributo não é um método.
+====
+
+O código no <> é mais curto que o do <>. Usar
+a nova `Order` é também um pouco mais simples, como mostram os doctests no
+<>.
+
+[[ex_strategy_tests]]
+.Amostra do uso da classe `Order` com as promoções como funções
+====
+[source, python]
+----
+include::../code/10-dp-1class-func/strategy.py[tags=STRATEGY_TESTS]
+----
+====
+<1> Mesmos dispositivos de teste do <>.
+<2> Para aplicar uma estratégia de desconto a uma `Order`, passamos a função de promoção como argumento.
+<3> Uma função de promoção diferente é usada aqui e no teste seguinte.
+
+
+Note que não precisamos criar uma nova instância de `promotion` a
+cada novo pedido: as funções já estão prontas para usar.
+
+É interessante notar que no _Padrões de Projetos_, os autores sugerem que:
+"Objetos Estratégia muitas vezes são bons "peso mosca"
+(_flyweight_)".footnote:[veja a página 323 da edição em inglês de _Padrões de
+Projetos_.] O padrão _Peso Mosca_ é definido em outra parte do livro
+assim: "Um _peso mosca_ é um objeto compartilhado que pode ser usado em
+múltiplos contextos simultaneamente."footnote:[Ibid., p. 196.]
+O compartilhamento é recomendado para reduzir o custo da criação de um novo
+objeto concreto de estratégia, quando a mesma estratégia é aplicada repetidamente
+a cada novo contexto—no nosso exemplo, a cada nova instância de `Order`. 
+Se uma loja que recebe 100.000 pedidos por dia, cada estratégia concreta
+será instanciada milhares de vezes.
+Então, para reduzir o custo de processamento do padrão Estratégia,
+os autores recomendam a aplicação de mais um padrão. Enquanto isso,
+o número de linhas e o custo de manutenção de seu código vai aumentando.
+
+Um caso de uso mais espinhoso, com estratégias concretas complexas mantendo
+estados internos, pode exigir a combinação de todas as partes dos padrões de
+projeto Estratégia e Peso Mosca. Muitas vezes, porém, estratégias concretas não
+têm estado interno; elas lidam apenas com dados vindos do contexto. Neste caso,
+não tenha dúvida, use as boas e velhas funções ao invés de escrever classes de
+um só metodo implementando uma interface de um só método declarada em outra
+classe diferente. Uma função pesa menos que uma instância de uma classe definida
+pelo usuário, e não há necessidade do Peso Mosca, pois cada função da estratégia
+é criada apenas uma vez por processo Python, quando o módulo é carregado. Uma
+função também é um "objeto compartilhado que pode ser usado em múltiplos
+contextos simultaneamente".
+
+Uma vez implementado o padrão Estratégia com funções, outras possibilidades nos
+ocorrem. Suponha que você queira criar uma "meta-estratégia", que seleciona o
+melhor desconto disponível para uma dada `Order`. Nas próximas seções vamos
+estudar as refatorações adicionais para implementar esse requisito, usando
+abordagens que se valem de funções e módulos vistos como objetos.((("",
+startref="RSfunction10")))((("", startref="funcorient01")))
+
+
+==== Escolhendo a melhor estratégia: abordagem simples
+
+Dados((("refactoring strategies", "choosing the best"))) os mesmos clientes e
+carrinhos de compras dos testes no <>, vamos agora
+acrescentar três testes adicionais ao  <>.
+
+[[ex_strategy_best_tests]]
+.A função `best_promo` aplica todos os descontos e devolve o maior
+====
+[source, python]
+----
+include::../code/10-dp-1class-func/strategy_best.py[tags=STRATEGY_BEST_TESTS]
+----
+====
+<1> `best_promo` selecionou a `larger_order_promo` para o cliente `joe`.
+<2> Aqui `joe` recebeu o desconto de `bulk_item_promo`, por comprar muitas bananas.
+<3> Neste caso `best_promo` deu à cliente fiel `ann` o desconto de fidelidade: `fidelity_promo`.
+
+A implementação de `best_promo` é simples. Veja o <>.
+
+[[ex_strategy_best]]
+.`best_promo` encontra o desconto máximo em uma lista de funções
+====
+[source, python]
+----
+include::../code/10-dp-1class-func/strategy_best.py[tags=STRATEGY_BEST]
+----
+====
+<1> `promos`: lista de estratégias implementadas como funções.
+<2> `best_promo` recebe uma instância de `Order` como argumento, como as outras funções `*_promo`.
+<3> Usando uma expressão geradora, aplicamos cada uma das funções de `promos` a `order`,
+e devolvemos o maior desconto encontrado.
+
+O <> é bem direto: `promos` é uma `list` de funções.
+Quando você se acostuma à ideia de funções como objetos de primeira classe, o
+próximo passo é notar como pode ser útil construir estruturas de dados
+contendo funções.
+
+Apesar do <> funcionar e ser fácil de ler, há alguma
+duplicação que poderia levar a um bug sutil: para adicionar uma nova estratégia,
+precisamos escrever a função e lembrar de incluí-la na lista `promos`. De outra
+forma a nova promoção só funcionará quando passada explicitamente como argumento
+para `Order`, e não será considerada por `best_promotion`.
+
+Vamos examinar algumas soluções para essa questão.
+
+==== Encontrando estratégias em um módulo
+
+Módulos((("refactoring strategies", "finding strategies in modules",
+id="RSfind10"))) também são objetos de primeira classe no Python, e a biblioteca
+padrão oferece várias funções para lidar com eles. A((("functions", "globals()
+function")))((("globals() function"))) função embutida `globals` é descrita
+assim na documentação de Python:
+
+`globals()`:: Devolve um dicionário representando a tabela de nomes do
+escopo global. Isso é sempre o dicionário do módulo atual
+(dentro de uma função, é o módulo onde ela foi definida, não o módulo
+onde é invocada).
+
+O <> é uma forma um tanto _hacker_ de usar `globals` para
+ajudar `best_promo` a encontrar automaticamente outras funções `*_promo`
+disponíveis.
+
+[[ex_strategy_best2]]
+.A lista `promos` é construída a partir da introspecção do espaço de nomes global do módulo
+====
+[source, python]
+----
+include::../code/10-dp-1class-func/strategy_best2.py[tags=STRATEGY_BEST2]
+----
+====
+<1> Importa as funções de promoções, para que fiquem disponíveis no espaço de
+nomes global.footnote:[Tanto o flake8 quanto o VS Code reclamam que esses nomes
+são importados mas não são usados. Por definição, ferramentas de análise
+estática não conseguem lidar com a natureza dinâmica de Python. Se seguirmos
+todos os conselhos dessas ferramentas, logo estaremos escrevendo programas
+austeros e prolixos similares aos de Java, mas com a sintaxe de Python.]
+<2> Itera sobre cada item no `dict` devolvido por `globals()`.
+<3> Seleciona apenas aqueles valores onde o nome termina com o sufixo `_promo` e...
+<4> ...filtra e remove a própria `best_promo`,
+para evitar uma recursão infinita quando `best_promo` for invocada.
+<5> Nenhuma mudança em `best_promo`.
+
+Outra forma de coletar as promoções disponíveis seria criar um módulo e colocar
+nele todas as funções de estratégia, exceto `best_promo`.
+
+No <>, a única mudança significativa é que a lista de funções
+de estratégia é criada pela introspecção de um módulo separado chamado
+`promotions`. Veja que o <> depende da importação do módulo
+`promotions` bem como de funções de introspecção de alto
+nível do módolo `inspect` da biblioteca padrão.
+
+
+[[ex_strategy_best3]]
+.A lista `promos` é construída a partir da introspecção de um novo módulo, `promotions`
+====
+[source, python]
+----
+include::../code/10-dp-1class-func/strategy_best3.py[tags=STRATEGY_BEST3]
+----
+====
+
+A função `inspect.getmembers` devolve os atributos de um objeto—neste caso, o
+módulo `promotions`—opcionalmente filtrados por um predicado (uma função
+booleana). Usamos `inspect.isfunction` para obter apenas as funções.
+
+O <> funciona independente dos nomes dados às funções;
+o que importa é que o módulo `promotions` contém apenas funções que, dado um
+pedido, calculam os descontos. Claro, isso é uma suposição implícita do código.
+Se alguém criasse uma função com uma assinatura diferente no módulo
+`promotions`, `best_promo` geraria um erro ao tentar aplicá-la a um pedido.
+
+Poderíamos acrescentar testes mais estritos para filtrar as funções, por exemplo
+inspecionando seus argumentos. O ponto principal do <> não é
+oferecer uma solução completa, mas enfatizar um uso possível da introspecção de
+módulo.
+
+Uma alternativa mais explícita para coletar dinamicamente as funções de desconto
+promocional seria usar um decorador simples. É nosso próximo tópico.((("",
+startref="FDPrefactor10")))((("", startref="RSfind10")))
+
+
+[[decorated_strategy_sec]]
+=== Estratégia com decorador de registro
+
+Lembre-se((("functions, design patterns with first-class", "decorator-enhanced
+strategy pattern", id="FDPdecorator10")))((("refactoring strategies",
+"decorator-enhanced pattern", id="RSdecorator10")))((("decorator-enhanced
+strategy pattern", id="decenh10"))) que nossa principal objeção ao
+<> foi a repetição dos nomes das funções em suas definições e
+na lista `promos`, usada pela função `best_promo` para determinar o maior
+desconto aplicável. A repetição é problemática porque alguém pode acrescentar
+uma nova função de estratégia promocional e esquecer de adicioná-la manualmente
+à lista `promos`—caso em que `best_promo` vai ignorar a nova
+estratégia, introduzindo um bug silencioso. O <>
+resolve esse problema com a técnica vista na <>.
+
+[[ex_strategy_best31]]
+.A lista `promos` é preenchida pelo decorador `promotion`
+====
+[source, python]
+----
+include::../code/10-dp-1class-func/strategy_best4.py[tags=STRATEGY_BEST4]
+----
+====
+<1> A lista `promos` é global no módulo, e começa vazia.
+<2> `promotion` é um decorador de registro: ele devolve a função `promo` inalterada, após inserí-la na lista `promos`.
+<3> Nenhuma mudança é necessária em `best_promo`, pois ela se baseia na lista `promos`.
+<4> Qualquer função decorada com `@promotion` será adicionada a `promos`.
+
+Essa solução tem várias vantagens sobre aquelas apresentadas anteriormente:
+
+* As funções de estratégia de promoção não precisam usar nomes especiais—não há
+necessidade do sufixo `_promo`.
+* O decorador `@promotion` realça o propósito da função decorada, e também torna
+mais fácil desabilitar temporariamente uma promoção: basta transformar a linha
+do decorador em comentário.
+* Estratégias de desconto promocional podem ser definidas em outros módulos, em
+qualquer lugar do sistema, desde que o decorador `@promotion` seja aplicado a
+elas.
+
+Na próxima seção vamos discutir Comando (_Command_)—outro padrão de projeto que
+é algumas vezes implementado via classes de um só metodo, quando funções simples
+seriam suficientes.((("", startref="decenh10")))((("",
+startref="RSdecorator10")))((("", startref="FDPdecorator10")))
+
+
+=== O padrão Comando
+
+Comando((("functions, design patterns with first-class", "Command pattern",
+id="FDPcommand10")))((("Command pattern", id="cmmd10")))((("refactoring
+strategies", "Command pattern", id="RScmmnd10")))((("UML class diagrams",
+"Command design pattern"))) é outro padrão de projeto que pode ser simplificado
+com o uso de funções passadas como argumentos. A <> mostra o
+arranjo das classes nesse padrão.
+
+[[command_uml]]
+.Diagrama de classes UML para um editor de texto controlado por menus, implementado com o padrão de projeto Comando. Cada comando pode ter um receptor diferente: o objeto que implementa a ação. Para `PasteCommand`, o receptor é Document. Para `OpenCommand`, o receptor é a aplicação.
+image::../images/flpy_1002.png[align="center",pdfwidth=12cm]
+
+O objetivo de Comando é desacoplar um objeto que invoca uma operação (o
+_invoker_ ou solicitante) do objeto fornecedor que implementa aquela operação (o
+_receiver_ ou receptor). No exemplo em _Padrões de Projetos_, cada solicitante é
+um item de menu em uma aplicação gráfica, e os receptores são o documento sendo
+editado ou a própria aplicação.
+
+A ideia é colocar um objeto `Command` entre os dois, implementando uma interface
+com um único método, `execute`, que chama algum método no receptor para executar
+a operação desejada. Assim, o solicitante não precisa conhecer a interface do
+receptor, e receptors diferentes podem ser adaptados com diferentes subclasses
+de `Command`. O solicitante é configurado com um comando concreto, e o opera
+chamando seu método `execute`. Observe na <> que `MacroCommand`
+pode armazenar um sequência de comandos; seu método `execute()` chama o mesmo
+método em cada comando armazenado.
+
+Citando _Padrões de Projetos_, "Comandos são um substituto orientado a objetos
+para _callbacks_." A pergunta é: precisamos de um substituto orientado a objetos
+para _callbacks_? Algumas vezes sim, mas nem sempre.
+Em vez de dar ao solicitante uma instância de `Command`, podemos dar
+a ele uma função. Em vez de invocar `command.execute()`, o solicitante pode
+invoca `command()` diretamente. +
+O `MacroCommand` pode ser uma classe que
+implementa `+__call__+`. Instâncias de `MacroCommand` seriam invocáveis, cada
+uma contendo uma lista de comandos para invocação futura, como implementado no
+<>.
+
+
+[[ex_macro_command]]
+.Cada instância de `MacroCommand` tem uma lista interna de comandos
+====
+[source, python]
+----
+class MacroCommand:
+    """A command that executes a list of commands"""
+
+    def __init__(self, commands):
+        self.commands = list(commands)  # <1>
+
+    def __call__(self):
+        for command in self.commands:  # <2>
+            command()
+----
+====
+<1> Criar uma nova lista com os itens do argumento `commands` garante que ela
+seja iterável e mantém uma cópia local de referências a comandos em cada
+instância de `MacroCommand`.
+<2> Quando uma instância de `MacroCommand` é invocada, cada comando em
+`self.commands` é chamado em sequência.
+
+Usos mais avançados do padrão Comando—para implementar "desfazer", por
+exemplo—podem exigir mais que uma simples função de _callback_. Mesmo assim,
+Python oferece algumas alternativas que merecem ser consideradas:
+
+* Uma instância invocável como `MacroCommand` no <> pode
+manter qualquer estado que seja necessário, e oferecer outros métodos além de
+`+__call__+`.
+
+* Uma clausura pode ser usada para armazenar algum estado interno em uma função entre
+invocações.
+
+Isso encerra nossa revisão do padrão Comando usando funções de primeira classe.
+Por alto, a abordagem aqui foi similar à que aplicamos a Estratégia: substituir
+por funções as instâncias de uma classe participante que implementava uma interface
+de método único. Afinal, todo invocável de Python implementa uma
+interface de método único, e esse método se chama `+__call__+`.((("",
+startref="RScmmnd10")))((("", startref="cmmd10")))((("",
+startref="FDPcommand10")))
+
+
+[[design_patterns_summary]]
+=== Resumo do capítulo
+
+Como((("functions, design patterns with first-class", "overview of"))) apontou
+Peter Norvig alguns anos após o surgimento do clássico _Padrões de Projetos_,
+"16 dos 23 padrões têm implementações qualitativamente mais simples em Lisp ou
+Dylan que em {cpp}, pelo menos para alguns usos de cada padrão".
+Python compartilha alguns dos recursos dinâmicos das linguagens Lisp e Dylan,
+especialmente funções de primeira classe, nosso foco neste capítulo.
+
+Na mesma palestra citada no início deste capítulo, refletindo sobre o 20º
+aniversário de _Padrões de Projetos: Soluções Reutilizáveis de Software
+Orientados a Objetos_, Ralph Johnson afirmou que um dos defeitos do livro é:
+"Excesso de ênfase nos padrões como linhas de chegada, em vez de como etapas em
+um processo de design".footnote:[_Root Cause Analysis of Some Faults in Design
+Patterns_ (Análise das Causas Básicas de Alguns Defeitos em Padrões de
+Projetos), palestra apresentada por Johnson no IME/CCSL da Universidade de São
+Paulo, em 15 de novembro de 2014.] Neste capítulo usamos o padrão Estratégia
+como ponto de partida: uma solução que funcionava, mas que simplificamos usando
+funções de primeira classe.
+
+Em muitos casos, funções ou objetos invocáveis oferecem um caminho mais natural
+para implementar _callbacks_ em Python que a imitação dos padrões Estratégia ou
+Comando como descritos pela Gangue dos Quatro em _Padrões de
+Projetos_. A refatoração de Estratégia e a discussão de Comando nesse capítulo
+são exemplos de uma ideia mais geral: algumas vezes você pode encontrar uma
+padrão de projeto ou uma API que exigem que seus componentes implementem uma
+interface com um único método, e aquele método tem um nome que soa muito
+genérico, como "executar", "rodar" ou "fazer". Tais padrões ou APIs podem
+frequentemente ser implementados em Python com menos código repetitivo, usando
+funções como objetos de primeira classe.
+
+[[dp_further]]
+=== Para saber mais
+
+A((("functions, design patterns with first-class", "further reading on")))
+Receita 8.21. _Implementing the Visitor Pattern_ (Implementando o Padrão
+Visitante) no _Python Cookbook 3rd ed_, mostra uma implementação elegante
+do padrão Visitante, na qual uma classe `NodeVisitor`
+trata métodos como objetos de primeira classe.
+
+Sobre o tópico mais geral de padrões de projetos, a oferta de leituras para o
+programador Python não é tão numerosa quando aquela disponível para as
+comunidades de outras linguagens.
+
+_Expert Python Programming_, de Tarek Ziadé (Packt), é um dos melhores livros
+sobre Python em nível intermediário, apresenta vários padrões clássicos com uma
+abordagem pythônica eu seu último capítulo. _Learning Python Design Patterns_,
+de Gennadiy Zlobin (Packt), é o único livro inteiramente dedicado a padrões em
+Python que encontrei. Mas esta obra de 100 páginas cobre apenas 8 dos 23 padrões
+de projeto originais.
+
+Alex Martelli já apresentou várias palestras sobre padrões de projetos em
+Python. Há um vídeo de sua https://fpy.li/10-5[apresentação na EuroPython]
+e um https://fpy.li/10-6[conjunto de slides em seu site pessoal]. Ao longo
+dos anos, encontrei vários conjuntos de slides e vídeos,
+então vale a pesquisar o nome dele e com palavras
+"Python Design Patterns".
+
+Há muitos livros sobre padrões de projetos com ênfase em Java.
+Meu preferido é _Head First Design Patterns_ (Use a Cabeça: 
+Padrões de Projeto), 2ª ed., de Eric Freeman e Elisabeth Robson (O'Reilly).
+Eles explicam 16 dos 23 padrões clássicos. Se você gosta do estilo
+amalucado da série _Head First_ e precisa de uma introdução a esse tópico, vai
+adorar esse livro. A segunda edição foi atualizada
+para incorporar o uso de funções de primeira classe em Java,
+tornando alguns dos exemplos mais próximos do modo como escreveríamos em
+Python.
+
+Para um olhar moderno sobre padrões, do ponto de vista de uma linguagem dinâmica
+com tipagem pato (_duck typing_) e funções de primeira classe, _Design Patterns in Ruby_
+("Padrões de Projetos em Ruby") de Russ Olsen (Addison-Wesley) traz muitas
+ideias aplicáveis também ao Python. A despeito de suas muitas diferenças
+sintáticas, no nível semântico Python e Ruby estão mais próximos entre si que de
+Java ou do {cpp}.
+
+No slides de https://fpy.li/norvigdp[_Design Patterns in Dynamic Languages_] (Padrões de
+Projetos em Linguagens Dinâmicas), Peter Norvig mostra como funções
+de primeira classe e outros recursos dinâmicos tornam vários dos padrões de
+projeto originais mais simples ou mesmo desnecessários.
+
+A "Introdução" do _Padrões de Projetos_ original, de Gamma et al. já vale o
+preço do livro—mais até que o catálogo de 23 padrões, que inclui desde receitas
+muito importantes até algumas raramente úteis. Alguns princípios de projetos de
+software muito conhecidos, como "Programe para uma interface, não para uma
+implementação" e "Prefira a composição de objetos à herança de classe",
+são citações daquela introdução.
+
+A ideia de padrões de projetos se originou com o arquiteto Christopher
+Alexander et al., e foi apresentada no livro _A Pattern Language_ ("Uma
+Linguagem de Padrões") (Oxford University Press). A ideia de Alexander é criar
+um vocabulário padronizado, permitindo que equipes compartilhem decisões comuns
+em projetos de edificações. M. J. Dominus wrote https://fpy.li/10-7[_"Design
+Patterns" Aren't_] (Padrões de Projetos Não São), uma curiosa apresentação de
+slides acompanhada de um texto argumentando que a visão original de Alexander
+sobre os padrões é mais profunda e mais humanista, e também se aplica à
+engenharia de software.
+
+.Ponto de vista
+****
+
+**Padrões para quem precisa de padrões**
+
+Python((("functions, design patterns with first-class", "Soapbox
+discussion")))((("Soapbox sidebars", "design patterns"))) tem funções de
+primeira classe e tipos de primeira classe, e Norvig afima que esses recursos
+afetam 10 dos 23 padrões (slide 10 de
+https://fpy.li/norvigdp[_Design Patterns in Dynamic Languages_]).
+Na <>, vimos que Python também tem funções
+genéricas de despacho único, uma forma limitada dos multi-métodos do
+CLOS, que Gamma et al. sugerem como uma maneira mais simples de implementar o
+padrão clássico Visitante (_Visitor_). Norvig, por outro lado, diz (no slide 10)
+que os multi-métodos simplificam o padrão Construtor (_Builder_).
+Ligar padrões de projetos a recursos de linguagens não é uma ciência exata.
+
+Em cursos a redor do mundo todo, padrões de projetos são frequentemente
+ensinados usando exemplos em Java. Ouvi mais de um estudante dizer que eles
+foram levados a crer que os padrões de projeto originais são úteis qualquer que
+seja a linguagem usada na implementação. A verdade é que os 23 padrões
+"clássicos" de _Padrões de Projetos_ se aplicam muito bem ao Java, apesar de
+terem sido apresentados principalmente no contexto do {cpp} (no livro, há alguns
+exemplos em Smalltalk). Mas isso não significa que todos aqueles
+padrões podem ser aplicados de forma igualmente satisfatória a qualquer
+linguagem. Os autores dizem explicitamente, logo no início de seu livro, que
+"alguns de nossos padrões são suportados diretamente por linguagens orientadas a
+objetos menos conhecidas" (a citação completa apareceu na primeira página deste
+capítulo).
+
+<<<
+Agora que Python está se tornando cada vez mais popular no ambiente acadêmico,
+podemos esperar que novos livros sobre padrões de projetos sejam escritos com
+foco nesta linguagem. Além disso, o Java 8 introduziu referências a
+métodos e funções anônimas, e esses recursos muito esperados devem incentivar o
+surgimento de novas abordagens aos padrões em Java—reconhecendo que, à medida
+que as linguagens evoluem, também é preciso evoluir nosso entendimento sobre
+quando e como aplicar os padrões de projetos clássicos.
+
+[role="soapbox-title"]
+**O chamado da natureza**
+
+Enquanto((("Soapbox sidebars", "__call__",
+secondary-sortas="call")))((("__call__")))
+trabalhávamos juntos para dar os toques finais a este livro, o revisor técnico
+Leonardo Rochael pensou:
+
+Se funções têm um método `+__call__+`, e métodos também são invocáveis, será que
+os métodos `+__call__+` também tem um método `+__call__+`?
+
+Não sei se a descoberta do Leo é util, mas com certeza é curiosa:
+
+[source, python]
+----
+>>> def turtle():
+...     return 'eggs'
+...
+>>> turtle()
+'eggs'
+>>> turtle.__call__()
+'eggs'
+>>> turtle.__call__.__call__()
+'eggs'
+>>> turtle.__call__.__call__.__call__()
+'eggs'
+>>> turtle.__call__.__call__.__call__.__call__()
+'eggs'
+>>> turtle.__call__.__call__.__call__.__call__.__call__()
+'eggs'
+>>> turtle.__call__.__call__.__call__.__call__.__call__.__call__()
+'eggs'
+----
+
+https://fpy.li/10-8[_Turtles all the way down!_]footnote:[NT:
+Literalmente: "Tartarugas até lá embaixo".
+Esta é uma forma poética de falar sobre regressão infinita,
+em alusão ao mito de que a Terra 
+se apoia sobre uma tartaruga gigante,
+que se apoia sobre outra tartaruga gigante,
+que se apoia sobre outra tartaruga gigante,
+que se apoia sobre outra tartaruga gigante,
+que se apoia sobre outra tartaruga gigante,
+que se apoia sobre outra tartaruga gigante,
+que se apoia sobre outra tartaruga gigante,
+que se apoia sobre outra tartaruga gigante...]
+
+
+****
+
+<<<
diff --git a/vol2/cap11.adoc b/vol2/cap11.adoc
new file mode 100644
index 0000000..9727664
--- /dev/null
+++ b/vol2/cap11.adoc
@@ -0,0 +1,1554 @@
+[[ch_pythonic_obj]]
+== Um objeto pythônico
+:example-number: 0
+:figure-number: 0
+
+[quote, Martijn Faassen, criador de frameworks Python e JavaScript]
+____
+Para uma biblioteca ou framework, ser pythônica significa tornar tão fácil e tão
+natural quanto possível que um programador Python descubra como realizar uma
+tarefa.footnote:[Do post no blog de Faassen intitulado https://fpy.li/11-1[_What
+is Pythonic?_ (O que é Pythônico?)]]
+____
+
+Graças((("Pythonic objects", "building user-defined classes"))) ao Modelo de
+Dados de Python, nossos tipos definidos pelo usuário podem se comportar de forma
+tão natural quanto os tipos embutidos. E isso pode ser realizado sem herança, no
+espírito do _duck typing:_ implemente os métodos necessários e seus objetos se
+comportarão da forma esperada.
+
+Nos capítulos anteriores, estudamos o comportamento de vários objetos embutidos.
+Vamos agora criar classes definidas pelo usuário que se portam como objetos
+Python nativos. As classes na sua aplicação provavelmente não precisam
+implementar tantos métodos especiais quanto os exemplos nesse capítulo. Mas se
+você estiver escrevendo uma biblioteca ou um framework, os programadores que
+usarão suas classes talvez esperem que elas se comportem como as classes
+fornecidas pelo Python. Satisfazer tal expectativa é um dos jeitos de ser
+"pythônico".
+
+Esse capítulo começa onde o https://fpy.li/1[«Capítulo 1»] (vol.1) terminou, mostrando como
+implementar vários métodos especiais comumente vistos em objetos Python de
+diferentes tipos.
+
+Veremos((("Pythonic objects", "topics covered"))) como:
+
+* Suportar as funções embutidas que convertem objetos para outros tipos (por
+exemplo, `repr()`, `bytes()`, `complex()`, etc.)
+* Implementar um construtor alternativo como um método da classe
+* Estender a mini-linguagem de formatação usada pelas f-strings, pela função
+embutida `format()` e pelo método `str.format()`
+* Fornecer acesso a atributos apenas para leitura
+* Tornar um objetos _hashable_, para uso em conjuntos e como chaves de `dict`
+* Economizar memória com `+__slots__+`
+
+Vamos fazer tudo isso enquanto desenvolvemos `Vector2d`, um tipo simples de
+vetor euclidiano bi-dimensional. No <>, o mesmo código servirá
+de base para uma classe de vetor N-dimensional.
+
+A evolução do exemplo incluirá dois tópicos conceituais importantes:
+
+* Como e quando usar os decoradores `@classmethod` e `@staticmethod`
+* Atributos privados e protegidos no Python: uso, convenções e limitações
+
+=== Novidades neste capítulo
+
+Acrescentei((("Pythonic objects", "significant changes to"))) uma nova epígrafe
+e também algumas palavras ao segundo parágrafo do capítulo, para falar do
+conceito de "pythônico"—que na primeira edição era mencionado só no final do
+livro.
+
+Atualizei a <> para mencionar as f-strings,
+introduzidas no Python 3.6. É uma mudança pequena, pois as f-strings suportam a
+mesma mini-linguagem de formatação que a função embutida `format()` e o método
+`str.format()`, então quaisquer métodos `+__format__+` implementados antes vão
+funcionar também com as f-strings.
+
+O resto do capítulo quase não mudou—os métodos especiais são praticamente os mesmos
+desde o Python 3.0, e a maioria existe desde o Python 2.2.
+
+Vamos começar pelos métodos de representação de objetos.
+
+[[object_repr_sec]]
+=== Representações de objetos
+
+Todas((("Pythonic objects", "object representations"))) as linguagens orientadas
+a objetos têm pelo menos uma forma padrão de se obter uma representação de
+qualquer objeto como uma string. Python tem duas formas:
+
+`repr()`:: Devolve((("repr() function")))((("functions", "repr() function")))
+uma string representando o objeto como o desenvolvedor quer vê-lo. É o que
+aparece quando o console de Python ou um depurador mostram um objeto.
+
+`str()`:: Devolve((("str() function")))((("functions", "str() function"))) uma
+string representando o objeto de uma forma amigável para o usuário final.
+É o que aparece quando se passa um objeto como argumento para `print()`.
+
+Os((("__repr__")))((("__str__")))
+métodos especiais `+__repr__+` e `+__str__+` suportam `repr()` e `str()`, como
+vimos no https://fpy.li/1[«Capítulo 1»] (vol.1).
+
+Existem((("__bytes__")))((("__format__")))
+mais dois métodos especiais para gerar representações alternativas de
+objetos, `+__bytes__+` e `+__format__+`. O método `+__bytes__+` é análogo a
+`+__str__+`: ele é chamado por `bytes()` para obter um objeto representado como
+uma sequência de bytes. Já `+__format__+` é usado por f-strings, pela função
+embutida `format()` e pelo método `str.format()`. Todos eles chamam
+`obj.__format__(fmt_spec)` 
+para gerar uma string exibindo o objeto conforme códigos de formatação especiais.
+Vamos tratar de `+__bytes__+` na próxima seção e de `+__format__+` logo depois.
+
+
+[WARNING]
+====
+Se você está vindo de Python 2, lembre-se que no Python 3
+`+__repr__+`, `+__str__+` e `+__format__+` devem sempre devolver strings Unicode
+(tipo `str`). Apenas `+__bytes__+` deveria devolver uma sequência de bytes (tipo
+`bytes`).
+====
+
+
+=== A volta da classe Vector
+
+Para((("Pythonic objects", "Vector2d class example",
+id="PYvector11")))((("Vector2d", "class example", id="V2dclass11"))) demonstrar
+os vários métodos usados para gerar representações de objetos, vamos criar uma
+classe `Vector2d`, similar à que vimos no https://fpy.li/1[«Capítulo 1»] (vol.1). O
+<> ilustra o comportamento básico que esperamos de uma
+instância de `Vector2d`.
+
+[[ex_vector2d_v0_demo]]
+.Instâncias de `Vector2d` têm várias representações
+====
+[source, python]
+----
+include::../code/11-pythonic-obj/vector2d_v0.py[tags=VECTOR2D_V0_DEMO]
+----
+====
+
+<1> Os componentes de um `Vector2d` podem ser acessados diretamente como
+atributos (não é preciso invocar métodos _getter_).
+
+<2> Um `Vector2d` pode ser desempacotado para uma tupla de variáveis.
+
+<3> O `repr` de um `Vector2d` imita o código-fonte usado para construir a instância.
+
+<4> Usar `eval` aqui mostra que o `repr` de um `Vector2d` é uma representação
+fiel da chamada a seu construtor.footnote:[Usei `eval` para clonar o objeto
+apenas para demonstrar a sintaxe da string gerada por `repr`; para clonar uma instância, a
+função `copy.copy` é mais segura e rápida.]
+
+<5> `Vector2d` suporta a comparação com `==` (muito útil para testes).
+
+<6> `print` chama `str`, que no caso de `Vector2d` exibe um par ordenado.
+
+<7> `bytes` usa o método `+__bytes__+` para produzir uma representação binária.
+
+<8> `abs` usa o método `+__abs__+` para devolver a magnitude do `Vector2d`.
+
+<9> `bool` usa o método `+__bool__+` para devolver `False` se o `Vector2d`
+tiver magnitude zero, caso contrário esse método devolve `True`.
+
+A classe `Vector2d` do <> é implementada em _vector2d_v0.py_ (no
+<>). O código está baseado no https://fpy.li/8w[«Exemplo 2 do Capítulo 1»] (vol.1), exceto pelos
+métodos para os operadores `{plus}` e `*`, que veremos mais tarde no
+<>. Vamos acrescentar o método para `==`, pois ele facilita
+escrever testes. Nesse ponto, `Vector2d` usa vários métodos especiais para oferecer
+operações que um pythonista espera encontrar em um objeto bem projetado.
+
+<<<
+[[ex_vector2d_v0]]
+.vector2d_v0.py: todos os métodos até aqui são métodos especiais
+====
+[source, python]
+----
+include::../code/11-pythonic-obj/vector2d_v0.py[tags=VECTOR2D_V0]
+----
+====
+
+<1> `typecode` é um atributo de classe, usado na conversão de instâncias de
+`Vector2d` de/para `bytes`.
+
+<2> Converter `x` e `y` para `float` em `+__init__+` captura erros mais rápido,
+algo útil quando `Vector2d` é chamado com argumentos não numéricos.
+
+<3> `+__iter__+` torna um `Vector2d` iterável; é isso que faz o desempacotamento
+funcionar (por exemplo, `x, y = my_vector`). Usamos uma
+expressão geradora para produzir os dois componentes, um após outro.footnote:[Essa
+linha também poderia ser escrita assim: `yield self.x; yield.self.y`. Terei mais
+a dizer sobre o método especial `+__iter__+`, sobre expressões geradoras e sobre
+a palavra reservada `yield` no https://fpy.li/17[«Capítulo 17»] (vol.3).]
+
+<4> O `+__repr__+` cria uma string interpolando os componentes com `{!r}`, para
+obter seus `repr`; como `Vector2d` é iterável, `*self` alimenta `format` com os
+componentes `x` e `y`.
+
+<5> Como `Vector2d` é iterável, é fácil criar uma `tuple` para exibição como um
+par ordenado.
+
+<6> Para gerar `bytes`, convertemos o typecode para `bytes` e concatenamos...
+
+<7> ...`bytes` convertidos a partir de um `array` criado iterando sobre a
+instância.
+
+<8> Para comparar facilmente todos os componentes, criamos tuplas a partir dos
+operandos. Isso funciona para operandos que sejam instâncias de `Vector2d`, mas
+tem problemas. Veja o alerta abaixo.
+
+<9> A magnitude é o comprimento da hipotenusa do triângulo retângulo
+com os catetos formados pelos componentes `x` e `y`.
+
+<10> `+__bool__+` usa `abs(self)` para computar a magnitude, então a converte
+para `bool`; assim, `0.0` se torna `False`, qualquer valor diferente de zero é
+`True`.
+
+[WARNING]
+====
+
+O método `+__eq__+` no <> funciona para operandos `Vector2d`,
+mas também devolve `True` ao comparar instâncias de `Vector2d` a outros
+iteráveis contendo os mesmos valores numéricos  (por exemplo, `Vector(3, 4) ==
+[3, 4]`). Isso pode ser considerado uma característica ou um bug. Essa discussão
+terá que esperar até o <>, onde falamos de sobrecarga de
+operadores.
+
+====
+
+Temos um conjunto bastante completo de métodos básicos, mas ainda precisamos de
+uma maneira de reconstruir um `Vector2d` a partir da representação binária
+produzida por `bytes()`.((("", startref="PYvector11")))((("",
+startref="V2dclass11")))
+
+=== Um construtor alternativo
+
+Já((("Pythonic objects", "alternative constructor for"))) que podemos exportar
+um `Vector2d` na forma de bytes, naturalmente precisamos de um método para
+importar um `Vector2d` de uma sequência binária. Procurando na biblioteca padrão
+por algo similar, descobrimos que `array.array` tem um método de classe chamado
+`.frombytes`, adequado a nossos propósitos--já o vimos na https://fpy.li/7v[«Seção 2.10.1»] (vol.1).
+Adotamos o mesmo nome e usamos sua funcionalidade em um método de classe para
+`Vector2d` em _vector2d_v1.py_ (no <>).
+
+[[ex_vector2d_v1]]
+.Parte de vector2d_v1.py: esse trecho mostra apenas o método de classe `frombytes`, acrescentado à definição de `Vector2d` em vector2d_v0.py (no <>)
+====
+[source, python]
+----
+include::../code/11-pythonic-obj/vector2d_v1.py[tags=VECTOR2D_V1]
+----
+====
+
+<1> O decorador `classmethod` modifica um método para que ele possa ser chamado
+diretamente em uma classe.
+
+<2> Nenhum argumento `self`; em vez disso, a própria classe é passada como
+primeiro argumento—por convenção chamado `cls`.
+
+<3> Lê o `typecode` do primeiro byte.
+
+<4> Cria uma `memoryview` a partir da sequência binária `octets`, e usa o
+`typecode` para convertê-la.footnote:[Tivemos uma pequena introdução a
+`memoryview` e explicamos seu método `.cast` na https://fpy.li/8d[«Seção 2.10.2»] (vol.1).]
+
+<5> Desempacota a `memoryview` resultante da conversão no par de argumentos
+necessários para o construtor.
+
+Acabei de usar um decorador `classmethod`, e ele é muito específico do Python.
+Vamos então falar um pouco disso.
+
+[[classmethod_x_staticmethod_sec]]
+=== `classmethod` versus `staticmethod`
+
+O((("Pythonic objects", "classmethod versus staticmethod")))((("classmethod
+decorator")))((("staticmethod decorator")))((("decorators and closures",
+"classmethod versus staticmethod"))) decorador `classmethod` não é mencionado no
+tutorial de Python, nem tampouco o `staticmethod`.
+Quem OO com Java pode se perguntar porque Python tem esses dois
+decoradores, e não apenas `staticmethod`.
+
+Vamos começar com `classmethod`. O <> mostra seu uso: definir um
+método que opera na classe, e não em suas instâncias. O `classmethod` muda a
+forma como o método é chamado, então recebe a própria classe como primeiro
+argumento, em vez de uma instância. Seu uso mais comum é em construtores
+alternativos, como `frombytes` no <>. Observe como a última
+linha de `frombytes` o argumento `cls`, invocando-o para criar uma
+nova instância: `cls(*memv)`.
+
+O decorador `staticmethod`, por outro lado, muda um método para que ele não
+receba um argumento automaticamente. Essencialmente, um método estático
+é apenas uma função simples que por acaso mora no corpo de uma classe, em vez de
+ser definida no nível do módulo. O <> compara a operação
+de `classmethod` e `staticmethod`.
+
+[[ex_class_staticmethod]]
+.Comparando o comportamento de `classmethod` e `staticmethod`
+====
+[source, python]
+----
+>>> class Demo:
+...     @classmethod
+...     def klassmeth(*args):
+...         return args  # <1>
+...     @staticmethod
+...     def statmeth(*args):
+...         return args  # <2>
+...
+>>> Demo.klassmeth()  # <3>
+(,)
+>>> Demo.klassmeth('spam')
+(, 'spam')
+>>> Demo.statmeth()   # <4>
+()
+>>> Demo.statmeth('spam')
+('spam',)
+----
+====
+<1> `klassmeth` apenas devolve todos os argumentos posicionais.
+<2> `statmeth` faz o mesmo.
+<3> Não importa como ele seja invocado, `Demo.klassmeth` recebe sempre
+a classe `Demo` como primeiro argumento.
+<4> `Demo.statmeth` se comporta exatamente como uma boa e velha função.
+
+[NOTE]
+====
+O decorador `classmethod` é obviamente útil mas, em minha experiência, bons
+casos de uso para `staticmethod` são raros. Talvez a função
+seja intimamente relacionada a classe, mesmo sem nunca usá-la em seu corpo.
+Daí você pode querer que ela fique próxima no código-fonte.
+Mesmo assim, definir a função logo antes ou logo depois da classe,
+no mesmo módulo, é perto o suficiente na maioria
+dos casos.footnote:[Leonardo Rochael, um dos revisores técnicos deste livro,
+discorda de minha opinião desabonadora sobre o `staticmethod`, e recomenda como
+contra-argumento o post de blog https://fpy.li/11-2[_The Definitive Guide on How
+to Use Static, Class or Abstract Methods in Python_] (O Guia Definitivo sobre
+Como Usar Métodos Estáticos, de Classe ou Abstratos em Python), de Julien
+Danjou. O post de Danjou é muito bom; recomendo sua leitura. Mas não foi
+suficiente para mudar meu ponto de vista sobre `staticmethod`. Você terá que
+decidir por conta própria se vale ou não a pena usar `staticmethod`.]
+====
+
+Agora que vimos para que serve o `classmethod` (e que o `staticmethod` não é
+muito útil), vamos voltar para a questão da representação de objetos e entender
+como gerar uma saída formatada.
+
+[[format_display_sec]]
+=== Exibição formatada
+
+As((("Pythonic objects", "formatted displays", id="POformat11")))((("functions",
+"format() function")))((("format() function")))((("str.format()
+method")))((("__format__")))((("f-string syntax",
+"delegation of formatting by")))((("displays, formatting", id="dispform11")))
+f-strings, a função embutida `format()` e o método `str.format()` delegam a
+lógica da formatação para cada tipo, chamando seu método
+`+.__format__(fmt_spec)+`.
+A string `fmt_spec` especifica a formatação desejada.
+Esta especificação é:
+
+* O segundo argumento em `format(my_obj, fmt_spec)`, ou
+
+* O que aparece após os dois pontos (`:`) em um campo de substituição
+delimitado por `{}` dentro de uma f-string ou na string `s` em `s.format()`
+
+Por exemplo:
+
+[source, python]
+----
+>>> brl = 1 / 4.82  # BRL to USD currency conversion rate
+>>> brl
+0.20746887966804978
+>>> format(brl, '0.4f')  # <1>
+'0.2075'
+>>> '1 BRL = {rate:0.2f} USD'.format(rate=brl)  # <2>
+'1 BRL = 0.21 USD'
+>>> f'1 USD = {1 / brl:0.2f} BRL'  # <3>
+'1 USD = 4.82 BRL'
+----
+
+<1> A especificação de formato é `'0.4f'`.
+
+<2> A especificação de formato é `'0.2f'`. O `rate` no campo de substituição não
+é parte da especificação de formato. Ele determina qual argumento nomeado de
+`.format()` entra naquele campo de substituição.
+
+<3> Novamente, a especificação é `'0.2f'`. A expressão `1 / brl` não é parte
+dela.
+
+O segundo e o terceiro comentário apontam um fato importante: uma
+string de formatação tal como `'{0.mass:5.3e}'` usa duas notações
+separadas. O `'0.mass'` à esquerda dos dois pontos é a parte `field_name` da
+sintaxe de campo de substituição, e pode ser uma expressão arbitrária em uma
+f-string. O `'5.3e'` após os dois pontos é a especificação do formato.
+A notação usada na especificação de formato é chamada
+https://fpy.li/63[Mini-Linguagem de
+Especificação de Formato].
+
+[TIP]
+====
+
+Se f-strings, `format()` e `str.format()` são novidades para você, minha
+experiência como professor me informa que é melhor estudar primeiro a função
+embutida `format()`, que usa apenas a
+https://fpy.li/63[Mini-Linguagem de
+Especificação de Formato].
+Após pegar o jeito dela, leia
+https://fpy.li/64[«Literais de string formatados»] e
+https://fpy.li/65[«Sintaxe das string de formato»],
+para aprender sobre a notação de campo de substituição
+(`{:}`), usada em f-strings e no método `str.format()` (incluindo os marcadores
+de conversão `!s`, `!r`, e `!a`). F-strings não tornam o método `str.format()`
+obsoleto:
+na maioria dos casos f-strings resolvem o problema, mas algumas vezes é melhor
+especificar a string de formatação em outro arquivo (diferente de onde ela será
+utilizada).
+
+====
+
+Alguns tipos embutidos têm seus próprios códigos de apresentação na
+Mini-Linguagem de Especificação de Formato. Por exemplo—entre muitos outros
+códigos—o tipo `int` suporta `b` e `x`, para saídas em base 2 e base 16,
+respectivamente, enquanto `float` implementa `f`, para uma exibição de ponto
+fixo, e `%`, para exibir porcentagens:
+
+[source, python]
+----
+>>> format(42, 'b')
+'101010'
+>>> format(2 / 3, '.1%')
+'66.7%'
+----
+
+A Mini-Linguagem de Especificação de Formato é extensível, porque cada classe
+interpreta o argumento `fmt_spec` como quiser. Por exemplo, as classes no
+módulo `datetime` usam em seus métodos `+__format__+` os mesmos códigos
+de formatação das funções `strftime()`, que são mais antigas.
+Veja abaixo alguns exemplos de uso da função
+`format()` e do método `str.format()`:
+
+[source, python]
+----
+>>> from datetime import datetime
+>>> now = datetime.now()
+>>> format(now, '%H:%M:%S')
+'18:49:05'
+>>> "It's now {:%I:%M %p}".format(now)
+"It's now 06:49 PM"
+----
+
+Se a classe não implementar `+__format__+`,
+o método herdado de `object` devolve `str(my_object)`.
+Como `Vector2d` tem um `+__str__+`, isso funciona:
+
+[source, python]
+----
+>>> v1 = Vector2d(3, 4)
+>>> format(v1)
+'(3.0, 4.0)'
+----
+
+Entretanto, se você passar um especificador de formato,
+`+object.__format__+` gera um `TypeError`:
+
+[source, python]
+----
+>>> format(v1, '.3f')
+Traceback (most recent call last):
+  ...
+TypeError: non-empty format string passed to object.__format__
+----
+
+Vamos corrigir isso implementando nossa própria mini-linguagem de formatação.
+O primeiro passo será presumir que o especificador de formato fornecido pelo
+usuário tem por objetivo formatar cada componente `float` do vetor. Esse é o
+resultado esperado:
+
+[source, python]
+----
+>>> v1 = Vector2d(3, 4)
+>>> format(v1)
+'(3.0, 4.0)'
+>>> format(v1, '.2f')
+'(3.00, 4.00)'
+>>> format(v1, '.3e')
+'(3.000e+00, 4.000e+00)'
+----
+
+O <> implementa `+__format__+` para produzir as formatações vistas acima.
+
+[[ex_format_t1]]
+.O método `+Vector2d.__format__+`, versão #1
+====
+[source, python]
+----
+    # inside the Vector2d class
+
+    def __format__(self, fmt_spec=''):
+        components = (format(c, fmt_spec) for c in self)  # <1>
+        return '({}, {})'.format(*components)  # <2>
+----
+====
+
+<1> Usa a função embutida `format` para aplicar o `fmt_spec` a cada componente
+do vetor, criando um iterável de strings formatadas.
+
+<2> Insere as strings formatadas no gabarito `'(x, y)'`.
+
+Agora vamos acrescentar um código de formatação customizado à nossa
+mini-linguagem: se o especificador de formato terminar com `'p'`, vamos exibir o
+vetor em coordenadas polares: ``, onde `r` é a magnitute e θ (theta) é o
+ângulo em radianos. O restante do especificador de formato (o que quer que venha
+antes do `'p'`) será usado como antes.
+
+[TIP]
+====
+
+Ao escolher a letra para um código customizado de formato, evitei sobrescrever
+códigos usados por outros tipos. Na
+https://fpy.li/63[Mini-Linguagem de
+Especificação de Formato] vemos que inteiros usam os códigos `'bcdoxXn'`,
+`floats` usam `'eEfFgGn%'` e strings usam `'s'`. Então escolhi `'p'` para
+coordenadas polares. Como cada classe interpreta esses códigos de forma
+independente, reutilizar uma letra em um formato customizado para um novo tipo
+não é um erro, mas pode ser confuso para os usuários.
+
+====
+
+Para gerar coordenadas polares, já temos o método `+__abs__+` para a magnitude.
+Vamos então escrever um método `angle` simples, usando a função `math.atan2()`,
+para obter o ângulo. Eis o código:
+
+[source, python]
+----
+    # inside the Vector2d class
+
+    def angle(self):
+        return math.atan2(self.y, self.x)
+----
+
+Com isso, podemos agora aperfeiçoar nosso `+__format__+` para gerar coordenadas
+polares. Veja o <>.
+
+[[ex_format_t2]]
+.O método `+Vector2d.__format__+`, versão #2, agora com coordenadas polares
+====
+[source, python]
+----
+include::../code/11-pythonic-obj/vector2d_v2_fmt_snippet.py[tags=VECTOR2D_V2_FORMAT]
+----
+====
+<1> O formato termina com `'p'`: usar coordenadas polares.
+<2> Remove o sufixo `'p'` de `fmt_spec`.
+<3> Cria uma `tuple` de coordenadas polares: `(magnitude, angle)`.
+<4> Configura o formato externo com colchetes angulares `< >`.
+<5> Caso contrário, usa os componentes `x, y` de `self` para coordenadas retângulares.
+<6> Configura o formato externo com parênteses.
+<7> Gera um iterável cujos componentes são strings formatadas.
+<8> Insere as strings formatadas no formato externo.
+
+Com o <>, obtemos resultados como esses:
+
+[source, python]
+----
+>>> format(Vector2d(1, 1), 'p')
+'<1.4142135623730951, 0.7853981633974483>'
+>>> format(Vector2d(1, 1), '.3ep')
+'<1.414e+00, 7.854e-01>'
+>>> format(Vector2d(1, 1), '0.5fp')
+'<1.41421, 0.78540>'
+----
+
+
+Como mostrou essa seção, não é difícil estender a Mini-Linguagem de
+Especificação de Formato para suportar tipos definidos pelo usuário.
+
+Vamos agora passar a um assunto que vai além das aparências: tornar nosso
+`Vector2d` _hashable_, para podermos colocar vetores em conjuntos 
+ou usá-los como chaves em um `dict`.((("", startref="dispform11")))((("",
+startref="POformat11")))
+
+
+[[hashable_vector2d_sec]]
+=== Um Vector2d _hashable_
+
+Da((("Pythonic objects", "hashable Vector2d", id="POhash11")))((("Vector2d",
+"hashable", id="V2dhash11"))) forma como ele está definido até agora, as
+instâncias de nosso `Vector2d` não são _hashable_, então não podemos colocá-las
+em um `set`:
+
+[source, python]
+----
+>>> v1 = Vector2d(3, 4)
+>>> hash(v1)
+Traceback (most recent call last):
+  ...
+TypeError: unhashable type: 'Vector2d'
+>>> set([v1])
+Traceback (most recent call last):
+  ...
+TypeError: unhashable type: 'Vector2d'
+----
+
+Para tornar um `Vector2d` _hashable_, precisamos implementar `+__hash__+`
+(`+__eq__+` também é necessário; já codamos esse método). Além disso,
+precisamos tornar imutáveis as instâncias do vetor, como vimos na
+https://fpy.li/8t[«Seção 3.4.1»] (vol.1).
+
+Nesse momento, qualquer um pode fazer `v1.x = 7`.
+Nada no código proíbe modificar um `Vector2d`.
+Mas o comportamento que queremos é o seguinte:
+
+[source, python]
+----
+>>> v1.x, v1.y
+(3.0, 4.0)
+>>> v1.x = 7
+Traceback (most recent call last):
+  ...
+AttributeError: can't set attribute
+----
+
+Faremos isso transformando os componentes `x` e `y` em propriedades apenas para
+leitura no <>.
+
+[[ex_vector2d_v3]]
+.vector2d_v3.py: só as mudanças necessárias para tornar `Vector2d` imutável aparecem aqui; a listagem completa está no <>
+====
+[source, python]
+----
+include::../code/11-pythonic-obj/vector2d_v3_prophash.py[tags=VECTOR2D_V3_PROP]
+----
+====
+
+<1> Usa exatamente dois sublinhados como prefixo (com zero ou um sublinhado como
+sufixo), para tornar um atributo privado.footnote:[Os prós e contras dos
+atributos privados são assunto da <>, mais adiante.]
+
+<2> O decorador `@property` marca o método _getter_ de uma propriedade.
+
+<3> O método _getter_ tem nome da propriedade pública que
+ele expõe: `x`.
+
+<4> Apenas devolve `self.__x`.
+
+<5> Repete a mesma fórmula para a propriedade `y`.
+
+<6> Todos os métodos que apenas leem os componentes `x` e `y` podem continuar
+lendo as propriedades públicas através de `self.x` e `self.y` em vez de usar os
+atributos privados. Por isso omiti o resto da classe.
+
+[NOTE]
+====
+
+`Vector.x` e `Vector.y` são exemplos de propriedades apenas para leitura.
+Propriedades para leitura/escrita serão tratadas no https://fpy.li/22[«Capítulo 22»] (vol.3), onde
+mergulhamos mais fundo no decorador `@property`.
+
+====
+
+Agora que nossos vetores estão razoavelmente protegidos contra mutação
+acidental, podemos implementar o método `+__hash__+`. Ele deve devolver um `int`
+e, idealmente, levar em consideração os hashs dos atributos do objeto usados
+também no método `+__eq__+`, pois objetos que são considerados iguais ao serem
+comparados devem ter o mesmo _hash_. A 
+https://fpy.li/66[documentação]
+do método especial `+__hash__+` sugere computar o _hash_ de uma tupla com os
+componentes, e é isso que fazemos no <>.
+
+[[ex_vector2d_v3_hash]]
+.vector2d_v3.py: implementação de __hash__
+====
+[source, python]
+----
+    # inside class Vector2d:
+
+    def __hash__(self):
+        return hash((self.x, self.y))
+----
+====
+
+Com o acréscimo do método `+__hash__+`, temos agora vetores _hashable_:
+
+[source, python]
+----
+>>> v1 = Vector2d(3, 4)
+>>> v2 = Vector2d(3.1, 4.2)
+>>> hash(v1), hash(v2)
+(1079245023883434373, 1994163070182233067)
+>>> {v1, v2}
+{Vector2d(3.1, 4.2), Vector2d(3.0, 4.0)}
+----
+
+[TIP]
+====
+Não é estritamente necessário implementar propriedades ou proteger de alguma
+forma os atributos de instância para criar um tipo _hashable_. Só é necessário
+implementar corretamente `+__hash__+` e `+__eq__+`. Mas, como o valor
+de um objeto _hashable_ nunca deve mudar, é um bom motivo para aprender
+a criar propriedades apenas para leitura (_read only_).
+====
+
+Se você criar um tipo com que tem um valor numérico escalar,
+você pode implementar os métodos `+__int__+` e `+__float__+`, invocados
+pelos construtores `int()` e `float()`, que são usados, em alguns contextos,
+para conversão de tipo. Há também o método `+__complex__+`, para suportar o
+construtor embutido `complex()`. Talvez `Vector2d` pudesse oferecer o
+`+__complex__+`, mas deixo isso como um exercício para vocês.((("",
+startref="POhash11")))
+
+[[positional_pattern_implement_sec]]
+=== Suportando o casamento de padrões posicionais
+
+Até aqui, instâncias de `Vector2d`((("Pythonic objects", "supporting positional
+patterns")))((("positional patterns"))) são compatíveis com o casamento de padrões
+com instâncias de classe—vistos na https://fpy.li/8a[«Seção 5.8.2»] (vol.1).
+
+No <>, os padrões nomeados funcionam como esperado.
+
+[[vector_match_keyword_ex]]
+.Padrões nomeados para sujeitos `Vector2d`—requer Python 3.10
+====
+[source, python]
+----
+include::../code/11-pythonic-obj/patterns.py[tags=KEYWORD_PATTERNS]
+----
+====
+
+Entretanto, se tentamos usar um padrão posicional, como esse:
+
+[source, python]
+----
+        case Vector2d(_, 0):
+            print(f'{v!r} is horizontal')
+----
+
+o resultado é esse:
+
+[source]
+----
+TypeError: Vector2d() accepts 0 positional sub-patterns (1 given)
+----
+
+Para resolver, criamos
+um atributo de classe `+__match_args__+`:
+
+[source, python]
+----
+class Vector2d:
+    __match_args__ = ('x', 'y')
+----
+
+O atributo `+__match_args__+` tem os nomes dos atributos de
+instância na ordem em que eles serão usados no casamento de padrões posicionais.
+
+Agora podemos escrever menos código ao criar padrões para casar com
+sujeitos `Vector2d`, como no <>.
+
+[[vector_match_positional_ex]]
+.Padrões posicionais para sujeitos `Vector2d`—requer Python 3.10
+====
+[source, python]
+----
+include::../code/11-pythonic-obj/patterns.py[tags=POSITIONAL_PATTERNS]
+----
+====
+
+O atributo de classe `+__match_args__+` não precisa incluir todos os atributos
+públicos de instância. Em especial, se o `+__init__+` da classe tem argumentos
+obrigatórios e opcionais, que são depois vinculados a atributos de instância,
+pode ser razoável nomear apenas os argumentos obrigatórios em
+`+__match_args__+`, omitindo os opcionais.
+
+Agora vamos revisar tudo o que programamos até aqui no `Vector2d`.
+
+
+=== Listagem completa `Vector2d`, versão 3
+
+Já((("Pythonic objects", "Vector2d full listing",
+id="POvectorfull11")))((("Vector2d", "full listing", id="V2dfull11"))) estamos
+trabalhando no `Vector2d` há algum tempo, mostrando apenas trechos isolados. O
+<> é uma listagem completa e consolidada de
+_vector2d_v3.py_, incluindo os doctests que usei durante o desenvolvimento.
+
+<<<
+
+[[ex_vector2d_v3_full]]
+.vector2d_v3.py: o módulo completo
+====
+[source, python]
+----
+include::../code/11-pythonic-obj/vector2d_v3.py[]
+----
+====
+
+{nbsp}
+
+Recordando, nessa seção e nas anteriores vimos alguns dos métodos especiais
+essenciais que você pode querer implementar para oferecer um objeto completo.
+
+[NOTE]
+====
+Você deve implementar os métodos especiais que forem úteis em sua aplicação.
+Os usuários finais não se importam se os objetos que da aplicação são
+pythônicos ou não.
+
+Por outro lado, se suas classes são parte de uma biblioteca para ser usada por
+outros programadores Python, você não tem como adivinhar como eles vão usar
+seus objetos. E os usuários de sua biblioteca estarão esperando os
+comportamentos pythônicos que descrevemos aqui.
+====
+
+Como programado no  <>, `Vector2d` é um exemplo didático
+com uma longa lista de métodos especiais relacionados à representação de
+objetos, não um modelo para qualquer classe que você vai escrever.
+
+Na próxima seção, deixamos o `Vector2d` de lado por um tempo para discutir o
+design e as limitações do mecanismo de atributos privados no Python—o prefixo
+de duplo sublinhado em `self.__x`.((("", startref="POvectorfull11")))((("",
+startref="V2dfull11")))
+
+[[private_protected_sec]]
+=== Atributos privados e "protegidos" no Python
+
+Em((("Pythonic objects", "private and protected attributes",
+id="POprivate11")))((("attributes", "private and protected", id=Aprivate11")))
+Python, não há como criar variáveis privadas como as criadas com o modificador
+`private` em Java. O que temos no Python é um mecanismo simples para prevenir
+que um atributo "privado" em uma subclasse seja acidentalmente sobrescrito.
+
+Considere o seguinte cenário: alguém escreveu uma classe chamada `Dog`, que usa
+um atributo de instância `mood` internamente, sem expô-lo. Você precisa criar a
+uma subclasse `Beagle` de `Dog`. Se você criar seu próprio atributo de instância
+`mood`, sem saber da colisão de nomes, vai afetar o atributo `mood` usado pelos
+métodos herdados de `Dog`. Isso seria bem complicado de depurar.
+
+Para prevenir esse tipo de problema, se você nomear o atributo de instância no
+formato `+__mood+` (dois sublinhados iniciais e zero ou no máximo um sublinhado
+no final), Python armazena o nome no `+__dict__+` da instância, prefixado com um
+sublinhado seguido do nome da classe. Na classe `Dog`, por exemplo, `+__mood+` se
+torna `+_Dog__mood+` e em `Beagle` ele será `+_Beagle__mood+`.
+Este mecanismo do Python é conhecido pela encantadora alcunha de((("name
+mangling"))) _name mangling_ (desfiguração de nome).
+
+O <> mostra o resultado na classe `Vector2d` do <>.
+
+[[name_mangling_ex]]
+.Nomes de atributos privados são "desfigurados" no `__dict__`
+====
+[source, python]
+----
+>>> v1 = Vector2d(3, 4)
+>>> v1.__dict__
+{'_Vector2d__y': 4.0, '_Vector2d__x': 3.0}
+>>> v1._Vector2d__x
+3.0
+----
+====
+
+<<<
+A desfiguração do nome oferece proteção, não segurança:
+evita acessos acidentais, mas não intencionais.
+A <> mostra um dispositivo de proteção.
+
+[[safety_fig]]
+.A capa sobre um interruptor é um dispositivo de proteção, não de segurança: previne acidentes, não sabotagem
+image::../images/flpy_1101.png[interruptores com coberturas de proteção]
+
+Qualquer um que saiba como os nomes privados são modificados pode ler o atributo
+privado diretamente, como mostra a última linha do <>.
+Este conhecimento é útil para depuração e serialização. Também pode ser usado para
+atribuir um valor a um componente privado de um `Vector2d`, escrevendo
+`v1._Vector2d__x = 7`. Mas se você estiver fazendo isso num código em produção,
+não poderá reclamar se alguma coisa explodir.
+
+A funcionalidade de desfiguração de nomes não é amada por todos os pythonistas,
+nem tampouco a aparência estranha de nomes escritos como `+self.__x+`. Muitos
+preferem evitar essa sintaxe e usar apenas um sublinhado no prefixo para
+"proteger" atributos por convenção: `+self._x+`. Críticos da
+desfiguração automática com o sublinhado duplo dizem que preocupações com
+modificações acidentais a atributos devem ser tratadas através de convenções
+de nomenclatura. O criador do _pip_, _virtualenv_ e outros
+projetos importantes, Ian Bicking, escreveu:
+
+[quote]
+____
+
+Nunca, de forma alguma, use dois sublinhados como prefixo. Isso é irritantemente
+privado. Se colisão de nomes for uma preocupação, use desfiguração explícita de
+nomes em seu lugar (por exemplo,`+_MyThing_blahblah+`). Isso é essencialmente a
+mesma coisa que o sublinhado duplo, mas é transparente enquanto o sublinhado duplo é
+obscuro.footnote:[Do https://fpy.li/11-8[_Paste Style Guide_] (Guia de Estilo do
+Paste).]
+
+____
+
+
+O prefixo de sublinhado único não tem nenhum significado especial para o
+interpretador Python, quando usado em nomes de atributo. Mas essa é uma
+convenção muito presente entre programadores Python: tais atributos não devem
+ser acessados de fora da classe.footnote:[Em módulos, um único `+_+` no início
+de um nome de nível superior tem sim um efeito: se você escrever `from mymod
+import *`, os nomes com um prefixo `+_+` não são importados de `mymod`.
+Entretanto, ainda é possível escrever `+from mymod import _privatefunc+`. Isso é
+explicado no
+https://fpy.li/67[_Tutorial
+de Python_, seção 6.1., "Mais sobre módulos"].] É fácil respeitar a privacidade
+de um objeto que marca seus atributos com um único `_`, da mesma forma que é
+fácil respeitar a convenção de tratar como constantes as variáveis com nomes
+inteiramente em maiúsculas.
+
+Atributos com um único `+_+` como prefixo são chamados "protegidos" em algumas
+partes da documentação de Python, por exemplo na documentação do módulo 
+https://fpy.li/68[_gettext_].
+A prática de "proteger" atributos por convenção com a forma
+`self._x` é muito difundida, mas chamar isso de atributo "protegido" não é tão
+comum. Alguns até falam em atributo "privado" nesses casos.
+
+Concluindo: os componentes de `Vector2d` são "privados" e nossas instâncias de
+`Vector2d` são "imutáveis"—com aspas irônicas—pois não há como tornar uns
+realmente privados e outras realmente imutáveis.footnote:[Se você acha este
+estado de coisas deprimente e desejaria que Python fosse mais parecido com o
+Java nesse aspecto, nem leia minha discussão sobre a força relativa do
+modificador `private` de Java no <>.]
+
+Vamos agora voltar à nossa classe `Vector2d`. Na próxima seção trataremos de um
+atributo especial (não um método) que 
+reduz o uso de memória das instâncias, sem afetar muito
+sua interface pública: `+__slots__+`.((("",
+startref="Aprivate11")))((("", startref="POprivate11")))
+
+
+[[slots_sec]]
+=== Economizando memória com `+__slots__+`
+
+Por((("Pythonic objects", "saving memory with
+__slots__",
+id="POslot11")))((("__slots__",
+id="slots11")))((("memory, saving with __slots__",
+id="memsave11"))) default, Python armazena os atributos de cada instância em um
+`dict` chamado `+__dict__+`. Como vimos em https://fpy.li/82[«Seção 3.9»] (vol.1), um
+`dict` ocupa um espaço significativo de memória, mesmo com as otimizações
+mencionadas naquela seção. Mas se você definir um atributo de classe chamado
+`+__slots__+` com uma sequência de nomes de atributos, Python usará um
+modelo alternativo de armazenamento para os atributos de instância: os atributos
+nomeados em `+__slots__+` serão armazenados em um array de referências oculto,
+que usa menos memória que um `dict`. Vamos ver como isso funciona através de
+alguns exemplos simples, começando pelo <>.
+
+<<<
+[[slots_ex1]]
+.A classe `Pixel` usa `+__slots__+`
+====
+[source, python]
+----
+include::../code/11-pythonic-obj/slots.rst[tags=PIXEL]
+----
+====
+
+<1> `+__slots__+` deve estar presente quando a classe é criada; acrescentá-lo ou
+modificá-lo posteriormente não tem efeito. Os nomes de atributos podem
+estar em uma `tuple` ou em uma `list`. Prefiro usar uma `tuple`, para deixar
+claro que não faz sentido modificá-la.
+
+<2> Cria uma instância de `Pixel` para testar, pois os efeitos de `+__slots__+` são vistos
+nas instâncias.
+
+<3> Primeiro efeito: instâncias de `Pixel` não têm um `+__dict__+`.
+
+<4> Define normalmente os atributos `p.x` e `p.y`.
+
+<5> Segundo efeito: tentar definir um atributo não listado em `+__slots__+` gera
+um `AttributeError`.
+
+Até aqui, tudo bem. Agora vamos criar uma subclasse de `Pixel`, no
+<>, para ver o lado contraintuitivo de `+__slots__+`.
+
+[[slots_ex2]]
+.`OpenPixel` é uma subclasse de `Pixel`
+====
+[source, python]
+----
+include::../code/11-pythonic-obj/slots.rst[tags=OPEN_PIXEL]
+----
+====
+<1> `OpenPixel` não declara qualquer atributo próprio.
+<2> Surpresa: instâncias de `OpenPixel` têm um `+__dict__+`.
+<3> Se você definir o atributo `x` (nomeado no `+__slots__+` da classe base `Pixel`)...
+<4> ...ele não será armazenado no `+__dict__+` da instância...
+<5> ...mas sim no array oculto de referências na instância.
+<6> Se você definir um atributo não nomeado no `+__slots__+`...
+<7> ...ele será armazenado no `+__dict__+` da instância.
+
+O <> mostra que o efeito de `+__slots__+` é herdado apenas
+parcialmente por uma subclasse. Para se assegurar que instâncias de uma
+subclasse não tenham o `+__dict__+`, é preciso declarar `+__slots__+` novamente
+na subclasse.
+
+Se você declarar `+__slots__ = ()+` (uma tupla vazia), as instâncias da
+subclasse não terão um `+__dict__+` e só aceitarão atributos nomeados no
+`+__slots__+` da classe base.
+
+Se você quiser que uma subclasse tenha atributos adicionais, basta nomeá-los em
+`+__slots__+`, como mostra o <>.
+
+[[slots_ex3]]
+.The `ColorPixel`, another subclass of `Pixel`
+====
+[source, python]
+----
+include::../code/11-pythonic-obj/slots.rst[tags=COLOR_PIXEL]
+----
+====
+
+<1> Em resumo, o `+__slots__+` da superclasse é adicionado ao `+__slots__+` da
+classe atual. Não esqueça que tuplas com um único elemento devem ter uma vírgula
+no final.
+
+<2> Instâncias de `ColorPixel` não tem um `+__dict__+`.
+
+<3> Você pode definir atributos declarados no `+__slots__+` dessa classe e nos
+de suas superclasses, mas nenhum outro.
+
+Curiosamente, também é possível colocar o nome `+'__dict__'+` em  `+__slots__+`. +
+Neste caso, as instâncias vão manter os atributos nomeados em `+__slots__+` num
+array de referências da instância, mas também vão aceitar atributos criados
+dinamicamente, que serão armazenados `+__dict__+`, como de costume.
+Isso é necessário para usar o decorador `@cached_property`, tratado na
+https://fpy.li/7x[«Seção 22.3.5»] (vol.3).
+
+Naturalmente, incluir `+'__dict__'+` em `+__slots__+` pode anular a economia
+de memória, dependendo do número de atributos estáticos e
+dinâmicos em cada instância, e de como eles são usados.
+Otimização descuidada é pior que otimização prematura:
+aumenta a complexidade sem trazer benefícios.
+
+Outro atributo de instância especial que você pode querer incluir é
+`+__weakref__+`, necessário para que objetos suportem referências fracas
+(mencionadas brevemente na https://fpy.li/86[«Seção 6.6»] (vol.1)). Esse atributo existe por default em
+instâncias de classes definidas pelo usuário. Entretanto, se a classe define
+`+__slots__+`, e é necessário que as instâncias possam ser alvo de referências
+fracas, então é preciso incluir  `+__weakref__+` entre os atributos nomeados em
+`+__slots__+`.
+
+Vejamos agora o efeito de `+__slots__+` em `Vector2d`.
+
+<<<
+==== Uma medida simples da economia gerada por `+__slots__+`
+
+<> mostra a implementação de `+__slots__+` em `Vector2d`.
+
+[[ex_vector2d_v3_slots]]
+.vector2d_v3_slots.py: o atributo `+__slots__+` é a única adição a `Vector2d`
+====
+[source, python]
+----
+include::../code/11-pythonic-obj/vector2d_v3_slots.py[tags=VECTOR2D_V3_SLOTS]
+    # methods are the same as previous version
+----
+====
+<1> `+__match_args__+` lista os nomes dos atributos públicos, para casamento de padrões posicionais.
+<2> `+__slots__+`, por outro lado, lista os nomes dos atributos de instância, que neste caso são atributos privados.
+
+Para medir a economia de memória, escrevi o script _mem_test.py_. Ele recebe,
+como argumento de linha de comando, o nome de um módulo com uma variante da
+classe `Vector2d`, e usa uma compreensão de lista para criar uma `list` com
+10.000.000 de instâncias de `Vector2d`. Na primeira execução, vista no
+<>, usei `vector2d_v3.Vector2d` (do <>); na
+segunda execução usei a versão com `+__slots__+` do <>.
+
+[[mem_test_demo]]
+.mem_test.py cria 10 milhões de instâncias de `Vector2d`, usando a classe definida no módulo nomeado
+====
+[source]
+----
+$ time python3 mem_test.py vector2d_v3
+Selected Vector2d type: vector2d_v3.Vector2d
+Creating 10,000,000 Vector2d instances
+Initial RAM usage:      6,983,680
+  Final RAM usage:  1,666,535,424
+
+real	0m11.990s
+user	0m10.861s
+sys	0m0.978s
+$ time python3 mem_test.py vector2d_v3_slots
+Selected Vector2d type: vector2d_v3_slots.Vector2d
+Creating 10,000,000 Vector2d instances
+Initial RAM usage:      6,995,968
+  Final RAM usage:    577,839,104
+
+real	0m8.381s
+user	0m8.006s
+sys	0m0.352s
+----
+====
+
+Como revela o <>, o uso de RAM do script cresce para 1,55 GB
+quando o `+__dict__+` de instância é usado em cada uma das 10 milhões de
+instâncias de `Vector2d`, mas isso se reduz a 551 MB quando `Vector2d` tem um
+atributo `+__slots__+`. A versão com `+__slots__+` também é mais rápida. O
+script _mem_test.py_ neste teste lida basicamente com o carregamento do módulo,
+a medição da memória utilizada e a formatação de resultados. O código-fonte pode
+ser encontrado no https://fpy.li/11-11[repositório
+_fluentpython/example-code-2e_].
+
+[TIP]
+====
+
+Se você precisa manipular milhões de objetos com dados numéricos, deveria na
+verdade estar usando os arrays da NumPy (veja a https://fpy.li/8h[«Seção 2.10.3»] (vol.1)), que são
+eficientes no de uso de memória, e também tem funções para processamento
+numérico extremamente otimizadas, muitas das quais operam sobre o array inteiro
+em paralelo. Projetei a classe `Vector2d` apenas como um contexto para a
+discussão de métodos especiais, pois sempre que possível tento evitar exemplos
+vagos com `Foo` e `Bar`.
+
+====
+
+[[problems_with_slots_sec]]
+==== Resumindo os problemas com __slots__
+
+O atributo de classe `+__slots__+` pode proporcionar uma economia significativa
+de memória se usado corretamente, mas existem algumas ressalvas:
+
+* É preciso lembrar de redeclarar `+__slots__+` em cada subclasse, para evitar
+que suas instâncias tenham um `+__dict__+`.
+
+* Instâncias só poderão ter os atributos listados em `+__slots__+`, a menos que
+`+__dict__+` seja incluído em `+__slots__+` (mas isso pode anular a economia de
+memória).
+
+* Classe que usam `+__slots__+` não podem usar o decorador `@cached_property`, a
+menos que nomeiem `+__dict__+` explicitamente em `+__slots__+`.
+
+* Instâncias não podem ser alvo de referências fracas, a menos que
+`+__weakref__+` seja incluído em `+__slots__+`.
+
+O último tópico do capítulo trata de sobrescrever de um atributo de classe
+em instâncias e subclasses.((("", startref="POslot11")))((("",
+startref="slots11")))((("", startref="memsave11")))
+
+[[overriding_class_attributes]]
+=== Sobrescrevendo atributos de classe
+
+Um((("Pythonic objects", "overriding class attributes",
+id="POoverride11")))((("attributes", "overriding class attributes",
+id="Aover11"))) recurso característico de Python é a forma como atributos de
+classe podem ser usados como valores default para atributos de instância.
+`Vector2d` contém o atributo de classe `typecode`.
+No método `+__bytes__+` eu o acesso como `self.typecode` de propósito.
+As instâncias de `Vector2d` são criadas sem um atributo `typecode` próprio,
+então a expressão `self.typecode` vai, por default, ler atributo de classe
+`Vector2d.typecode`.
+
+Agora, quando atribuimos valor a um atributo na instância—por exemplo,
+um atributo `typecode` na instância—o atributo de classe com o mesmo nome
+não é alterado.
+Mas daí em diante, sempre a expressão `self.typecode` aparecer,
+o `typecode` da instância será usado, na prática escondendo o
+atributo de classe de mesmo nome. Isso abre a possibilidade de customizar uma
+instância individual com um `typecode` diferente do padrão definido
+na classe `Vector2d`.
+
+O `Vector2d.typecode` default é `'d'`: isso significa que cada componente do
+vetor será representado como um número de ponto flutuante de precisão dupla e 8
+bytes de tamanho quando for exportado para `bytes`. Se definirmos o `typecode`
+de uma instância `Vector2d` como `'f'` antes da exportação, cada componente será
+exportado como um número de ponto flutuante de precisão simples e 4 bytes de
+tamanho. O <> demonstra isso.
+
+[NOTE]
+====
+Estamos falando de criar um novo atributo em uma instância, por isso o
+<> usa a implementação de `Vector2d` sem `+__slots__+`,
+como aparece no <>.
+====
+
+<<<
+
+[[typecode_instance_demo]]
+.Customizando uma instância para redefinir o atributo `typecode`, sobrescrevendo o valor do `typecode` da classe `Vector2d`
+====
+[source, python]
+----
+>>> from vector2d_v3 import Vector2d
+>>> v1 = Vector2d(1.1, 2.2)
+>>> dumpd = bytes(v1)
+>>> dumpd
+b'd\x9a\x99\x99\x99\x99\x99\xf1?\x9a\x99\x99\x99\x99\x99\x01@'
+>>> len(dumpd)  # <1>
+17
+>>> v1.typecode = 'f'  # <2>
+>>> dumpf = bytes(v1)
+>>> dumpf
+b'f\xcd\xcc\x8c?\xcd\xcc\x0c@'
+>>> len(dumpf)  # <3>
+9
+>>> Vector2d.typecode  # <4>
+'d'
+----
+====
+[role="pagebreak-before less_space"]
+<1> A representação default em `bytes` tem 17 bytes de comprimento.
+<2> Define `typecode` como `'f'` na instância `v1`.
+<3> Agora `bytes` tem 9 bytes de comprimento.
+<4> `Vector2d.typecode` não foi modificado;
+apenas a instância `v1` usa o `typecode` `'f'`.
+
+Isso deixa claro porque a exportação para `bytes` de um `Vector2d` tem um
+prefixo `typecode`: queríamos suportar a exportação de vetores com
+números de diferentes precisões.
+
+Para modificar um atributo de classe, é preciso redefini-lo diretamente na
+classe, e não através de uma instância. Poderíamos modificar o `typecode`
+default para todas as instâncias (que não tenham seu próprio `typecode`) assim:
+
+[source, python]
+----
+>>> Vector2d.typecode = 'f'
+----
+
+Porém, no Python, há uma maneira idiomática de obter um efeito mais permanente,
+e de ser mais explícito sobre a modificação. Como atributos de classe são
+públicos, eles são herdados por subclasses. Então é uma prática comum fazer a
+subclasse customizar um atributo da classe. As views baseadas em classes do
+Django usam amplamente essa técnica. O <> mostra como se
+faz.
+
+[[typecode_subclass_demo]]
+.O `ShortVector2d` é uma subclasse de `Vector2d`, que apenas sobrescreve o `typecode` default
+====
+[source, python]
+----
+>>> from vector2d_v3 import Vector2d
+>>> class ShortVector2d(Vector2d):  # <1>
+...     typecode = 'f'
+...
+>>> sv = ShortVector2d(1/11, 1/27)  # <2>
+>>> sv
+ShortVector2d(0.09090909090909091, 0.037037037037037035)  # <3>
+>>> len(bytes(sv))  # <4>
+9
+----
+====
+<1> Cria `ShortVector2d` como uma subclasse de `Vector2d`
+apenas para  sobrescrever o atributo de classe `typecode`.
+<2> Cria `sv`, uma instância de `ShortVector2d`, para demonstração.
+<3> Verifica o `repr` de `sv`.
+<4> Verifica que a quantidade de bytes exportados é 9, e não 17 como antes.
+
+Esse exemplo também explica porque não atribui a constante `'Vector2d'`
+ao `class_name` no método `+__repr__+`, optando por obter o nome da
+classe através de `+type(self).__name__+`:
+
+[source, python]
+----
+    # inside class Vector2d:
+
+    def __repr__(self):
+        class_name = type(self).__name__
+        return '{}({!r}, {!r})'.format(class_name, *self)
+----
+
+Se eu tivesse escrito o `class_name` explicitamente, subclasses de `Vector2d`
+como `ShortVector2d` teriam que sobrescrever `+__repr__+` só para mudar o
+`class_name`. Lendo o nome do `type` da instância, tornei `+__repr__+` mais
+seguro para ser herdado.
+
+Aqui termina nossa conversa sobre a criação de uma classe simples, que
+aproveita modelo de dados de Python para se adaptar bem ao restante da linguagem:
+oferecendo diferentes representações do objeto, fornecendo um código de formatação
+customizado, expondo atributos somente para leitura e suportando `hash()` para
+se integrar a conjuntos e mapeamentos.((("", startref="Aover11")))((("",
+startref="POoverride11")))
+
+
+=== Resumo do capítulo
+
+O((("Pythonic objects", "overview of"))) objetivo desse capítulo foi demonstrar
+o uso dos métodos especiais e as convenções na criação de uma classe pythônica
+bem comportada.
+
+Será _vector2d_v3.py_ (do <>) mais pythônica que
+_vector2d_v0.py_ (do <>)? A classe `Vector2d` em
+_vector2d_v3.py_ com certeza utiliza mais recursos de Python.
+Mas, decidir qual das duas implementações de `Vector2d` é mais adequada,
+depende do contexto onde a classe será usada. No _Zen of Python_,
+Tim Peter escreveu:
+
+[quote]
+____
+Simples é melhor que complexo.
+____
+
+Um objeto deve ser tão simples quanto seus requisitos exigem—e não um desfile
+de recursos da linguagem. Se o código é parte de uma aplicação, deve se
+concentrar no que é necessário para atender os usuários finais, e nada
+mais. Se o código for parte de uma biblioteca para uso por outros programadores,
+então é razoável oferecer comportamentos esperados por pythonistas,
+implementados através de métodos especiais.
+Por exemplo, `+__eq__+` pode não ser um requisito do negócio,
+mas torna a classe mais fácil de testar.
+
+Ao expandir o código do `Vector2d` meu objetivo foi criar um contexto para a
+discussão dos métodos especiais e outras convenções de programação em Python.
+Os exemplos neste capítulo demonstraram vários dos métodos especiais mencionados
+no https://fpy.li/1[«Capítulo 1»] (vol.1):
+
+* Métodos de representação de strings e bytes: `+__repr__+`, `+__str__+`,
+`+__format__+` e `+__bytes__+`
+
+* Métodos para reduzir um objeto a um número: `+__abs__+`, `+__bool__+` e
+`+__hash__+`
+
+* O operador `+__eq__+`, para facilitar testes e permitir _hashing_
+(juntamente com `+__hash__+`)
+
+Quando suportamos a conversão para `bytes`, também implementamos um construtor
+alternativo, `Vector2d.frombytes()`, que nos deu motivo para falar dos
+decoradores `@classmethod` (muito conveniente) e `@staticmethod` (não tão útil:
+funções a nível do módulo são mais simples). O método `frombytes` foi inspirado
+pelo método de mesmo nome na classe `array.array`.
+
+Vimos que a
+https://fpy.li/63[Mini-Linguagem de
+Especificação de Formato] é extensível, ao implementarmos um método
+`+__format__+` que analisa uma especificação de formato passada para 
+a função embutida `format(obj, fmt_spec)`, ou dentro de campos
+de substituição `f'{expr:fmt_spec}'` em f-strings, ou
+ainda strings usadas como alvo do método `str.format()`.
+
+Para preparar que instâncias de `Vector2d` sejam _hashable_, fizemos
+um esforço para torná-las imutáveis, ao menos prevenindo modificações
+acidentais, programando os atributos `x` e `y` como privados, e expondo-os como
+propriedades para leitura apenas. Então implementamos `+__hash__+` usando a
+técnica recomendada, aplicando o operador `^` (xor) aos _hashes_ dos atributos da
+instância.
+
+Discutimos a seguir a economia de memória e as ressalvas de se declarar um
+atributo `+__slots__+` em `Vector2d`. Como o uso de `+__slots__+` tem efeitos
+colaterais, ele só faz real sentido quando é preciso processar um número muito
+grande de instâncias—pense em milhões de instâncias, não apenas milhares. Em
+muitos destes casos, usar a https://fpy.li/pandas[pandas] pode ser a melhor
+opção.
+
+O último tópico tratado foi a sobrescrita de um atributo de classe acessado
+através das instâncias (por exemplo, `self.typecode`). Fizemos isso primeiro
+criando um atributo de instância, depois criando uma subclasse e sobrescrevendo
+o atributo no nível da classe.
+
+Por todo o capítulo, apontei como escolhas de design nos exemplos foram baseadas
+no estudo das APIs dos objetos padrão de Python. Se esse capítulo pode ser
+resumido em uma só frase, seria essa:
+
+[quote, Antigo provérbio chinês]
+____
+Para criar objetos pythônicos, observe como se comportam os objetos do Python.
+____
+
+[[pythonic_reading_sec]]
+=== Para saber mais
+
+Este((("Pythonic objects", "further reading on"))) capítulo tratou de vários dos
+métodos especiais do modelo de dados, então naturalmente as referências
+primárias são as mesmas do https://fpy.li/1[«Capítulo 1»] (vol.1), onde tivemos uma ideia geral do
+mesmo tópico. Por conveniência, vou repetir aquelas indicações aqui:
+
+Modelo de Dados, em A Referência da Linguagem Python::
+A maioria dos métodos usados neste capítulo estão
+documentados em https://fpy.li/69[«Customização básica»].
+
+_Python in a Nutshell_, 3rd ed. (Martelli, Ravenscroft & Holden)::
+Trata com profundidade dos métodos especiais .
+
+_Python Cookbook_, 3rd ed. (Beazley & Jones):: 
+Práticas modernas de Python demonstradas através de receitas. Especialmente o Capítulo 8,
+_Classes and Objects_, que traz várias receitas
+relacionadas às discussões deste capítulo.
+
+_Python Essential Reference_, 4th ed. (Beazley)::
+Trata do modelo de dados em detalhes, apesar de falar apenas de Python 2.6 e do 3.0
+(na quarta edição). Todos os conceitos fundamentais são os mesmos,
+e a maior parte das APIs do Modelo de Dados não mudou nada desde Python 2.2,
+quando aconteceu a unificação dos tipos embutidos e classes definidas pelo usuário.
+
+Em 2015—o ano que terminei a primeira edição de _Python Fluente_—Hynek Schlawack
+começou a desenvolver o pacote `attrs`. Da documentação de `attrs`:
+
+[quote]
+____
+
+`attrs` é um pacote Python que vai trazer de volta a *alegria* de *criar
+classes*, liberando você do tedioso trabalho de implementar protocolos de objeto
+(também conhecidos como métodos _dunder_)
+____
+
+Mencionei `attrs` como uma alternativa mais poderosa ao `@dataclass` na
+https://fpy.li/85[«Seção 5.10»] (vol.1). As fábricas de classes de dados do
+https://fpy.li/5[«Capítulo 5»] (vol.1),
+assim como `attrs`, automaticamente equipam suas classes com vários métodos
+especiais. Mas saber como programar métodos especiais ainda é essencial para
+entender o que aqueles pacotes fazem, para decidir se você realmente precisa
+deles e para sobrescrever os métodos que eles geram, quando necessário.
+
+Vimos neste capítulo todos os métodos especiais relacionados à representação de
+objetos, exceto `+__index__+` e `+__fspath__+`. Discutiremos `+__index__+` no
+<>, na <>. Não vou tratar de `+__fspath__+`.
+Para aprender sobre esse método, veja a
+https://fpy.li/pep519[_PEP 519—Adding a file system path protocol_]
+(Adicionando um protocolo de caminho de sistema de
+arquivos).
+
+A necessidade de diferentes strings de representação para
+objetos apareceu primeiro em Smalltalk. O artigo
+https://fpy.li/11-13[_How to Display an Object as a String: printString and displayString_]
+(Como Exibir um Objeto como uma String: printString e displayString), de Bobby Woolf,
+discute a implementação dos métodos `printString` e `displayString`
+na linguagem Smalltalk em 1996.
+Foi lá que encontrei as descrições
+"como o desenvolvedor quer vê-lo" para a `repr()` 
+e "como o usuário quer vê-lo" para a `str()`, na <>.
+
+
+[role="pagebreak-before less_space"]
+[[pythonic_soapbox]]
+.Ponto de Vista
+****
+**Proteção versus segurança em atributos privados**
+
+[quote, Larry Wall, criador da linguagem Perl]
+____
+O Perl não tem nenhum amor por privacidade forçada.
+Ele preferiria que você não entrasse em sua sala de estar [apenas]
+por não ter sido convidado, e
+não porque ele tem uma espingarda.
+____
+
+Python((("Soapbox sidebars", "safety versus security in private
+attributes")))((("attributes", "safety versus security in private"))) e Perl
+estão em polos opostos em vários aspectos, mas Guido e Larry parecem concordar
+sobre a privacidade de objetos.
+
+Ensinando Python para muitos programadores Java ao longo do anos, percebi que
+muitos têm uma fé excessiva nas garantias de "privacidade" oferecidas pelo
+Java. Na verdade, os modificadores `private` e `protected` de Java normalmente
+fornecem defesas apenas contra acidentes (isto é, proteção). Eles só oferecem
+segurança contra ataques mal-intencionados se a aplicação for especialmente
+configurada e implantada sob um https://fpy.li/11-15[`SecurityManager`] de
+Java, e isso raramente acontece na prática, mesmo em instalações corporativas
+preocupadas com segurança.
+
+Para provar meu argumento, considere a classe Java a seguir.
+
+[[ex_java_confidential_class]]
+.Confidential.java: uma classe Java com um campo privado chamado `secret`
+====
+[source, java]
+----
+include::../code/11-pythonic-obj/private/Confidential.java[]
+----
+====
+
+No <>, armazeno o `text` no campo `secret` após
+convertê-lo todo para caixa alta, para deixar óbvio que o argumento `text`
+passado para o construtor sofre uma transformação antes de ser armazenado.
+  
+A verdadeira demonstração consiste em rodar _expose.py_ com Jython. Este
+script usa introspecção (_reflection_ ou reflexão no jargão de Java) para acessar
+o valor de um campo privado. O código aparece no <>.
+
+[[ex_expose_py]]
+.expose.py: código em Jython para ler o conteúdo de um campo privado em outra classe
+====
+[source, python]
+----
+include::../code/11-pythonic-obj/private/expose.py[]
+----
+====
+
+Executando o <>, o resultado é esse:
+
+[source]
+----
+$ jython expose.py
+message.secret = TOP SECRET TEXT
+----
+
+A string `'TOP SECRET TEXT'` foi lida do campo privado `secret` da classe `Confidential`.
+
+Não há magia aqui: _expose.py_ usa a API de reflexão de Java para obter uma
+referência para o campo privado chamado `'secret'`, e então chama
+`secret_field.setAccessible(True)` para tornar acessível seu conteúdo. A mesma
+coisa pode ser feita com código Java, claro (mas exige mais que o triplo de
+linhas; veja o arquivo https://fpy.li/11-16[_Expose.java_] no 
+https://fpy.li/code[repositório de código] deste livro.
+
+A chamada `.setAccessible(True)` só falhará se o script Jython ou o programa
+principal em Java (por exemplo, `Expose.class`) estiverem rodando sob a
+supervisão de um https://fpy.li/11-15[`SecurityManager`]. Mas, no mundo
+real, aplicações Java raramente são implantadas com um `SecurityManager`—com a
+exceção das _applets_ Java, quando elas ainda eram suportadas pelos navegadores.
+
+Meu ponto: também em Java, na prática os modificadores de controle de acesso
+oferecem proteção mas não segurança. Então relaxe e aprecie o poder dado a
+você pelo Python. E use esse poder com 
+responsabilidade.((("", startref="POsoap11")))
+
+**Propriedades ajudam a reduzir custos iniciais**
+
+Nas((("attributes", "properties and up-front costs")))((("Pythonic objects",
+"Soapbox discussion", id="POsoap11")))((("Soapbox sidebars", "properties and
+up-front costs"))) primeiras versões de `Vector2d`, os atributos `x` e `y` eram
+públicos, como são, por default, todos os atributos de instância e classe no
+Python. Naturalmente, os usuários de vetores precisam acessar seus componentes.
+Apesar de nossos vetores serem iteráveis e poderem ser desempacotados em um par
+de variáveis, também é desejável poder escrever `my_vector.x` e `my_vector.y`
+para obter cada componente.
+
+Quando surge a necessidade de proteger os atributos
+`x` e `y`, implementamos propriedades, mas nada muda no restante do código ou
+na interface pública de `Vector2d`, como se verifica através dos doctests.
+Continuamos podendo acessar `my_vector.x` e `my_vector.y`.
+
+Isso mostra que podemos sempre iniciar o desenvolvimento de nossas classes da
+maneira mais simples possível, com atributos públicos, pois quando (ou se) for
+preciso impor restrições depois, com _getters_ e _setters_, eles podem ser
+implementados usando propriedades, sem mudar nada no código que já interage com
+nossos objetos através dos nomes que eram, inicialmente, simples atributos
+públicos como `x` e `y` em nosso exemplo.
+
+Essa abordagem é o oposto daquilo que é encorajado pela linguagem Java: um
+programador Java não pode começar com atributos públicos simples e apenas mais
+tarde, se necessário, implementar propriedades, porque elas não existem naquela
+linguagem. Portanto, escrever _getters_ e _setters_ é a regra em Java—mesmo
+quando esses métodos não fazem nada de útil—porque a API não pode evoluir de
+atributos públicos simples para _getters_ e _setters_ sem quebrar todo o código
+que já use aqueles atributos.
+
+Além disso, como Martelli, Ravenscroft e Holden observam no 
+https://fpy.li/pynut3[Python in a Nutshell 3rd ed.],
+digitar chamadas a _getters_ e _setters_ por toda parte é
+patético. Você é obrigado a escrever coisas como:
+
+[source, python]
+----
+>>> my_object.set_foo(my_object.get_foo() + 1)
+----
+
+Apenas para fazer isso:
+
+[source, python]
+----
+>>> my_object.foo += 1
+----
+
+Ward Cunningham, inventor do wiki e um pioneiro da Programação Extrema (_Extreme
+Programming_), recomenda perguntar: "Qual a coisa mais simples que tem alguma
+chance de funcionar?" A ideia é se concentrar no objetivo.footnote:[Veja
+https://fpy.li/11-14[_Simplest Thing that Could Possibly Work: A Conversation
+with Ward Cunningham, Part V_] (A Coisa Mais Simples que Poderia Funcionar: Uma
+Conversa com Ward Cunningham, Parte V).] Implementar _setters_ e _getters_
+desde o início é um desvio em relação ao objetivo. Em Python, podemos
+simplesmente usar atributos públicos, sabendo que podemos transformá-los mais
+tarde em propriedades, se essa necessidade surgir.
+
+****
+
+
+<<<
diff --git a/vol2/cap12.adoc b/vol2/cap12.adoc
new file mode 100644
index 0000000..0ffaac8
--- /dev/null
+++ b/vol2/cap12.adoc
@@ -0,0 +1,1439 @@
+[[ch_seq_methods]]
+== Métodos especiais para sequências
+:example-number: 0
+:figure-number: 0
+
+[quote, Alex Martelli]
+____
+Não queira saber se aquilo _é_-um pato: veja se ele _grasna_-como-um pato, +
+_anda_-como-um pato, etc., etc., dependendo de qual subconjunto de
+comportamentos de pato você precisa usar em seu jogo de palavras. (`comp.lang.python`, Jul. 26, 2000)
+____
+
+Neste((("sequences, special methods for", "topics covered")))((("Vector class, multidimensional",
+"topics covered"))) capítulo, vamos criar uma classe `Vector`,
+para representar um vetor multidimensional—um avanço significativo sobre o `Vector2D` bidimensional do <>.
+`Vector` vai se comportar como uma sequência plana imutável como outras que existem em Python.
+Seus elementos serão números de ponto flutuante, e ao final do capítulo a classe suportará o seguinte:
+
+* O protocolo de sequência básico: `+__len__+` e `+__getitem__+`
+* Representação abreviada de instâncias com  muitos itens
+* Suporte adequado a fatiamento, produzindo novas instâncias de `Vector`
+* _Hashing_ agregado, considerando cada elemento contido na sequência
+* Um extensão customizada da linguagem de formatação
+
+Também vamos implementar, com `+__getattr__+`, o acesso dinâmico a atributos,
+como forma de substituir as propriedades apenas para leitura
+que usamos no `Vector2d`—apesar disso não ser comum em sequências.
+
+Além disso, teremos uma discussão conceitual sobre a ideia de protocolos como interfaces informais.
+Vamos discutir a relação entre protocolos e a tipagem pato (_duck typing_),
+e as implicações práticas disso na criação de seus próprios tipos.
+
+=== Novidades neste capítulo
+
+Não((("sequences, special methods for", "significant changes to"))) fiz grandes
+mudanças neste capítulo. Há uma breve discussão nova sobre o `typing.Protocol`
+em um quadro de dicas, no final da <>.
+
+Na <>, a implementação do `+__getitem__+` no <>
+está mais concisa e robusta que o exemplo na primeira edição, graças ao _duck
+typing_ e ao `operator.index`. Essa mudança foi replicada para as implementações
+seguintes de `Vector` aqui e no <>.
+
+Vamos começar.
+
+=== Vector: tipo sequência definido pelo usuário
+
+Nossa((("sequences, special methods for", "Vector implementation
+strategy")))((("Vector class, multidimensional", "implementation strategy")))
+estratégia na implementação de `Vector` será usar composição, não herança. Vamos
+armazenar os componentes em um array de números de ponto flutuante, e
+implementar os métodos necessários para que nossa classe `Vector` se comporte
+como uma sequência plana imutável.
+
+Mas antes de implementar os métodos de sequência, vamos desenvolver uma
+implementação básica de `Vector` compatível com nossa classe `Vector2d`, vista
+anteriormente--exceto onde tal compatibilidade não fizer sentido.
+
+.Aplicações de vetores além de três dimensões
+****
+
+Quem((("Vector class, multidimensional", "applications beyond three
+dimensions")))((("sequences, special methods for", "applications beyond three
+dimensions"))) precisa de vetores com 1.000 dimensões? Vetores N-dimensionais
+(com valores grandes de N) são bastante utilizados em recuperação de informação,
+onde documentos e consultas textuais são representados como vetores, com uma
+dimensão para cada palavra. Isso se chama
+https://fpy.li/6b[Modelo vetorial].
+Nesse modelo, a métrica fundamental de relevância é a
+__similaridade de cosseno__—o cosseno do ângulo entre um vetor que representa a
+consulta e um vetor representando um documento. Conforme o ângulo diminui, o valor
+do cosseno aumenta, indicando a relevância do documento para aquela consulta:
+cosseno próximo de 1 significa alta relevância; próximo de 0 indica baixa
+relevância.
+
+Dito isto, a classe `Vector` nesse capítulo é um exemplo didático. O objetivo é
+apenas demonstrar alguns métodos especiais de Python no contexto de um tipo
+sequência, sem grandes conceitos matemáticos.
+
+A NumPy e a SciPy são as ferramentas que você precisa para fazer cálculos
+vetoriais em aplicações reais. O pacote https://fpy.li/12-2[_gensim_] do PyPi, de
+Radim Řehůřek, implementa a modelagem de espaço vetorial para processamento de
+linguagem natural e recuperação de informação, usando a NumPy e a SciPy.
+
+****
+
+[[vector_take1_sec]]
+=== Vector versão #1: compatível com Vector2d
+
+A((("Vector class, multidimensional", "Vector2d compatibility", id="VCM2d12")))((("sequences, special methods for", "Vector2d compatibility", id="SSM2d12"))) primeira versão de `Vector` deve ser tão compatível quanto possível com nossa classe `Vector2d` desenvolvida anteriormente.
+
+Entretanto, pela((("__repr__")))((("__init__"))) própria natureza das classes, o construtor de `Vector` não é compatível com o construtor de `Vector2d`. Poderíamos fazer `Vector(3, 4)` e `Vector(3, 4, 5)` funcionarem, recebendo argumentos arbitrários com `*args` em `+__init__+`. Mas a melhor prática para um construtor de sequências é receber os dados através de um argumento iterável, como fazem todos os tipos embutidos de sequências.
+O <> mostra algumas maneiras de instanciar objetos do nosso novo `Vector`.
+
+[[ex_vector_demo]]
+.Testes de `+Vector.__init__+` e `+Vector.__repr__+`
+====
+[source, python]
+----
+>>> 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, ...])
+----
+====
+
+Exceto pela nova assinatura do construtor, verifiquei que todos os testes
+realizados com `Vector2d` (por exemplo, `Vector2d(3, 4)`) passam e
+produzem os mesmos resultados com um `Vector` de dois componentes,
+como `Vector([3, 4])`.
+
+[WARNING]
+====
+Quando um `Vector` tem mais de seis componentes, a string produzida por `repr()` é abreviada com
+`\...`, como visto na última linha do <>. Isso é fundamental para qualquer tipo de coleção que possa conter um número grande de itens, pois `repr` é usado na depuração—e você não quer que um único objeto grande ocupe milhares de linhas em seu console ou arquivo de log. Use o módulo `reprlib` para produzir representações de tamanho limitado, como no <>. O módulo `reprlib` se chamava `repr` no Python 2.7.
+====
+
+O <> é a primeira versão de `Vector`
+baseada no <> e <> do <>.
+
+[[ex_vector_v1]]
+.vector_v1.py: baseado em vector2d_v1.py
+====
+[source, python]
+----
+include::../code/12-seq-hacking/vector_v1.py[tags=VECTOR_V1]
+----
+====
+
+<1> O atributo de instância "protegido" `self._components` vai manter um `array`
+com os componentes do `Vector`.
+
+<2> Para permitir iteração, devolvemos um itereador sobre
+`self._components`; a função `iter()` é assunto do https://fpy.li/17[«Capítulo 17»] (vol.3),
+juntamente com o método `+__iter__+`.
+
+<3> Usa `reprlib.repr()` para obter um representação de tamanho limitado de
+`self._components` (por exemplo, `+array('d', [0.0, 1.0, 2.0, 3.0, 4.0, ...])+`).
+
+<4> Remove o prefixo `array('d',` e o `)`  final, antes de inserir a string em
+uma chamada ao construtor de `Vector`.
+
+<5> Cria um objeto `bytes` diretamente de `self._components`.
+
+<6> Desde o Python 3.8, `math.hypot` aceita pontos N-dimensionais. Já usei a
+seguinte expressão antes: `math.sqrt(sum(x * x for x in self))`.
+
+<7> A única mudança necessária no `frombytes` anterior é na última linha:
+passamos a `memoryview` diretamente para o construtor, sem desempacotá-la com
+`*`, como fazíamos antes.
+
+O uso de `reprlib.repr` merece uma explicação.
+Essa função produz representações seguras de estruturas grandes ou recursivas,
+limitando a tamanho da string devolvida e indicando a abreviação com `'\...'`.
+Eu queria que o `repr` de um `Vector` se parecesse com `Vector([3.0, 4.0, 5.0])` e
+não com `Vector(array('d', [3.0, 4.0, 5.0]))`, porque a existência de um `array`
+dentro de um `Vector` é um detalhe de implementação. Como essas chamadas ao
+construtor criam objetos `Vector` idênticos, preferi a sintaxe mais simples,
+usando um argumento `list`.
+
+Ao escrever o `+__repr__+`, eu poderia construir uma string para exibir
+`components` com este código:
+`reprlib.repr(list(self._components))`.
+Mas isto teria um custo adicional, pois eu estaria copiando cada item de `self._components` para uma
+`list` só para usar a `list` no `repr`. Em vez disso, decidi aplicar
+`reprlib.repr` diretamente no array `self._components`, e então remover os
+caracteres fora dos `[]`. É isso o que faz a segunda linha do `+__repr__+` no
+<>.
+
+[TIP]
+====
+Por seu papel na depuração, chamar `repr()` em um objeto não deveria nunca gerar uma exceção.
+Se alguma coisa der errado dentro de sua implementação de `+__repr__+`,
+você deve lidar com o problema e fazer o melhor possível para produzir uma saída aproveitável,
+que dê ao usuário uma chance de identificar o objeto receptor (`self`).
+====
+
+Observe que os métodos `+__str__+`, `+__eq__+`, e `+__bool__+` são idênticos a
+suas versões em  `Vector2d`, e apenas um caractere mudou em `frombytes`
+(retirei um `*` na última linha). Esta é uma das vantagens de fazer o
+`Vector2d` original iterável.
+
+Poderíamos criar `Vector` como uma subclasse de `Vector2d`, mas
+escolhi não fazer assim por duas razões. Em primeiro lugar, os construtores
+são incompatíveis, o que torna relação de super/subclasse desaconselhável,
+por violar o
+https://fpy.li/6c[princípio de substituição de Liskov].
+Seria possível contornar isso como um tratamento engenhoso dos argumenos em
+`+__init__+`, mas a segunda razão é mais importante: eu queria que `Vector` fosse
+um exemplo independente de uma classe que implementa o protocolo de sequência.
+É o que faremos a seguir, após uma discussão sobre o termo _protocolo_.((("",
+startref="VCM2d12")))((("", startref="SSM2d12")))
+
+[[protocol_duck_sec]]
+=== Protocolos e a tipagem pato
+
+Desde((("Vector class, multidimensional",
+"protocols and duck typing")))((("sequences, special methods for",
+"protocols and duck typing")))((("protocols", "duck typing and")))((("duck typing")))
+o primeiro capítulo vimos que não é necessário herdar de qualquer classe específica
+para criar um tipo sequência completamente funcional em Python;
+basta implementar os métodos que satisfazem o protocolo de sequência.
+Mas de que tipo de protocolo estamos falando?
+
+No contexto da programação orientada a objetos, um protocolo é uma interface
+informal, definida apenas na documentação (e não no código). Por exemplo, o
+protocolo de sequência no Python implica apenas no métodos `+__len__+` e
+`+__getitem__+`. Qualquer classe `Spam`, que implemente esses métodos com a
+assinatura e a semântica padrão, pode ser usada em qualquer lugar onde uma
+sequência é esperada. É irrelevante se `Spam` é uma subclasse dessa ou daquela
+outra classe; tudo o que importa é que ela fornece os métodos necessários. Vimos
+isso no https://fpy.li/8x[«Exemplo 1 do Capítulo 1»] (vol.1), reproduzido no <>.
+
+[[ex_pythonic_deck_rep]]
+.Código do https://fpy.li/8x[«Exemplo 1 do Capítulo 1»] (vol.1), repetido aqui
+====
+[source, python]
+----
+include::../code/01-data-model/frenchdeck.py[]
+----
+====
+
+A classe `FrenchDeck`, no <>, pode tirar proveito de
+muitas facilidades de Python por implementar o protocolo de sequência, mesmo que
+isso não esteja declarado em qualquer ponto do código. Um programador Python
+experiente vai olhar para ela e entender que aquilo _é_ uma sequência, mesmo
+sendo apenas uma subclasse de `object`.
+Dizemos que ela _é_ uma sequênca porque ela _se comporta_ como uma sequência.
+Esta abordagem ficou conhecida como _duck typing_ (literalmente "tipagem pato"),
+após o post de Alex Martelli citado no início deste capítulo.
+
+Como protocolos são informais e não obrigatórios, muitas vezes é possível
+resolver nosso problema implementando apenas parte de um protocolo,
+se exatamente como a classe será utilizada.
+Por exemplo, apenas `+__getitem__+` é necessário para suportar iteração;
+não é preciso implemtar `+__len__+`.
+
+
+[TIP]
+====
+
+Com((("protocol classes")))((("protocols", "static protocols"))) a
+https://fpy.li/pep544[_PEP 544—Protocols: Structural subtyping (static duck typing)_]
+(Protocolos: sub-tipagem estrutural (tipagem pato estática))],
+o Python 3.8 suporta _classes protocolo_: subclasses de `typing.Protocol`,
+que estudamos na https://fpy.li/8m[«Seção 8.5.10»] (vol.1).
+Este novo uso da palavra "protocolo" no Python tem um significado parecido, mas não idêntico.
+Quando preciso diferenciá-los, escrevo((("static protocols", "versus dynamic protocols",
+secondary-sortas="dynamic protocols")))
+"protocolo estático" para me referir a um protocolos formalizado por uma classe
+subclasse de `typing.Protocol`, e((("dynamic protocols")))
+"protocolo dinâmico" para me referir ao sentido tradicional.
+Uma diferença fundamental é que
+uma implementação de um protocolo estático precisa oferecer todos os métodos
+definidos na classe protocolo. A <>
+apresentará muito mais detalhes.
+
+====
+
+Vamos agora implementar o protocolo de sequência em `Vector`,
+primeiro sem suporte adequado ao fatiamento, que acrescentaremos mais tarde.
+
+
+[[sliceable_sequence_sec]]
+=== Vector versão #2: sequência fatiável
+
+Como((("Vector class, multidimensional", "sliceable sequences",
+id="VCMslice12")))((("sequences, special methods for", "sliceable sequences",
+id="SSMslice12")))((("slicing", "sliceable sequences",
+id="Sslseq12")))((("__len__",
+id="len12")))((("__getitem__", id="getitem12")))
+vimos no exemplo da classe `FrenchDeck`, suportar o protocolo de sequência é
+muito fácil se você puder delegar para um atributo sequência em seu objeto, como
+nosso array `self._components`. Esses `+__len__+` e `+__getitem__+` de uma linha
+são um bom começo:
+
+[source, python]
+----
+class Vector:
+    # muitas linhas omitidas...
+
+    def __len__(self):
+        return len(self._components)
+
+    def __getitem__(self, index):
+        return self._components[index]
+----
+
+Com tais acréscimos, as seguintes operações funcionam:
+
+[source, python]
+----
+>>> v1 = Vector([3, 4, 5])
+>>> len(v1)
+3
+>>> v1[0], v1[-1]
+(3.0, 5.0)
+>>> v7 = Vector(range(7))
+>>> v7[1:4]
+array('d', [1.0, 2.0, 3.0])
+----
+
+Como se vê, até o fatiamento é suportado—mas não muito bem. Seria melhor se uma
+fatia de um `Vector` fosse também uma instância de `Vector`, e não um `array`.
+A classe `FrenchDeck` do primeiro capítulo tem o mesmo problema: quando fatiamos,
+obtemos uma `list`. No caso de `Vector`, perdemos muita funcionalidade
+quando o fatiamento devolve um simples array.
+
+Considere os tipos sequência embutidos: cada um deles, ao ser fatiado, produz
+uma nova instância de seu próprio tipo, e não de um outro tipo.
+
+Para fazer `Vector` produzir fatias como instâncias de `Vector`, não podemos
+simplesmente delegar o fatiamento para `array`. Precisamos analisar os
+argumentos recebidos em `+__getitem__+` e fazer a coisa certa.
+
+Vejamos agora como Python transforma a sintaxe `my_seq[1:3]` em argumentos para
+`+my_seq.__getitem__(...)+`.
+
+
+[[how_slicing_works_sec]]
+==== Como funciona o fatiamento
+
+Uma demonstração vale mais que mil palavras, então veja o <>.
+
+[[ex_slice0]]
+.Examinando o comportamento de `+__getitem__+` e fatias
+====
+[source, python]
+----
+>>> class MySeq:
+...     def __getitem__(self, index):
+...         return index  # <1>
+...
+>>> s = MySeq()
+>>> s[1]  # <2>
+1
+>>> s[1:4]  # <3>
+slice(1, 4, None)
+>>> s[1:4:2]  # <4>
+slice(1, 4, 2)
+>>> s[1:4:2, 9]  # <5>
+(slice(1, 4, 2), 9)
+>>> s[1:4:2, 7:9]  # <6>
+(slice(1, 4, 2), slice(7, 9, None))
+----
+====
+<1> Para essa demonstração, o método `+__getitem__+` simplesmente devolve o que for passado a ele.
+<2> Um único índice, nada de novo.
+<3> A notação `1:4` se torna `slice(1, 4, None)`.
+<4> `slice(1, 4, 2)` significa comece em 1, pare em 4, ande de 2 em 2.
+<5> Surpresa: a presença de vírgulas dentro do `[]` significa que `+__getitem__+` recebe uma tupla.
+<6> A tupla pode inclusive conter vários objetos `slice`.
+
+Vamos agora olhar mais de perto a própria classe `slice`, no <>.
+
+[[ex_slice1]]
+.Inspecionando os atributos da classe `slice`
+====
+[source, python]
+----
+>>> slice  # <1>
+
+>>> dir(slice) # <2>
+['__class__', '__delattr__', '__dir__', '__doc__', '__eq__',
+ '__format__', '__ge__', '__getattribute__', '__gt__',
+ '__hash__', '__init__', '__le__', '__lt__', '__ne__',
+ '__new__', '__reduce__', '__reduce_ex__', '__repr__',
+ '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
+ 'indices', 'start', 'step', 'stop']
+----
+====
+<1> `slice` é um tipo embutido (que já vimos antes na https://fpy.li/8p[«Seção 2.7.2»] (vol.1)).
+<2> Inspecionando uma `slice` descobrimos os atributos de dados
+`start`, `stop`, e `step`, e um método `indices`.
+
+No <>, a chamada `dir(slice)` revela um atributo `indices`, um método
+pouco conhecido mas muito interessante. Eis o que diz `help(slice.indices)`:
+
+`S.indices(len) {rt-arrow} (start, stop, stride)`::
+  Supondo uma sequência de tamanho `len`, calcula os índices `start` (_início_) e `stop` (_fim_), e a extensão do `stride` (_passo_) da fatia estendida descrita por `S`. Índices fora dos limites são recortados, exatamente como acontece em uma fatia normal.
+
+<<<
+Em outras palavras, o método `indices` expõe a lógica complexa implementada nas
+sequências embutidas, para tratar índices inexistentes ou
+negativos e fatias maiores que a sequência original. Esse método produz
+tuplas "normalizadas" com os inteiros não-negativos `start`, `stop`, e `stride`
+ajustados para uma sequência de um dado tamanho.
+
+Aqui estão dois exemplos. Imagine que estamos lidando com
+uma sequência de `len == 5`, por exemplo `'ABCDE'`.
+Neste casos, passamos o valor `5` para `indices`:
+
+[source, python]
+----
+>>> slice(None, 10, 2).indices(5)  # <1>
+(0, 5, 2)
+>>> slice(-3, None, None).indices(5)  # <2>
+(2, 5, 1)
+----
+<1> `'ABCDE'[:10:2]` é o mesmo que `'ABCDE'[0:5:2]`.
+<2> `'ABCDE'[-3:]` é o mesmo que  `'ABCDE'[2:5:1]`.
+
+No código de nosso `Vector` não vamos precisar do método `slice.indices()`,
+pois quando recebermos uma fatia como argumento vamos
+delegar seu tratamento para o `array` interno `_components`.
+Mas quando você não puder contar com  os serviços de uma sequência subjacente,
+esse método poupa o trabalho de implementar uma lógica sutil.
+
+Agora que sabemos como tratar fatias, vamos ver a implementação aperfeiçoada de `+Vector.__getitem__+`.
+
+[[slice_aware_sec]]
+==== Um __getitem__ que trata fatias
+
+O <> lista os dois métodos necessários para fazer `Vector` se comportar como uma sequência: `+__len__+` e `+__getitem__+` (com o último implementado para tratar corretamente o fatiamento).
+
+[[ex_vector_v2]]
+.Parte de vector_v2.py: métodos `+__len__+` e `+__getitem__+` adicionados à classe `Vector`, de vector_v1.py (no <>)
+====
+[source, python]
+----
+include::../code/12-seq-hacking/vector_v2.py[tags=VECTOR_V2]
+----
+====
+<1> Se o argumento `key` é uma `slice`...
+<2> ...obtém a classe da instância (isto é, `Vector`) e...
+<3> ...invoca a classe para criar outra instância de `Vector` a partir de uma fatia do array `_components`.
+<4> Se podemos obter um `index` de `key`...
+<5> ...devolve o item específico de `_components`.
+
+A função `operator.index()` chama o método especial `+__index__+`.
+A função e o método especial foram definidos na
+https://fpy.li/pep357[_PEP 357—Allowing Any Object to be Used for Slicing_]
+(Permitir que Qualquer Objeto seja Usado para Fatiamento),
+proposta por Travis Oliphant, para permitir que qualquer um dos numerosos tipos
+de inteiros na NumPy fossem usados como argumentos de índices e fatias. A
+diferença essencial entre `operator.index()` e `int()` é que a primeira foi
+projetada para o propósito específico de obter índices.
+Por exemplo, `int(3.14)` devolve `3`,
+mas `operator.index(3.14)` gera um `TypeError`,
+porque não faz sentido tentar usar um `float` como índice de um array.
+
+
+[NOTE]
+====
+O uso excessivo de `isinstance` pode ser um sinal de design orientado a objetos ruim, mas tratar fatias em `+__getitem__+` é um caso de uso justificável.
+Na primeira edição, também usei um teste `isinstance` com `key`, para checar se esse argumento era um inteiro.
+O uso de `operator.index` evita esse teste, e gera um `TypeError` com uma mensagem muito informativa, se não for possível obter o `index` a partir de `key`.
+Observe a última mensagem de erro no <>, abaixo.
+====
+
+Após a adição do código do <> à classe `Vector` class, temos o comportamento apropriado para fatiamento, como demonstra o  <> .
+
+[[ex_vector_v2_demo]]
+.Testes do `+Vector.__getitem__+` aperfeiçoado, do <>
+====
+[source, python]
+----
+include::../code/12-seq-hacking/vector_v2.py[tags=VECTOR_V2_DEMO]
+----
+====
+<1> Um índice inteiro recupera apenas o valor de um componente, um `float`.
+<2> Uma fatia como índice cria um novo `Vector`.
+<3> Um fatia de `len == 1` também cria um `Vector`.
+<4> `Vector` não suporta indexação multidimensional, então tuplas de índices ou de fatias geram um erro.((("", startref="getitem12")))((("", startref="len12")))((("", startref="Sslseq12")))((("", startref="SSMslice12")))((("", startref="VCMslice12")))
+
+[[vector_dynamic_attrs_sec]]
+=== Vector versão #3: atributos dinâmicos
+
+Ao((("Vector class, multidimensional", "dynamic attribute access",
+id="VCMdyn12")))((("sequences, special methods for", "dynamic attribute access",
+id="SSMdyn12")))((("__getattr__",
+id="getattr12")))((("attributes", "dynamic attribute access", id="Adyn12")))
+evoluir `Vector2d` para `Vector`, perdemos a habilidade de acessar os
+componentes do vetor por nome (por exemplo, `v.x`, `v.y`). Agora estamos
+trabalhando com vetores que podem ter um número grande de componentes. Ainda
+assim, pode ser conveniente acessar os primeiros componentes usando letras como
+atalhos, como `v.z` em vez de `v[2]`.
+
+Esta é a sintaxe alternativa que queremos oferecer para a leitura dos quatro
+primeiros componentes de um vetor:
+
+[source, python]
+----
+>>> v = Vector(range(10))
+>>> v.x
+0.0
+>>> v.y, v.z, v.t
+(1.0, 2.0, 3.0)
+----
+
+No `Vector2d`, oferecemos acesso somente para leitura a `x` e `y` através do
+decorador `@property` (veja o <> do <>). Poderíamos incluir quatro
+propriedades no `Vector`, mas isso seria tedioso. O método especial
+`+__getattr__+` é uma opção melhor.
+
+O método `+__getattr__+` é invocado só quando a busca por um
+atributo falha. Simplificando, dada a expressão `my_obj.x`, Python verifica se a
+instância de `my_obj` tem um atributo chamado `x`; em caso negativo, a busca
+passa para a classe (`+my_obj.__class__+`) e depois sobe pelo diagrama de
+herança.footnote:[A pesquisa de atributos é mais complicada que isso; veremos
+todos os detalhes sinistros na Parte V: Metaprogramação (vol.3). Por ora, esta
+explicação simplificada nos serve.] Se por fim o atributo `x` não for
+encontrado, o método `+__getattr__+`, definido na classe de `my_obj`, é chamado
+com `self` e o nome do atributo como uma string, por exemplo, `'x'`.
+
+O <> lista nosso método `+__getattr__+`. Ele basicamente
+verifica se o atributo desejado é uma das letras `xyzt`. Em caso positivo,
+devolve o componente correspondente do vetor.
+
+[[ex_vector_v3_getattr]]
+.Parte de _vector_v3.py_: método `+__getattr__+` acrescentado à classe `Vector`
+====
+[source, python]
+----
+include::../code/12-seq-hacking/vector_v3.py[tags=VECTOR_V3_GETATTR]
+----
+====
+
+<1> Define `+__match_args__+` para permitir casamento de padrões posicionais sobre
+os atributos dinâmicos suportados por `+__getattr__+`.footnote:[Apesar de
+`+__match_args__+` existir para suportar casamento de padrões desde o Python 3.10,
+é inofensivo definir este atributo em versões anteriores da linguagem. Na
+primeira edição chamei este atributo de `shortcut_names`. Com o novo nome, ele
+cumpre dois papéis: suportar padrões posicionais em instruções `case` e guardar
+os nomes dos atributos dinâmicos suportados por uma lógica especial em
+`+__getattr__+` e `+__setattr__+`.]
+
+<2> Obtém a classe de `Vector`, para uso posterior.
+
+<3> Tenta obter a posição de `name` em `+__match_args__+`.
+
+<4> `.index(name)` gera um `ValueError` quando `name` não é encontrado; define
+`pos` como `-1`. (Eu preferiria usar algo como `str.find` aqui, mas `tuple` não
+implementa esse método.)
+
+<5> Se `pos` está dentro da faixa de componentes disponíveis, devolve aquele
+componente.
+
+<6> Se chegamos até aqui, gera um `AttributeError` com uma mensagem de erro
+padrão.
+
+Não é difícil implementar `+__getattr__+`, mas neste caso não é o suficiente.
+Observe a interação bizarra no <>.
+
+[[ex_vector_v3_getattr_bug]]
+.Comportamento inapropriado: realizar uma atribuição a `v.x` não gera um erro, mas introduz uma inconsistência
+====
+[source, python]
+----
+>>> v = Vector(range(5))
+>>> v
+Vector([0.0, 1.0, 2.0, 3.0, 4.0])
+>>> v.x  # <1>
+0.0
+>>> v.x = 10  # <2>
+>>> v.x  # <3>
+10
+>>> v
+Vector([0.0, 1.0, 2.0, 3.0, 4.0])  # <4>
+----
+====
+<1> Acessa o elemento `v[0]` como `v.x`.
+<2> Atribui um novo valor a `v.x`. Isso deveria gera uma exceção.
+<3> Ler `v.x` obtém o novo valor, `10`.
+<4> Entretanto, os componentes do vetor não mudam.
+
+Você consegue explicar o que está acontecendo?
+Em especial, por que no passo `③`, `v.x` devolve `10`,
+se este valor não está presente no array de componentes do vetor?
+Se você não souber responder de imediato,
+estude a explicação de `+__getattr__+` que aparece logo antes do <>.
+A razão é um sutil, mas é um fundamento importante
+para entender técnicas que veremos mais tarde no livro.
+
+Após pensar um pouco sobre essa questão, veja a seguir a explicação para o que aconteceu.
+
+<<<
+A inconsistência no <> ocorre devido à forma como
+`+__getattr__+` funciona: Python só chama esse método como último recurso,
+quando o objeto não contém o atributo nomeado.
+Entretanto, após atribuirmos `v.x = 10`, o objeto `v` agora contém
+um atributo `x`, e então `+__getattr__+` não
+será mais invocado para obter `v.x`: o interpretador vai apenas devolver o valor
+`10`, que agora está vinculado a `v.x`.
+Por outro lado, nossa implementação de
+`+__getattr__+` obtém os valores dos "atributos
+virtuais" listados em `+__match_args__+` acessando apenas
+`self._components`, ignorando qualquer outro atributo da instância.
+
+Para evitar essa inconsistência, precisamos mudar a lógica de definição de
+atributos em nossa classe `Vector`.
+
+Como você se lembra, nos nossos últimos exemplos de `Vector2d` no
+<>, tentar atribuir valores aos atributos de instância `.x` ou
+`.y` gerava um `AttributeError`. Em `Vector`, queremos produzir a mesma exceção
+em resposta a tentativas de atribuição a qualquer nome de atributo com um única
+letra minúscula, para evitar confusão. Para fazer isso, implementaremos
+`+__setattr__+`, como listado no <>.
+
+[[ex_vector_v3_setattr]]
+.Parte de vector_v3.py: o método `+__setattr__+` na classe `Vector`
+====
+[source, python]
+----
+include::../code/12-seq-hacking/vector_v3.py[tags=VECTOR_V3_SETATTR]
+----
+====
+<1> Tratamento especial para nomes de atributos com uma única letra.
+<2> Se `name` está em `+__match_args__+`, configura uma mensagem de erro específica.
+<3> Se `name` é uma letra minúscula, configura a mensagem de erro sobre nomes de uma letra.
+<4> Caso contrário, configura uma mensagem de erro vazia.
+<5> Se existir uma mensagem de erro não-vazia, gera um `AttributeError`.
+<6> Caso default: chama `+__setattr__+` na superclasse para seguir o comportamento padrão.
+
+[TIP]
+====
+
+A((("super() function")))((("functions", "super() function"))) função `super()`
+fornece uma maneira de acessar dinamicamente métodos de superclasses, uma
+necessidade em uma linguagem dinâmica que suporta herança múltipla, como Python.
+Ela é usada para delegar alguma tarefa de um método em uma subclasse para um
+método adequado em uma superclasse, como visto no <>.
+Falaremos mais sobre `super` na <>.
+
+====
+
+Ao escolher a menssagem de erro para mostrar com `AttributeError`, primeiro eu
+verifiquei o comportamento do tipo embutido `complex`, pois ele é imutável e tem
+um par de atributos de dados `real` and `imag`. Tentar mudar qualquer um dos
+dois em uma instância de `complex` gera um `AttributeError` com a mensagem
+`+"can't set attribute"+` ("não é possível setar o atributo"). Por outro
+lado, a tentativa de modificar um atributo protegido por uma propriedade, como
+fizemos no <>, produz a mensagem `"read-only attribute"`
+("atributo apenas para leitura"). Eu me inspirei em ambas as frases para definir
+a string `error` em `+__setitem__+`, mas fui mais explícito sobre os atributos
+proibidos.
+
+Note que não estamos proibindo a modificação de todos os atributos, apenas
+daqueles com nomes formados por uma letra minúscula, para evitar
+conflitos com os atributos suportados apenas para leitura: `x`, `y`, `z`, e `t`.
+
+[WARNING]
+====
+
+Sabendo((("__slots__"))) que declarar `+__slots__+`
+no nível da classe impede a definição de novos atributos de instância, é
+tentador usar esse recurso em vez de implementar `+__setattr__+` como fizemos.
+Entretanto, por todas as ressalvas discutidas na <>, usar
+`+__slots__+` apenas para prevenir a criação de atributos de instância não é
+recomendado. `+__slots__+` deve ser usado apenas para economizar memória, e
+apenas quando isso for um problema real.
+
+====
+
+<<<
+Mesmo não suportando escrita nos componentes de `Vector`, aqui está uma lição importante deste exemplo: muitas vezes, quando você implementa `+__getattr__+`, é necessário também escrever o `+__setattr__+`, para evitar comportamentos inconsistentes em seus objetos.
+
+Para permitir a modificação de componentes, poderíamos implementar `+__setitem__+`, para permitir `v[0] = 1.1`, e/ou `+__setattr__+`, para fazer `v.x = 1.1` funcionar.
+Mas `Vector` permanecerá imutável, pois queremos torná-lo _hashable_, na próxima seção.((("", startref="VCMdyn12")))((("", startref="SSMdyn12")))((("", startref="getattr12")))((("", startref="Adyn12")))
+
+
+
+[[multi_hashing_sec]]
+=== Vector versão #4: o hash e um == mais rápido
+
+Vamos((("Vector class, multidimensional", "__hash__
+and __eq__", secondary-sortas="hash",
+id="VCMhasheq12")))((("sequences, special methods for",
+"__hash__ and __eq__",
+secondary-sortas="hash",
+id="SSMhasheq12")))((("__hash__",
+id="hash12")))((("__eq__", id="eq12"))) novamente
+implementar um método `+__hash__+`. Juntamente com o `+__eq__+` existente, isso
+tornará as instâncias de `Vector` _hashable_.
+
+O `+__hash__+` do `Vector2d` (no <> do <>) computava o _hash_ de
+uma `tuple` construída com os dois componentes, `self.x` and `self.y`. Agora
+podemos estar lidando com milhares de componentes, então criar uma `tuple` pode
+ser caro demais. Em vez disso, vou aplicar sucessivamente o operador `^` (xor)
+aos _hashes_ de todos os componentes, assim: `v[0] ^ v[1] ^ v[2]`. É para isso
+que serve a função `functools.reduce`. Anteriormente afirmei que `reduce` não é
+mais tão popular quanto antes,footnote:[`sum`, `any`, e `all` cobrem a maioria
+dos casos de uso comuns de `reduce`. Veja a discussão na
+https://fpy.li/8b[«Seção 7.3.1»] (vol.1).] mas computar o _hash_ de todos os componentes do
+vetor é um bom caso de uso para ela. A <> ilustra a ideia geral
+da((("reducing functions"))) função `reduce`.
+
+[[reduce_fig]]
+.Funções de redução—`reduce`, `sum`, `any`, `all`—produzem um único resultado agregando valores de uma sequência ou de qualquer objeto iterável finito.
+image::../images/flpy_1201.png[align="center",pdfwidth=7cm]
+
+Até aqui vimos que `functools.reduce()` pode ser substituída por `sum()`. Vamos
+agora explicar exatamente como ela funciona. A ideia chave é reduzir uma série
+de valores a um valor único. O primeiro argumento de `reduce()` é uma função com
+dois argumentos, o segundo argumento é um iterável. Vamos dizer que temos uma
+função `fn`, que recebe dois argumentos, e uma lista `lst`. Quando chamamos
+`reduce(fn, lst)`, `fn` será aplicada ao primeiro par de elementos de
+`lst`—`fn(lst[0], lst[1])`—produzindo um primeiro resultado, `r1`. Então `fn` é
+aplicada a `r1` e ao próximo elemento—`fn(r1, lst[2])`—produzindo um segundo
+resultado, `r2`. Agora `fn(r2, lst[3])` é chamada para produzir `r3` ... e assim
+por diante, até o último elemento, quando finalmente um único elemento, `rN`, é
+produzido e devolvido.
+
+Veja como `reduce` pode ser usada para computar `5!` (o fatorial de 5):
+
+[source, python]
+----
+>>> 2 * 3 * 4 * 5  # resultado esperado: 5! == 120
+120
+>>> import functools
+>>> functools.reduce(lambda a,b: a*b, range(1, 6))
+120
+----
+
+Voltando a nosso problema de _hash_, o <> demonstra a ideia da
+computação de um xor agregado, fazendo isso de três formas diferente: com um
+laço `for` e com dois modos diferentes de usar `reduce`.
+
+[[ex_reduce_xor]]
+.Três maneiras de calcular o xor acumulado de inteiros de 0 a 5
+====
+[source, python]
+----
+>>> n = 0
+>>> for i in range(1, 6):  # <1>
+...     n ^= i
+...
+>>> n
+1
+>>> import functools
+>>> functools.reduce(lambda a, b: a^b, range(6))  # <2>
+1
+>>> import operator
+>>> functools.reduce(operator.xor, range(6))  # <3>
+1
+----
+====
+<1> xor agregado com um laço `for` e uma variável de acumulação.
+<2> `functools.reduce` usando uma função anônima.
+<3> `functools.reduce` substituindo a `lambda` customizada por `operator.xor`.
+
+Das alternativas apresentadas no <>, a última é minha favorita, e
+o laço `for` vem a seguir. Qual sua preferida?
+
+Como visto na https://fpy.li/8j[«Seção 7.8.1»] (vol.1), `operator` oferece a funcionalidade de
+todos os operadores infixos de Python em formato de função, diminuindo a
+necessidade do uso de `lambda`.
+
+Para escrever `+Vector.__hash__+` no meu estilo preferido precisamos importar os
+módulos `functools` e `operator`.
+O <> apresenta as mudanças relevantes.
+
+
+[[ex_vector_v4]]
+.Parte de vector_v4.py: duas importações e o método `+__hash__+` adicionados à classe `Vector` de vector_v3.py
+====
+[source, python]
+----
+from array import array
+import reprlib
+import math
+import functools  # <1>
+import operator  # <2>
+
+
+class Vector:
+    typecode = 'd'
+
+    # many lines omitted in book listing...
+
+    def __eq__(self, other):  # <3>
+        return tuple(self) == tuple(other)
+
+    def __hash__(self):
+        hashes = (hash(x) for x in self._components)  # <4>
+        return functools.reduce(operator.xor, hashes, 0)  # <5>
+
+    # more lines omitted...
+----
+====
+<1> Importa `functools` para usar `reduce`.
+<2> Importa `operator` para usar `xor`.
+<3> Não há mudanças em `+__eq__+`; listei-o aqui porque é uma boa prática manter `+__eq__+` e
+`+__hash__+` próximos no código-fonte, pois eles precisam trabalhar juntos.
+<4> Cria uma expressão geradora para computar sob demanda o _hash_ de cada componente.
+<5> Alimenta `reduce` com `hashes` e a função `xor`, para computar o código _hash_ agregado;
+o terceiro argumento, `0`, é o inicializador (veja o aviso a seguir).
+
+[WARNING]
+====
+
+Ao usar `reduce`, é uma boa prática fornecer o terceiro argumento,
+`reduce(function, iterable, initializer)`, para prevenir a seguinte exceção:
+`TypeError: reduce() of empty sequence with no initial value`
+("reduce() de uma sequência vazia sem valor inicial", uma mensagem bem escrita:
+explica o problema e diz como resolvê-lo).
+O `initializer` é o valor devolvido se a sequência for vazia e
+é usado como primeiro argumento no laço de redução,
+e portanto deve ser o elemento neutro da operação.
+Assim, o `initializer` para `{plus}`, `|`, `^` (xor) deve ser `0`,
+mas para  `*` e `&` deve ser `1`.
+
+====
+
+Da forma como está implementado, o método `+__hash__+` no <> é um
+exemplo perfeito de uma do padrão _map-reduce_ (mapear e reduzir). Veja a
+(<>).
+
+[[map_reduce_fig]]
+.Map-reduce: `map` aplica uma função a cada item, gerando uma nova série , `reduce` computa o agregado.
+image::../images/flpy_1202.png[align="center",pdfwidth=7cm]
+
+A etapa de mapeamento produz um _hash_ para cada componente, e a etapa de
+redução agrega todos os _hashes_ com o operador +xor+.
+Se usarmos a função `map` em vez de uma _genexp_, a etapa de mapeamento fica ainda mais visível:
+
+[source, python]
+----
+    def __hash__(self):
+        hashes = map(hash, self._components)
+        return functools.reduce(operator.xor, hashes)
+----
+
+[TIP]
+====
+
+A solução com `map` era menos eficiente no Python 2, onde a função `map` criava
+uma nova `list` com os resultados. Mas no Python 3, `map` é preguiçosa (_lazy_):
+ela cria um gerador que produz os resultados sob demanda, e assim economiza
+memória—exatamente como a expressão geradora que usamos no método `+__hash__+`
+do <>.
+
+====
+
+E enquanto estamos falando de funções de redução, podemos substituir nossa implementação apressada de `+__eq__+` com uma outra, menos custosa em termos de processamento e uso de memória, pelo menos para vetores grandes.
+Como visto no <> do <>, temos esta implementação bastante concisa de `+__eq__+`:
+
+[source, python]
+----
+    def __eq__(self, other):
+        return tuple(self) == tuple(other)
+----
+
+Isso funciona com `Vector2d` e com `Vector`—e até considera `Vector([1, 2])`
+igual a `(1, 2)`, o que pode ser um problema, mas por ora vamos ignorar esta
+questão.footnote:[Vamos considerar seriamente o caso de `++Vector([1, 2]) == (1,
+2)++` na <>.] Mas para instâncias de `Vector`, que podem
+ter milhares de componentes, esse método é muito ineficiente. Ele cria duas
+tuplas copiando todo o conteúdo dos operandos, apenas para usar o `+__eq__+` do
+tipo `tuple`. Para  `Vector2d` (com apenas dois componentes), é um bom atalho.
+Mas não para grandes vetores multidimensionais. Uma forma melhor de comparar um
+`Vector` com outro `Vector` ou iterável seria o código do <>.
+
+[[ex_eq_loop]]
+.A implementação de `+Vector.__eq__+` usando `zip` em um laço `for`, para uma comparação mais eficiente
+====
+[source, python]
+----
+    def __eq__(self, other):
+        if len(self) != len(other):  # <1>
+            return False
+        for a, b in zip(self, other):  # <2>
+            if a != b:  # <3>
+                return False
+        return True  # <4>
+----
+====
+<1> Objetos de tamanho diferentes não são iguais.
+Teste necessário porque `zip` retorna quando termina o iterável menor.
+<2> `zip` produz um gerador de tuplas criadas a partir dos itens em cada argumento iterável.
+<3> Sai assim que dois componentes sejam diferentes, devolvendo `False`.
+<4> Caso contrário, os objetos são iguais.
+
+[TIP]
+====
+O((("zip() function")))((("functions", "zip() function"))) nome da função `zip` vem de zíper,
+pois o fecho de roupas funciona engatando pares de dentes a partir de duas abas paralelas,
+uma boa analogia visual para o que faz `zip(esquerda, direita)`.
+Nenhuma relação com arquivos comprimidos.
+
+Por padrão, `zip` encerra silenciosamente a geração de tuplas assim que um de seus argumentos
+é consumido até o fim, ainda que sobrem itens em outros argumentos.
+Escrevi na primeira edição deste livro que este comportamento violava o princípio
+_fail fast_ (falhar logo) do Python, e que `zip` deveria gerar um `ValueError` se os iteráveis
+não forem todos do mesmo tamanho, como acontece quando se desempacota um
+iterável para uma tupla de variáveis de tamanho diferente.
+
+No Python 3.10, `zip` passou a aceitar o argumento nomeado opcional `strict=True`,
+que faz o que eu imaginava. 
+Mas atenção: para preservar a compatibilidade, o default é `strict=False`,
+portanto o comportamento padrão ainda é parar sem avisar assim que
+um dos argumentos é consumido. Veja a caixa <> logo adiante para saber mais sobre zip.
+====
+
+O <> é eficiente, mas a função `all` pode produzir o mesmo resultado do laço `for` em uma linha:
+se todas as comparações entre componentes correspoendentes forem `True`, o resultado é `True`.
+Assim que uma comparação é `False`, `all` devolve `False`.
+Confira o <>: novamente, comparamos os `len` para não invocar `zip`
+se os vetores têm tamanhos diferentes.((("", startref="eq12")))((("", startref="hash12")))((("",
+startref="SSMhasheq12")))((("", startref="VCMhasheq12")))
+
+[[ex_eq_all]]
+.`+Vector.__eq__+` com `zip` e `all`: mesma lógica do <>
+====
+[source, python]
+----
+    def __eq__(self, other):
+        return (len(self) == len(other) and
+                all(a == b for a, b in zip(self, other)))
+----
+====
+
+
+
+
+
+
+
+[[zip_box]]
+.O fantástico zip
+****
+
+Ter um laço `for` que itera sobre itens sem perder tempo com variáveis de índice
+é muito bom e evita muitos bugs, mas exige algumas funções utilitárias
+especiais. Uma delas é a função embutida `zip`, que facilita a iteração em
+paralelo sobre dois ou mais iteráveis, devolvendo tuplas que você pode
+desempacotar em variáveis, uma para cada item nas entradas paralelas. Veja o
+<>.
+
+[[zip_demo]]
+.A função embutida `zip` trabalhando
+====
+[source, python]
+----
+>>> zip(range(3), 'ABC')  # <1>
+
+>>> list(zip(range(3), 'ABC'))  # <2>
+[(0, 'A'), (1, 'B'), (2, 'C')]
+>>> list(zip(range(3), 'ABC', [0.0, 1.1, 2.2, 3.3]))  # <3>
+[(0, 'A', 0.0), (1, 'B', 1.1), (2, 'C', 2.2)]
+>>> from itertools import zip_longest  # <4>
+>>> list(zip_longest(range(3), 'ABC', [0.0, 1.1, 2.2, 3.3], fillvalue=-1))
+[(0, 'A', 0.0), (1, 'B', 1.1), (2, 'C', 2.2), (-1, -1, 3.3)]
+----
+====
+<1> `zip` devolve um gerador que produz tuplas sob demanda.
+<2> Cria uma `list` apenas para exibição; normalmente iteramos sobre o gerador.
+<3> `zip` para sem aviso quando um dos iteráveis é exaurido.
+<4> A função `itertools.zip_longest` se comporta de forma diferente: ela usa um
+`fillvalue` opcional (por default `None`) para preencher os valores ausentes, e
+assim consegue gerar tuplas até que o último iterável seja exaurido.
+
+A função `zip` pode também ser usada para transpor uma matriz, representada como
+iteráveis aninhados. Por exemplo:
+
+[source, python]
+----
+>>> a = [(1, 2, 3),
+...      (4, 5, 6)]
+>>> list(zip(*a))
+[(1, 4), (2, 5), (3, 6)]
+>>> b = [(1, 2),
+...      (3, 4),
+...      (5, 6)]
+>>> list(zip(*b))
+[(1, 3, 5), (2, 4, 6)]
+----
+
+Se você quiser entender `zip`, passe algum tempo considerando como esses
+exemplos funcionam.
+
+A função embutida `enumerate` é outra função geradora usada com frequência em
+laços `for`, para evitar manipulação direta de variáveis índice. Quem não
+estiver familiarizado com `enumerate` deve estudar a seção dedicada a ela na
+documentação das
+https://fpy.li/6d[Funções embutidas].
+Voltaremos a falar sobre `zip` e `enumerate`, bem como
+várias outras funções geradores na biblioteca padrão, na
+https://fpy.li/8q[«Seção 17.9»] (vol.3).
+
+****
+
+Vamos encerrar esse capítulo trazendo de volta o método `+__format__+` do
+`Vector2d` para o `Vector`.
+
+=== Vector versão #5: Formatando
+
+O((("Vector class, multidimensional", "__format__",
+secondary-sortas="format", id="VCMformat12")))((("sequences, special methods
+for", "__format__", secondary-sortas="format",
+id="SSMformat12")))((("__format__", id="format12")))
+método `+__format__+` de `Vector` será parecido com o mesmo método em
+`Vector2d`, mas em vez de fornecer uma exibição customizada em coordenadas
+polares, `Vector` usará coordenadas esféricas—também conhecidas como coordendas
+"hiperesféricas", pois agora suportamos _n_ dimensões, e esferas com mais
+de 3 dimensões são "hiperesferas".footnote:[O website Wolfram Mathworld tem um artigo
+sobre https://fpy.li/12-4[hypersphere (_hiperesfera_)]; na Wikipedia,
+"hypersphere" redireciona para a página https://fpy.li/nsphere[_n_-sphere]]
+Por este motivo, mudaremos o sufixo do formato customizado de `'p'` para `'h'`.
+
+
+[TIP]
+====
+
+Como vimos na <>, ao estender a
+https://fpy.li/63[«Minilinguagem de especificação de formato»] é bom evitar os códigos de formato
+usados pelos tipos embutidos. Nossa minilinguagem estendida também
+usa os códigos de formato dos números de ponto flutuante (`'eEfFgGn%'`) com seus
+significados originais.
+Inteiros usam `'bcdoxXn'` e strings usam `'s'`. Escolhi `'p'` para as
+coordenadas polares de `Vector2d`. O código `'h'` para coordendas hiperesféricas
+é uma boa opção.
+
+====
+
+Por exemplo, dado um objeto `Vector` em um espaço 4D (`len(v) == 4`), o código
+`'h'` irá produzir uma linha como `<3.2, 15.0, 45.0, 30.0>`, onde `3.2` é a
+magnitude (`abs(v)`), e os demais números são os componentes angulares
+que uma matemática chamaria de Φ~1~, Φ~2~, Φ~3~.
+
+Aqui estão algumas amostras do formato de coordenadas esféricas em 4D, retiradas dos doctests de _vector_v5.py_ (veja o <>):
+
+[source, python]
+----
+>>> format(Vector([-1, -1, -1, -1]), 'h')
+'<2.0, 2.0943951023931957, 2.186276035465284, 3.9269908169872414>'
+>>> 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>'
+----
+
+Antes de podermos implementar as pequenas mudanças necessárias em
+`+__format__+`, precisamos escrever um par de métodos de apoio: `angle(n)`, para
+computar uma das coordenadas angulares (por exemplo, Φ~1~), e `angles()`, para
+devolver um iterável com todas as coordenadas angulares. Não vou descrever a
+matemática aqui; se você tiver curiosidade, a página
+https://fpy.li/nsphere[_n_-sphere] da Wikipedia apresenta as
+fórmulas que usei para calcular coordenadas esféricas a partir das coordendas
+cartesianas no array de componentes de `Vector`.
+
+O <> é a listagem completa de _vector_v5.py_, consolidando tudo que implementamos desde a <>, e acrescentando a formatação customizada
+
+[[ex_vector_v5]]
+.vector_v5.py: a classe `Vector` com métodos para suportar `+__format__+`
+====
+[source, python]
+----
+include::../code/12-seq-hacking/vector_v5.py[tags=VECTOR_V5]
+----
+====
+<1> Importa `itertools` para usar a função `chain` em `+__format__+`.
+<2> Computa uma das coordendas angulares, conforme o artigo
+https://fpy.li/nsphere[_n_-sphere] na Wikipedia.
+<3> Cria uma expressão geradora para computar sob demanda todas as coordenadas
+angulares.
+<4> Produz uma _genexp_ usando `itertools.chain`, para iterar de forma contínua
+sobre a magnitude e as coordenadas angulares.
+<5> Configura uma coordenada esférica para exibição delimitada por `<` e `>`.
+<6> Configura uma coordenda cartesiana para exibição entre parênteses.
+<7> Cria uma genexp para formatar cada componente.
+<8> Insere componentes separados por vírgulas nos delimitadores.
+
+[NOTE]
+====
+Usamos intensivamente expressões geradoras em `+__format__+`, `angle`, e
+`angles`, mas nosso foco aqui é fornecer um `+__format__+` para levar `Vector`
+ao mesmo nível de implementação de `Vector2d`. Quando tratarmos de geradores, no
+https://fpy.li/17[«Capítulo 17»] (vol.3), vamos usar parte do código de `Vector` nos exemplos, e lá
+os geradores serão explicados em detalhes.
+====
+
+Completamos a missão deste capítulo. Aperfeiçoaremos a classe `Vector`
+com operadores infixos no <>. Nosso objetivo aqui foi explorar
+técnicas para programação de métodos especiais que são úteis em uma grande
+variedade de classes que implementam coleções de
+valores.((("", startref="VCMformat12")))((("", 
+startref="SSMformat12")))((("", startref="format12")))
+
+
+=== Resumo do capítulo
+
+A((("Vector class, multidimensional", "overview of")))((("sequences, special
+methods for", "overview of"))) classe `Vector`, o exemplo que desenvolvemos
+nesse capítulo, foi projetada para ser compatível com `Vector2d`, exceto pelo
+uso de uma assinatura de construtor diferente, aceitando um único argumento
+iterável, como fazem todos os tipos embutidos de sequências. O fato de `Vector`
+se comportar como uma sequência apenas por implementar `+__getitem__+` e
+`+__len__+` deu margem a uma discussão sobre protocolos, as interfaces informais
+usadas em linguagens com tipagem pato.
+
+A seguir vimos como a sintaxe `my_seq[a:b:c]` funciona por baixo dos panos,
+criando um objeto `slice(a, b, c)` e entregando esse objeto a `+__getitem__+`.
+Armados com esse conhecimento, fizemos `Vector` responder corretamente ao
+fatiamento, devolvendo novas instâncias de `Vector`, como se espera de qualquer
+sequência pythônica.
+
+O próximo passo foi fornecer acesso somente para leitura aos primeiros
+componentes de `Vector`, usando uma notação do tipo `my_vec.x`. Fizemos isso
+implementando `+__getattr__+`.
+Ao suportar esta forma de acessar atributos, podemos induzir o
+usuário tentar alterar aqueles componentes usando a forma `my_vec.x = 7`,
+revelando um possível bug.
+Consertamos o problema implementando também
+`+__setattr__+`, para barrar a atribuição de valores a atributos
+com nomes de uma letra. Após escrever um `+__getattr__+`, é comum
+surgir a necessidade de adicionar também `+__setattr__+`,
+para evitar comportamentos surpreendentes.
+
+Implementar a função `+__hash__+` nos deu um contexto perfeito para usar
+`functools.reduce`, pois precisávamos aplicar o operador xor (`^`)
+sucessivamente aos _hashes_ de todos os componentes de `Vector`, para produzir
+um código de _hash_ agregado para o `Vector` como um todo.
+Após aplicar `reduce` em `+__hash__+`, usamos a função de redução embutida `all`,
+para criar um método `+__eq__+` eficiente.
+
+<<<
+O último aperfeiçoamento em `Vector` foi reimplementar o método `+__format__+` de
+`Vector2d`, para suportar coordenadas esféricas como alternativa às coordenadas
+cartesianas default. Usamos alguma matemática e vários geradores para
+programar `+__format__+` e suas funções auxiliares, mas esses são detalhes de
+implementação. Voltaremos aos geradores no https://fpy.li/17[«Capítulo 17»] (vol.3). O objetivo
+daquela última seção foi suportar um formato customizado, cumprindo assim a
+promessa de um `Vector` capaz de fazer tudo que um `Vector2d` faz, e algo mais.
+
+Como fizemos no <>, muitas vezes aqui examinamos como os
+objetos padrão de Python se comportam, para emulá-los e dar a `Vector` uma
+funcionalidade "pythônica".
+
+No <> vamos implemenar vários operadores infixos em `Vector`.
+A matemática será mais simples que o método `angle()` de `Vector`,
+mas explorar como os operadores infixos funcionam no Python
+é uma grande lição sobre design orientado a objetos.
+Mas antes de chegar à sobrecarga de operadores em uma classe,
+vamos estudar a organização de várias classes com interfaces e herança,
+os assuntos do <> e do <>.
+
+
+=== Para saber mais
+
+A((("Vector class, multidimensional", "further reading on")))((("sequences,
+special methods for", "further reading on"))) maioria dos métodos especiais
+tratados no exemplo `Vector` também apareceram no exemplo `Vector2d`,
+no <>, então as referências na <>
+são relevantes aqui também.
+
+A poderosa função de ordem superior `reduce` também é conhecida como _fold_
+(dobrar), _accumulate_ (acumular), _aggregate_ (agregar), _compress_
+(comprimir), e _inject_ (injetar).
+Para mais informações, veja o artigo
+https://fpy.li/12-5[_Fold (higher-order function)_] (Dobrar (função de
+ordem superior)), que apresenta aplicações daquela função,
+com ênfase em programação funcional com estruturas de dados
+recursivas. O artigo também inclui uma tabela mostrando funções similares a
+_fold_ em dezenas de linguagens de programação.
+
+Em
+https://fpy.li/12-6[_What's New in Python 2.5_]
+(Novidades no Python 2.5) há uma pequena explicação sobre o método `+__index__+`,
+projetado para suportar métodos `+__getitem__+`, como vimos na
+<>. A 
+https://fpy.li/pep357[_PEP 357—Allowing Any Object to be Used for Slicing_]
+(Permitir que Qualquer Objeto seja Usado para Fatiamento)
+detalha a necessidade daquele método especial na perspectiva do mantenedor
+de uma extensão em C—Travis Oliphant, o principal criador da NumPy.
+As muitas contribuições de Oliphant tornaram Python uma das mais importantes
+linguagem para computação científica, favorecendo sua ampla adoção em
+aplicações de aprendizagem de máquina.
+
+[[sequence_hacking_soapbox]]
+.Ponto de vista
+****
+
+[role="soapbox-title"]
+**Protocolos como interfaces informais**
+
+Protocolos((("Soapbox sidebars", "protocols as informal
+interfaces")))((("sequences, special methods for", "Soapbox discussion",
+id="SSMsoap12")))((("protocols", "as informal interfaces",
+secondary-sortas="informal interfaces")))((("interfaces", "protocols as
+informal"))) não são uma invenção de Python. Os criadores de Smalltalk, que
+também cunharam a expressão "orientado a objetos", usavam "protocolo" como um
+sinônimo para aquilo que hoje chamamos de interfaces. Alguns ambientes de
+programação Smalltalk permitiam que os programadores marcassem um grupo de
+métodos como um protocolo, mas tal marcação era só para documentação e
+navegação pelo código, e não era usada pela linguagem.
+Por isso acredito que "interface
+informal" é uma explicação curta razoável para "protocolo" quando falo para uma
+audiência mais familiar com interfaces formais, que são
+checadas por um compilador.
+
+Protocolos bem estabelecidos ou consagrados evoluem naturalmente em qualquer
+linguagem que usa tipagem dinâmica (isto é, quando a checagem de tipos acontece
+durante a execução), porque não há informação estática de tipo em assinaturas de
+métodos e em variáveis. Ruby é outra importante linguagem orientada a objetos
+que tem tipagem dinâmica e usa protocolos.
+
+Na documentação de Python, muitas vezes podemos perceber que um protocolo está
+sendo discutido pelo uso de palavras como "_a file like object_"
+("um objeto semelhante a um arquivo").
+Esta é uma forma abreviada de dizer "algo que se comporta como um arquivo,
+implementando as partes da interface de arquivo relevantes no presente contexto".
+
+Você poderia achar que implementar apenas parte de um protocolo é um desleixo,
+mas isso tem a vantagem de manter as coisas simples. A
+https://fpy.li/6e[Seção3.3]
+do capítulo "Modelo de Dados" na documentação de Python sugere:
+
+<<<
+[quote]
+____
+Ao implementar uma classe que emula qualquer tipo embutido, é importante que a
+emulação seja implementada apenas na medida em que faça sentido para o objeto
+que está sendo modelado. Por exemplo, algumas sequências podem funcionar
+bem com a recuperação de elementos individuais, mas extrair uma fatia pode
+não fazer sentido.
+____
+
+Quando((("KISS principle"))) não precisamos escrever métodos inúteis apenas para
+cumprir o contrato de uma interface excessivamente detalhista e satisfazer o
+compilador, fica mais fácil seguir o
+https://fpy.li/6f[princípio KISS].
+
+Por outro lado, se quiser usar um checador de tipos para checar suas
+implementações de protocolos, então uma definição mais estrita de "protocolo" é
+necessária. É isso que `typing.Protocol` possibilita.
+
+Terei mais a dizer sobre protocolos e interfaces no <>,
+onde esses conceitos são o assunto principal.
+
+
+[role="soapbox-title"]
+**De onde vieram os patos**
+
+Creio((("Soapbox sidebars", "duck typing")))((("duck typing"))) que a comunidade
+Ruby, mais que qualquer outra, ajudou a popularizar o termo _duck typing_,
+ao pregar para as massas de convertidos do Java. Mas a expressão
+já era usada nas discussões de Python muito antes de Ruby ou Python se
+tornarem "populares". De acordo com a Wikipedia, um dos primeiros exemplos de
+uso da analogia do pato, no contexto da programação orientada a objetos, foi uma
+mensagem para https://fpy.li/12-11[Python-list], escrita por Alex Martelli
+e datada de 26 de julho de 2000: https://fpy.li/12-9["polymorphism (was Re: Type
+checking in python?)" (_polimorfismo (era Re: Verificação de tipo em
+python?_))]. Foi dali que veio a citação no início desse capítulo. Se você tiver
+curiosidade sobre as origens literárias do termo "duck typing", e a aplicação
+desse conceito de orientação a objetos em muitas linguagens, veja a página
+https://fpy.li/6g[Duck typing] na Wikipedia.
+
+
+[role="soapbox-title"]
+**Um __format__ seguro, com usabilidade aperfeiçoada**
+
+Ao((("__format__")))((("Soapbox sidebars",
+"__format__", secondary-sortas="format")))
+implementar `+__format__+`, não tomei qualquer precaução a respeito de
+instâncias de `Vector` com um número muito grande de componentes, como fizemos
+no `+__repr__+` usando `reprlib`. A justificativa é que `repr()` é usado para
+depuração e registro de logs, então precisa sempre gerar uma saída minimamente
+aproveitável, enquanto `+__format__+` é usado para exibir resultados para
+usuários finais, que presumivelmente desejam ver o `Vector` inteiro.
+Se isso for inconveniente, então seria bom implementar um nova extensão à
+minilinguagem de especificação de formato.
+
+O quê eu faria: por default, qualquer `Vector` formatado mostraria um número
+razoável mas limitado de componentes, digamos uns 30. Se existirem mais
+elementos que isso, o comportamento default seria similar ao de `reprlib`:
+cortar o excesso e exibir `+'...'+`. Entretanto, se o especificador
+de formato terminar com um código especial `+*+`, significando "all" (_todos_),
+então a limitação de tamanho seria desabilitada. Assim, um usuário
+que desconhece o problema de exibição de vetores muito grandes não será
+penalizado. Mas se a limitação não for desejada, a presença das `+'...'+`
+pode levar o usuário a consultar a documentação e descobrir
+o uso do `*` como opção de formatação.
+
+
+[role="soapbox-title"]
+**A busca por uma soma pythônica**
+
+Na((("Soapbox sidebars", "Pythonic sums",
+id="SSpysum12")))((("Pythonic sums", id="pysum12")))
+https://fpy.li/12-11[_python-list_], há uma thread de abril de 2003 intitulada
+https://fpy.li/12-12[_Pythonic Way to Sum n-th List Element?_]
+(A forma pythônica de somar o n-ésimo elemento em listas).
+
+Não há uma resposta única para a "O que é pythônico?", da mesma
+forma que não há uma resposta única para "O que é belo?"
+
+Mas talvez esta troca de ideias traga alguma luz.
+
+O autor original, Guy Middleton, pediu melhorias para a solução abaixo, afirmando
+não gostar de usar `lambda`. Adaptei o código apresentado aqui: em
+2003, `reduce` era uma função embutida, mas no Python 3 precisamos importá-la;
+também substitui os nomes `x` e `y` por `my_list` e `sub` (para sub-lista),
+e usei `ac` como variável acumuladora para o `reduce`.
+
+No caso específico, Middleton quer somar o segundo item de cada lista de uma série de listas.
+Este foi o código que ele enviou para iniciar a discussão:
+
+<<<
+[source, python]
+----
+>>> from functools import reduce
+>>> my_list = [[1, 2, 3], [30, 50, 70], [9, 8, 7]]
+>>> reduce(lambda ac, n: ac+n, [sub[1] for sub in my_list])
+60
+----
+
+Esse código usa várias peculiaridades de Python:
+`lambda`, `reduce` e uma compreensão de lista.
+Ele provavelmente ficaria em último lugar em um concurso de popularidade, pois
+ofende quem odeia `lambda` e também aqueles que desprezam as compreensões de
+lista.
+
+Se você vai usar `lambda`, provavelmente não há razão para usar uma compreensão
+de lista—exceto para filtrar com `if`, que não é o caso aqui.
+
+Aqui está uma solução minha que ofenderá todo mundo, exceto os fanáticos por `lambda`:
+
+[source, python]
+----
+>>> reduce(lambda ac, sub: ac + sub[1], my_list, 0)
+60
+----
+
+Não participei da discussão original,
+e não usaria este código porque também não gosto muito de `lambda`,
+principalmente em casos obscuros como este.
+Apenas quis mostrar aqui um exemplo sem uma compreensão de lista.
+
+A primeira resposta veio de Fernando Perez, criador do IPython e do Jupyter
+Notebook, mostrando como a NumPy suporta arrays +
+_n_-dimensionais e fatiamento
+_n_-dimensional:
+
+[source, python]
+----
+>>> import numpy as np
+>>> my_array = np.array(my_list)
+>>> np.sum(my_array[:, 1])
+60
+----
+
+A solução de Perez é boa, mas obviamente requer a NumPy.
+
+<<<
+Guy Middleton elogiou esta próxima solução,
+de Paul Rubin e Skip Montanaro:
+
+[source, python]
+----
+>>> import operator
+>>> reduce(operator.add, [sub[1] for sub in my_list], 0)
+60
+----
+
+Então Evan Simpson perguntou, "O que há de errado em fazer assim?":
+
+[source, python]
+----
+>>> ac = 0
+>>> for sub in my_list:
+...     ac += sub[1]
+...
+>>> ac
+60
+----
+
+Muitos concordaram que este código era bastante pythônico.
+Alex Martelli chegou a escrever que Guido provavelmente 
+resolveria o problema desta maneira.
+Gosto do código de Evan Simpson, mas também gosto do comentário
+de David Eppstein sobre ele:
+
+[quote]
+____
+
+Se você quer a soma de uma lista de itens, deveria escrever algo como
+"a soma de uma lista de itens", não como "faça um laço sobre
+esses itens, mantenha uma variável `ac`, execute uma série de somas".
+Por que temos linguagens de alto nível, senão para expressar nossas
+intenções em um nível mais alto e deixar a linguagem se preocupar
+com as operações de baixo nível necessárias para executá-las?
+
+____
+
+E daí Alex Martelli voltou para sugerir:
+
+[quote]
+____
+
+Fazemos somas com tanta frequência que eu não me importaria de forma
+alguma se Python a tornasse uma função embutida. Mas `reduce(operator.add,
+\...)` não é mesmo uma boa maneira de expressar isso, na minha opinião (e vejam
+que, como um antigo APListafootnote:[NT: Aqui Martelli refere-se à
+linguagem https://fpy.li/6h[APL]]
+e um apreciador da FPfootnote:[NT: E aqui à linguagem https://fpy.li/6j[FP]],
+eu _deveria_ gostar daquilo, mas não gosto).
+
+____
+
+Martelli então sugere uma função `sum()`, que ele mesmo programa e propõe para
+Python. Ela se torna uma função embutida no Python 2.3, lançado apenas três
+meses após aquela conversa na lista. E a sintaxe preferida de Alex se torna a
+regra:
+
+[source, python]
+----
+>>> sum([sub[1] for sub in my_list])
+60
+----
+
+No final do ano seguinte (novembro de 2004), Python 2.4 foi lançado e incluía
+expressões geradoras, fornecendo o que agora é, na minha opinião, a resposta
+mais pythônica para a pergunta original de Guy Middleton:
+
+[source, python]
+----
+>>> sum(sub[1] for sub in my_list)
+60
+----
+
+Isso não só é mais legível que `reduce`, também evita a armadilha da sequência
+vazia: `sum([])` é `0`, simples assim.
+
+Na mesma conversa, Alex Martelli sugeriu que a função embutida `reduce` de
+Python 2 trazia mais problemas que soluções, porque encorajava idiomas de
+programação difíceis de explicar. Ele foi bastante convincente: a função foi
+rebaixada para o módulo `functools` no Python 3.
+
+Ainda assim, `functools.reduce` tem seus usos. Ela resolveu o problema de nosso
+`+Vector.__hash__+` de uma forma que eu chamaria de pythônica.((("",
+startref="SSMsoap12")))((("", startref="SSpysum12")))((("",
+startref="pysum12")))
+
+****
+
+<<<
diff --git a/vol2/cap13.adoc b/vol2/cap13.adoc
new file mode 100644
index 0000000..4174f97
--- /dev/null
+++ b/vol2/cap13.adoc
@@ -0,0 +1,2770 @@
+[[ch_ifaces_prot_abc]]
+== Interfaces, protocolos, e ABCs
+:example-number: 0
+:figure-number: 0
+
+[quote, Gamma, Helm, Johnson, Vlissides, First Principle of Object-Oriented Design]
+____
+Programe mirando uma interface,
+não uma implementação.footnote:[Design Patterns:
+Elements of Reusable Object-Oriented Software, Introduction, p. 18.]
+____
+
+A programação orientada a objetos((("interfaces", "role in object-oriented programming")))
+tem tudo a ver com interfaces.
+A melhor forma de entender um tipo em Python é conhecer os métodos que
+aquele tipo oferece—sua interface—como vimos na
+https://fpy.li/8s[«Seção 8.4»] (vol.1).
+Desde o Python 3.8, temos quatro maneiras de definir e usar interfaces.
+Elas estão ilustradas no _Mapa de Sistemas de Tipagem_ (<>).
+
+[[type_systems_described]]
+.Na metade superior, checagens de tipo dinâmicas (em tempo de execução) usando só o interpretador Python; a metade inferior requer um checador estático externo como o Mypy, ou um IDE como o PyCharm. Os quadrantes da esquerda se referem à tipagem baseada na estrutura do objeto—isto é, os métodos oferecidos pelo objeto, independente de sua classe ou superclasses; os quadrantes da direita dependem de tipos explicitamente nomeados no código: a classe do objeto, ou suas superclasses.
+image::../images/mapa-da-tipagem.png[align="center",pdfwidth=12cm]
+
+Podemos as quatro abordagens assim:
+
+Tipagem pato (_duck typing_)::
+    O((("duck typing"))) tratamento padrão para tipos em Python desde o início.
+    Estamos estudando tipagem pato desde o primeiro capítulo do volume 1.
+Tipagem ganso (_goose typing_)::
+    A((("goose typing", "definition of term"))) abordagem suportada pelas classes base abstratas
+    (ABCs, _sigla em inglês para Abstract Base Classes_) desde Python 2.6,
+    que depende de checar objetos contra ABCs durante a execução.
+    A tipagem ganso é um dos principais temas deste capítulo.
+Tipagem estática::
+    A((("static typing"))) abordagem tradicional das linguagens de tipos estáticos como C e Java;
+    suportada desde o Python 3.5 pelo módulo `typing`,
+    e aplicada por checadores de tipos externos compatíveis com a
+    https://fpy.li/pep484[PEP 484—Type Hints].
+    Este não é o foco deste capítulo.
+    A maior parte do https://fpy.li/8[«Capítulo 8»] (vol.1) e do <>
+    mais adiante são sobre tipagem estática.
+Tipagem pato estática (_static duck typing_)::
+    Uma((("static duck typing"))) abordagem popularizada pela linguagem Go;
+    suportada por subclasses de `typing.Protocol`—lançada no Python 3.8 e também
+    aplicada com o suporte de checadores de tipos externos. Tratamos desse tema
+    pela primeira vez na https://fpy.li/8m[«Seção 8.5.10»] (vol.1), e
+    continuamos neste capítulo.
+
+
+=== O mapa de tipagem
+
+As((("interfaces", "typing map")))((("typing map"))) quatro abordagens retratadas na
+<> são complementares: elas têm diferentes prós e contras.
+Não faz sentido descartar qualquer uma delas.
+
+Cada uma dessas quatro abordagens depende de interfaces para funcionar, mas a
+tipagem estática pode ser implementada de forma limitada usando apenas tipos
+concretos em vez de abstrações de interfaces como protocolos e classes base
+abstratas.
+Este capítulo é sobre tipagem pato, tipagem ganso, e
+tipagem pato estática—disciplinas de tipagem com foco em interfaces.
+
+O((("interfaces", "topics covered")))((("protocols", "topics covered")))
+capítulo está dividido em quatro seções principais, tratando de três dos quatro
+quadrantes no Mapa de Sistemas de Tipagem. (<>):
+
+* A <> compara duas formas de tipagem estrutural com
+protocolos—o lado esquerdo do Mapa.
+
+* A <> se aprofunda na tipagem pato, que já é familiar para
+quem programa em Python. Vamos ver como fazê-la mais segura,
+preservando sua melhor qualidade: a flexibilidade.
+
+* A <> explica o uso de ABCs para uma checagem de tipo mais
+estrita durante a execução do código. É a seção mais longa, não por ser a mais
+importante, mas porque há mais seções sobre tipagem pato, tipagem pato estática e
+tipagem estática em outras partes do livro.
+
+* A <> cobre o uso, a implementação e o design de subclasses
+de `typing.Protocol`—para checagem de tipo estática e durante a execução.
+
+
+=== Novidades neste capítulo
+
+Editei((("interfaces", "significant changes to")))((("protocols",
+"significant changes to"))) profundamente este capítulo,
+e ele ficou cerca de 24% mais longo que o capítulo correspondente
+(o capítulo 11) na primeira edição de _Python Fluente_.
+Apesar de algumas seções e muitos parágrafos serem idênticos, há muito
+conteúdo novo.
+Estes são os principais acréscimos e modificações:
+
+* A introdução do capítulo e o Mapa de Sistemas de Tipagem
+(<>) são novos. Essa é a chave da maior parte do
+conteúdo novo—e de todos os outros capítulos relacionados à tipagem em Python
+≥ 3.8.
+
+* A <> compara
+protocolos dinâmicos e estáticos.
+
+* Atualizei a <> e dei um título que destaca sua
+importância: Programação defensiva e "falhe logo"
+
+* A <> é toda nova. Ela se apoia na apresentação inicial na
+https://fpy.li/8m[«Seção 8.5.10»] (vol.1).
+
+* Atualizei os diagramas de classe de `collections.abc` para incluir a ABC
+`Collection`, introduzida no Python 3.6.
+
+Na primeira edição de _Python Fluente_ escrevi uma seção encorajando o uso das
+ABCs do módulo `numbers` para tipagem ganso.
+Na <> explico por que, atualmente, é melhor usar
+protocolos numéricos estáticos do módulo `typing` como `SupportsFloat` se você
+planeja usar checadores de tipos estáticos, ou checagem durante a execução no
+estilo da tipagem ganso.
+
+
+[[two_kinds_protocols_sec]]
+=== Dois tipos de protocolos
+
+A((("protocols", "meanings of protocol"))) palavra _protocolo_ tem significados
+diferentes na ciência da computação, dependendo do contexto.
+Um protocolo de
+rede como o HTTP especifica comandos que um cliente pode enviar para um
+servidor, como `GET`, `PUT` e `HEAD`.
+
+Vimos na <> que um protocolo especifica métodos
+que um objeto precisa oferecer para cumprir um papel.
+
+O exemplo `FrenchDeck` no https://fpy.li/1[«Capítulo 1»] (vol.1) demonstra um protocolo, o
+protocolo de sequência: os métodos que permitem a um objeto Python se comportar
+como uma sequência.
+
+Implementar um protocolo completo pode exigir muitos métodos, mas muitas vezes
+não há problema em implementar apenas parte dele.
+Considere a classe `Vowels` no
+<>.
+
+
+[[ex_minimal_sequence]]
+.Implementação parcial do protocolo de sequência usando `+__getitem__+`
+====
+[source, python]
+----
+>>> class Vowels:
+...     def __getitem__(self, i):
+...         return 'AEIOU'[i]
+...
+>>> v = Vowels()
+>>> v[0]
+'A'
+>>> v[-1]
+'U'
+>>> for c in v: print(c)
+...
+A
+E
+I
+O
+U
+>>> 'E' in v
+True
+>>> 'Z' in v
+False
+----
+====
+
+Implementar `+__getitem__+` é o suficiente para obter itens pelo índice, e
+também para permitir iteração e o operador `in`. O método
+`+__getitem__+` é o método essencial do protocolo de sequência.
+
+Veja a seção
+https://fpy.li/6k[Protocolo de Sequência]
+do Manual de referência da API Python/C:
+
+`int PySequence_Check(PyObject *o)`::
+    Retorna `1` se o objeto oferecer o protocolo de sequência,
+    caso contrário retorna `0`.
+    Note que esta função retorna `1` para classes Python com um método
+    `+__getitem__+`, a menos que sejam subclasses de `dict` [...]
+
+Esperamos que uma sequência também suporte `len()`, através da implementação de
+`+__len__+`. `Vowels` não tem um método `+__len__+`, mas ainda assim se comporta
+como uma sequência em alguns contextos.
+E isso pode ser o suficiente para nossos
+propósitos.
+Por isso gosto de dizer que um protocolo é uma "interface
+informal." Também é assim que protocolos são entendidos em Smalltalk, o primeiro
+ambiente de programação orientado a objetos a usar esse termo.
+
+Exceto em páginas sobre programação de redes, a maioria dos usos da palavra
+"protocolo" na documentação de Python se refere a essas interfaces informais.
+
+Agora, com a adoção da
+https://fpy.li/pep544[_PEP 544—Protocols: Structural subtyping (static duck typing)_]
+no Python 3.8, a palavra "protocolo" ganhou um novo sentido em Python—um sentido próximo,
+mas diferente.
+Como vimos na https://fpy.li/8m[«Seção 8.5.10»] (vol.1),
+a PEP 544 nos permite criar subclasses de `typing.Protocol` para definir
+um ou mais métodos que uma classe
+deve implementar (ou herdar) para satisfazer um checador de tipos estático.
+
+Quando precisar ser específico, vou adotar os seguintes termos:
+
+Protocolo dinâmico::
+  Os((("dynamic protocols"))) protocolos informais que Python sempre teve.
+  Protocolos dinâmicos são implícitos, definidos por convenção e descritos na
+  documentação. Os protocolos dinâmicos mais importantes de Python são
+  implementados no próprio interpretador, e documentados no capítulo
+  https://fpy.li/2j[Modelo de Dados] em _A Referência da Linguagem Python_.
+
+Protocolo estático::
+  Um((("static protocols", "definition of"))) protocolo como definido pela
+  https://fpy.li/pep544[_PEP 544—Protocols..._],
+  a partir de Python 3.8. Um protocolo estático é declarado explicitamente
+  como uma subclasse de `typing.Protocol`.
+
+Há duas diferenças fundamentais entre eles:
+
+* Um objeto pode implementar apenas parte de um protocolo dinâmico e ainda assim
+ser útil; mas para satisfazer um protocolo estático, o objeto precisa oferecer
+todos os métodos declarados na classe do protocolo, mesmo que seu programa não
+precise de todos eles.
+
+* Protocolos estáticos podem ser inspecionados por checadores de tipos
+estáticos, protocolos dinâmicos não.
+
+Os dois tipos de protocolo têm uma característica essencial: uma classe
+nunca precisa declarar que suporta um protocolo pelo nome (por herança).
+
+Antes dos protocolos estáticos, Python já oferecia outra forma de definir uma
+interface explícita no código: uma classe base abstrata (ABC).
+O restante deste capítulo trata de protocolos dinâmicos e estáticos, bem como
+das ABCs.
+
+[[prog_ducks_sec]]
+=== Programando patos
+
+Vamos((("protocols", "sequence and iterable protocols",
+id="Pseqit13")))((("sequence protocol", id="seqpro13")))((("Iterable interface",
+id="itpro13")))((("interfaces", "Iterable interface"))) começar nossa discussão
+de protocolos dinâmicos com os dois mais importantes em Python: o protocolo de
+sequência e o iterável. O interpretador faz grandes esforços para lidar com
+objetos que fornecem mesmo uma implementação mínima desses protocolos, como
+explicado na próxima seção.
+
+[[python_digs_seq_sec]]
+==== Python curte sequências
+
+A filosofia do Modelo de Dados de Python é cooperar o máximo possível com os
+protocolos dinâmicos essenciais. Quando se trata de sequências, Python faz de
+tudo para lidar até com implementações mais rudimentares.
+
+A <>((("UML class diagrams", "Sequence ABC and abstract
+classes"))) mostra como a interface `Sequence` está formalizada como uma ABC. O
+interpretador Python e as sequências embutidas como `list`, `str`, etc., não
+dependem de forma alguma daquela ABC. Só estou usando a figura para descrever o
+que uma `Sequence` completa deve oferecer.
+
+[role="width-90"]
+[[sequence_uml_repeat]]
+.Diagrama de classe UML para a ABC `Sequence` e classes abstratas relacionadas de `collections.abc`. As setas de herança apontam de uma subclasse para suas superclasses. Nomes em itálico são métodos abstratos. Antes de Python 3.6, não existia uma ABC `Collection`—`Sequence` era uma subclasse direta de `Container`, `Iterable` e `Sized`.
+image::../images/flpy_1302.png[align="center",pdfwidth=10cm]
+
+[TIP]
+====
+A maior parte das ABCs no módulo `collections.abc` existe para formalizar
+interfaces que já eram implementadas por objetos nativos e implicitamente
+suportadas pelo interpretador, muito antes daquele módulo existir.
+As ABCs são úteis como pontos de partida para novas classes, e
+para permitir checagem de tipo explícita durante a execução (tipagem ganso),
+bem como para servirem de dicas de tipo para checadores de tipos estáticos.
+====
+
+Estudando a <>, vemos que uma subclasse concreta de
+`Sequence` deve implementar `+__getitem__+` e `+__len__+` (de `Sized`). Todos os
+outros métodos `Sequence` são concretos, então as subclasses podem herdar suas
+implementações ou fornecer versões melhores.
+
+Agora, lembre-se da classe `Vowels` no <>. Ela não herda de
+`abc.Sequence` e implementa apenas `+__getitem__+`.
+
+As instâncias de `Vowels` são iteráveis porque, na falta de um `+__iter__+`,
+Python tenta iterar invocando `+__getitem__+` com índices inteiros começando em
+`0`. Da mesma forma que Python é esperto o suficiente para iterar sobre
+instâncias de `Vowels`, ele também consegue fazer o operador `in` funcionar
+mesmo quando o método `+__contains__+` não existe: ele faz uma busca sequencial
+para verificar se o item está presente.
+
+Em resumo, dada a importância das sequências como estruturas de dados, Python consegue
+fazer a iteração e o operador `in` funcionarem invocando `+__getitem__+` quando
+`+__iter__+` e `+__contains__+` não estão presentes.
+
+O `FrenchDeck` original do https://fpy.li/1[«Capítulo 1»] (vol.1) também não é subclasse de
+`abc.Sequence`, mas ele implementa os dois métodos do protocolo de sequência:
+`+__getitem__+` e `+__len__+`. Veja o <>.
+
+[[ex_pythonic_deck_repeat]]
+.Um baralho como uma sequência de cartas, como https://fpy.li/8x[«Exemplo 1 do Capítulo 1»] (vol.1)
+====
+[source, python]
+----
+include::../code/01-data-model/frenchdeck.py[]
+----
+====
+
+Muitos dos exemplos no https://fpy.li/1[«Capítulo 1»] (vol.1) funcionam por causa do tratamento
+especial que Python dá a estruturas vagamente semelhantes a uma sequência.
+O protocolo iterável em Python representa uma forma extrema de tipagem pato:
+o interpretador tenta dois métodos diferentes para iterar sobre objetos.
+
+Para deixar mais claro, os comportamentos que descrevi nessa seção estão
+implementados no próprio interpretador, na maioria dos casos em C. Eles não
+dependem dos métodos da ABC `Sequence`. Por exemplo, os métodos concretos
+`+__iter__+` e `+__contains__+` na classe `Sequence` emulam comportamentos
+internos do interpretador Python. Se tiver curiosidade, veja o código-fonte
+destes métodos em https://fpy.li/13-3[_Lib/_collections_abc.py_].
+
+Agora vamos estudar um exemplo que demonstra por que checadores de tipos
+estáticos não têm como lidar com protocolos
+dinâmicos.((("", startref="Pseqit13")))((("", startref="seqpro13")))((("",
+startref="itpro13")))
+
+
+==== Monkey patching: implementando um protocolo em runtime
+
+_Monkey patching_((("protocols", "implementing at runtime",
+id="Prun13")))((("monkey-patching", id="monkey13"))) é o ato de remendar (_patch_)
+dinamicamente um programa durante a execução do código (_runtime_),
+para acrescentar funcionalidade ou corrigir bugs. Por exemplo, a biblioteca de
+rede https://www.gevent.org/api/gevent.monkey.html[_gevent_] faz "monkey patch"
+em partes da biblioteca padrão de Python, para permitir concorrência sem threads ou
+`async`/`await`.footnote:[O artigo https://fpy.li/13-4["Monkey patch"] na
+Wikipedia tem um exemplo engraçado em Python.]
+O monkey patch não lê nem altera o código-fonte do programa,
+apenas os objetos na memória que representam as partes do programa,
+como módulos, classes e funções.
+
+Vamos fazer _monkey patch_ na classe `FrenchDeck` do <>
+para superar uma grande limitação: ela não pode ser embaralhada. Anos atrás,
+quando escrevi pela primeira vez o exemplo `FrenchDeck`, implementei um método
+`shuffle`. Depois tive uma sacada pythônica: se um `FrenchDeck` funciona como
+uma sequência, não precisa ter um método `shuffle`, pois já existe a função
+`random.shuffle`, que "embaralha a sequência x internamente" conforme a
+https://fpy.li/6m[documentação oficial]. 
+
+A função `random.shuffle` é usada assim:
+
+[source, python]
+----
+>>> from random import shuffle
+>>> l = list(range(10))
+>>> shuffle(l)
+>>> l
+[5, 2, 9, 7, 8, 3, 1, 4, 0, 6]
+----
+
+[TIP]
+====
+Ao adotar protocolos estabelecidos, aumenta muito suas chances de aproveitar o
+código já existente na biblioteca padrão e em bibliotecas de terceiros, graças à
+tipagem pato.
+====
+
+Entretanto, se tentamos usar shuffle com uma instância de `FrenchDeck`
+ocorre uma exceção, como visto no <>.
+
+[[ex_unshuffable]]
+.`random.shuffle` não funciona com `FrenchDeck`
+====
+[source, python]
+----
+>>> from random import shuffle
+>>> from frenchdeck import FrenchDeck
+>>> deck = FrenchDeck()
+>>> shuffle(deck)
+Traceback (most recent call last):
+  File "", line 1, in 
+  File ".../random.py", line 265, in shuffle
+    x[i], x[j] = x[j], x[i]
+TypeError: 'FrenchDeck' object does not support item assignment
+----
+====
+
+A mensagem de erro é clara: "o objeto 'FrenchDeck' não suporta a atribuição de
+itens". O problema é que `shuffle` opera internamente, trocando os itens de
+lugar dentro da coleção, mas `FrenchDeck` só implementa o protocolo de sequência
+imutável. Para ser uma sequência mutável, `FrenchDeck` precisa oferecer um
+método `+__setitem__+`.
+
+Como Python é dinâmico, podemos consertar isso durante a execução, até mesmo no
+console interativo. O <> mostra como fazer isso.
+
+[[ex_monkey_patch]]
+."Monkey patching" o `FrenchDeck` para torná-lo mutável e compatível com `random.shuffle` (continuação do <>)
+====
+[source, python]
+----
+>>> def set_card(deck, position, card):  <1>
+...     deck._cards[position] = card
+...
+>>> FrenchDeck.__setitem__ = set_card  <2>
+>>> shuffle(deck)  <3>
+>>> deck[:5]
+[Card(rank='3', suit='hearts'), Card(rank='4', suit='diamonds'), Card(rank='4',
+suit='clubs'), Card(rank='7', suit='hearts'), Card(rank='9', suit='spades')]
+----
+====
+<1> Cria uma função que recebe `deck`, `position`, e `card` como argumentos.
+
+<2> Atribui aquela função a um atributo chamado `+__setitem__+` na classe
+`FrenchDeck`.
+
+<3> `deck` agora pode ser embaralhado, pois acrescentei o método necessário do
+protocolo de sequência mutável.
+
+A assinatura do método especial `+__setitem__+` está definida na
+Referência da Linguagem Python em
+https://fpy.li/6n[Emulando tipos contêineres].
+Aqui nomeei os argumentos `deck, position, card`—e não `self, key, value` como na
+referência da linguagem—para mostrar que todo método Python começa sua vida como
+uma função comum, e nomear o primeiro argumento `self` é só uma convenção.
+Fugir da convenção é OK em uma sessão no console onde o código é descartável,
+mas em um arquivo de código-fonte de Python é muito melhor usar
+`self`, `key`, e `value`, seguindo a documentação.
+
+O truque é que `set_card` pressupõe que o `deck` tem um atributo chamado
+`+_cards+`, e seu valor deve ser uma sequência mutável. A função `set_cards` é
+então anexada à classe `FrenchDeck` como o método especial
+`+__setitem__+`. Isso é um exemplo de _monkey patching_: modificar uma classe ou
+módulo durante a execução, sem tocar no código-fonte. O "monkey patching" é
+poderoso, mas o código que executa a modificação fica muito intimamente acoplado
+ao programa sendo modificado, muitas vezes trabalhando com atributos privados e
+não-documentados.
+
+Além de ser um exemplo de monkey patching, o <> enfatiza a
+natureza dinâmica dos protocolos na tipagem pato: `random.shuffle` não
+se importa com a classe do argumento, ela só precisa que o objeto implemente
+métodos do protocolo de sequência mutável. Não importa sequer se o objeto
+"nasceu" com os métodos necessários ou se eles foram de alguma forma adquiridos
+depois.
+
+Quando bem aplicada, tipagem pato não é loucamente insegura ou difícil de depurar e
+manter. A próxima seção mostra alguns padrões de programação úteis para detectar
+protocolos dinâmicos sem recorrer a checagens explícitas.((("",
+startref="monkey13")))((("", startref="Prun13")))
+
+[[defensive_duck_prog_sec]]
+==== Programação defensiva e "falhe logo"
+
+Programação defensiva((("protocols", "defensive programming",
+id="Pdefens13")))((("fail-fast philosophy", id="failfast13")))((("defensive programming",
+id="defprog13"))) é como direção defensiva: um conjunto de
+práticas para melhorar a segurança, mesmo na presença de programadores
+(ou motoristas) descuidados.
+
+Muitos bugs não podem ser encontrados exceto durante a execução—mesmo nas
+principais linguagens de tipagem estática.footnote:[Por isso a necessidade de
+testes automatizados.] Em uma linguagem de tipagem dinâmica, "falhe logo" 
+(_fail fast_) é um ótimo conselho para codar programas mais seguros e mais fáceis de manter.
+Falhar logo significa assegurar que os erros em tempo de execução
+sejam detectados o mais cedo possível.
+Por exemplo, rejeitando argumentos inválidos no início do corpo de uma
+função.
+
+Exemplo prático: quando você escreve código que aceita uma sequência de
+itens para processar internamente como uma `list`, não valide o argumento
+só por checagem de tipo. Em vez disso, receba o argumento e construa
+imediatamente uma `list` a partir dele. Um exemplo desse padrão de programação é
+o método `+__init__+` no <>, que veremos mais à frente nesse capítulo:
+
+[source, python]
+----
+    def __init__(self, iterable):
+        self._balls = list(iterable)
+----
+
+Desta forma você torna seu código mais flexível, pois o construtor de `list()`
+processa qualquer iterável que caiba na memória. Se o argumento não for
+iterável, a chamada vai falhar logo com uma exceção de `TypeError`
+bastante clara, no exato momento em que o objeto for inicializado. Se
+quiser ser mais explícito, pode colocar a chamada a `list()` em um
+`try/except`, para adequar a mensagem de erro—mas eu escreveria este código extra
+apenas em uma API externa, pois a falha já estaria bem visível para os
+mantenedores que conhecem a base de código. De toda forma, a chamada errônea vai aparecer
+perto do final do traceback, tornando-a fácil de corrigir. Se você não barrar o
+argumento inválido no construtor da classe, o programa vai quebrar mais tarde,
+quando algum outro método da classe precisar usar a variável `self._balls` e ela
+não for uma `list`. Então a causa do problema estará mais distante e 
+será mais difícil de encontrar.
+
+Naturalmente, seria ruim passar o argumento para `list()` se os dados não devem
+ser copiados, ou por seu tamanho ou porque quem chama a função,
+espera que os itens sejam modificados internamente, como no caso de
+`random.shuffle`. Neste caso, uma checagem durante a execução como
+`isinstance(x, abc.MutableSequence)` seria a melhor opção:
+a abordagem da tipagem ganso.
+
+Se tiver receio de consumir um gerador infinito—algo que não é um
+problema muito comum—pode começar chamando `len()` com o argumento. Isso
+rejeitaria iteradores, mas lidaria de forma segura com tuplas, arrays e outras
+classes existentes ou futuras que implementem a interface `Sequence` completa.
+Chamar `len()` normalmente não custa muito, e um argumento inválido vai gerar
+um erro na hora.
+
+Por outro lado, se qualquer iterável for aceitável, chame `iter(x)` assim que
+possível, para obter um iterador, como veremos na https://fpy.li/89[«Seção 17.3»] (vol.3). E
+novamente, se `x` não for iterável, isso falhará logo com uma exceção
+fácil de depurar.
+
+<<<
+Nos casos que acabei de descrever, uma dica de tipo poderia apontar alguns
+problemas mais cedo, mas não todos os problemas. Lembre-se de que o tipo `Any` é
+_consistente-com_ qualquer outro tipo. Inferência de tipo pode fazer com que uma
+variável seja marcada com o tipo `Any`. Quando isso acontece, o checador de
+tipos se torna inútil. Além disso, dicas de tipo não são aplicadas durante a
+execução. Falhar logo é a última linha de defesa.
+
+Código defensivo usando tipagem pato também pode incluir lógica para lidar
+com tipos diferentes sem usar testes com `isinstance()` e `hasattr()`.
+
+Um exemplo é como poderíamos imitar como
+https://fpy.li/13-8[`collections.namedtuple`] lida com o argumento
+`field_names`: ele aceita uma única string com identificadores
+separados por espaços ou vírgulas, ou uma sequência de identificadores. O
+<> mostra como eu faria isso usando tipagem pato.
+
+[[ex_duck_typing_str_list]]
+.Tipagem pato para lidar com uma string ou um iterável de strings
+====
+[source, python]
+----
+    try:  <1>
+        field_names = field_names.replace(',', ' ').split()  <2>
+    except AttributeError:  <3>
+        pass  <4>
+    field_names = tuple(field_names)  <5>
+    if not all(s.isidentifier() for s in field_names):  <6>
+        raise ValueError('field_names must all be valid identifiers')
+----
+====
+
+<1> Supõe que é uma string.
+
+<2> Converte vírgulas em espaços e divide o resultado em uma lista de nomes.
+
+<3> Perdão, `field_names` não grasna como uma `str`: não tem `.replace`, ou
+tem um `.replace` que devolve algo que não funciona com `.split`
+
+<4> Se um `AttributeError` aconteceu, então `field_names` não é uma `str`.
+Supomos que já é um iterável de nomes.
+
+<5> Para ter certeza de que é um iterável e para manter nossa própria cópia,
+criamos uma tupla com o que temos. Uma tuple é mais compacta que uma lista, e
+também impede que meu código troque os nomes por acidente.
+
+<6> Usamos `str.isidentifier` para garantir que todos os nomes são válidos.
+
+<<<
+O passo `②` do <> é uma aplicação de EAFP ou
+Princípio de Hopper.footnote:[A pioneira
+da computação Grace Hopper dizia que, para inovar em uma burocracia,
+é mais fácil pedir perdão do que permissão
+(_"(It's) Easier to Ask Forgiveness than Permission"_ ou _EAFP_).]
+Em vez de testar se `+field_names+` é uma string,
+invocamos métodos como se fosse uma string,
+e se não der certo, tratamos a exceção.
+Não pedimos licença:
+fazemos o que temos que fazer e pedimos perdão se for necessário.
+
+O <> mostra uma situação em que a tipagem pato é mais
+expressiva que dicas de tipo estáticas. Não há como escrever uma dica de tipo
+que diga "o argumento `field_names` deve ser uma string de identificadores separados por
+espaços ou vírgulas." Esta é a parte relevante da assinatura de `namedtuple` no
+typeshed (veja o código-fonte completo em 
+https://fpy.li/13-9[_stdlib/3/collections/__init__.pyi_]):
+
+[source, python]
+----
+    def namedtuple(
+        typename: str,
+        field_names: Union[str, Iterable[str]],
+        *,
+        # outros parâmetros omitidos
+----
+
+Como se vê, `field_names` está anotado como `Union[str, Iterable[str]]`,
+que ajuda em parte, mas não é suficiente para descrever a estrutura interna da
+string.
+
+Após revisar protocolos dinâmicos, passamos para uma forma mais explícita de
+checagem de tipo durante a execução: tipagem ganso.((("",
+startref="Pdefens13")))((("", startref="failfast13")))((("",
+startref="defprog13")))
+
+[[goose_typing_sec]]
+=== Tipagem ganso
+
+[quote, Bjarne Stroustrup, criador do {cpp}]
+____
+
+Uma classe abstrata representa uma interface.footnote:[No original: "An abstract
+class represents an interface", Bjarne Stroustrup, _The Design and Evolution of
+{cpp}_ (Addison-Wesley, 1994), p. 278.]
+
+____
+
+Python((("goose typing", "abstract base classes (ABCs)",
+id="GTabcs13")))((("ABCs (abstract base classes)", "goose typing and",
+id="ABCgoose13"))) não tem uma palavra-chave `interface`. Usamos classes base
+abstratas (ABCs) para definir interfaces úteis para checagem explícita de tipo
+durante a execução, e também para anotações compatíveis com
+checadores de tipos estáticos.
+
+
+O verbete
+https://fpy.li/6p[classe base abstrata]
+no Glossário da Documentação de Python tem uma boa explicação do
+valor dessas estruturas para linguagens que usam tipagem pato:
+
+[quote]
+____
+
+Classes base abstratas complementam a tipagem pato, fornecendo uma maneira de
+definir interfaces quando outras técnicas, como `hasattr()`, seriam desajeitadas
+ou sutilmente erradas (por exemplo, com métodos mágicos). ABCs introduzem
+subclasses virtuais, classes que não herdam de uma classe mas ainda são
+reconhecidas por `isinstance()` e `issubclass()`; veja a documentação do módulo
+`abc`.
+____
+
+A tipagem ganso é uma abordagem à checagem de tipo durante a execução que se
+apoia nas ABCs. Vou deixar que Alex Martelli explique, no texto _<>_.
+
+[NOTE]
+====
+Sou muito grato a meus amigos Alex Martelli e Anna Ravenscroft. Mostrei a eles a
+primeira lista de tópicos do _Python Fluente_ na OSCON 2013, e eles me
+encorajaram a submeter à O'Reilly para publicação. Depois os dois
+contribuíram com revisões técnicas minuciosas. Alex já era a pessoa mais citada
+nesse livro quando se ofereceu para escrever este ensaio.
+====
+
+[[waterfowl_essay]]
+.Pássaros aquáticos e as ABCs
+****
+
+*por Alex Martelli*
+
+Fui https://fpy.li/13-11[creditado na Wikipedia] por ajudar a popularizar
+o meme útil e frase de efeito "_duck typing_" (isto é, ignorar o tipo declarado
+de um objeto, e em vez disso se dedicar a assegurar que o objeto implementa os
+nomes, assinaturas e semântica dos métodos necessários para o uso pretendido).
+
+Em Python, isso essencialmente significa evitar o uso de `isinstance` para
+checar o tipo do objeto (sem nem mencionar a abordagem ainda pior de
+checar, por exemplo, se `type(foo) is bar`—que é corretamente
+considerado um anátema, pois inibe até as formas mais simples de herança!).
+
+No geral, a abordagem da tipagem pato continua muito útil em inúmeros
+contextos—mas em muitos outros, uma nova abordagem muitas vezes preferível
+evoluiu ao longo do tempo. E aqui começa nossa história...
+
+Em gerações recentes, a taxonomia de gênero e espécies (incluindo, mas não
+limitada à família de pássaros aquáticos conhecida como Anatidae) foi guiada
+principalmente pela _fenética_—uma abordagem centrada nas similaridades de
+morfologia e comportamento... principalmente traços _observáveis_. A analogia
+com "_duck typing_" era evidente.
+
+Entretanto, a evolução paralela muitas vezes pode produzir características
+similares, tanto morfológicas quanto comportamentais, em espécies sem qualquer
+relação de parentesco, que apenas calharam de evoluir em nichos ecológicos
+similares, porém separados. "Similaridades acidentais" parecidas acontecem
+também em programação—por exemplo, considere um exemplo clássico de
+programação orientada a objetos:footnote:[NT: O exemplo citado por Martelli é
+intraduzível. Ele joga com três significados diferentes do verbo
+"to draw": artista desenha; o pistoleiro saca (a arma);
+a loteria sorteia um número.]
+
+[source, python]
+----
+class Artist:
+    def draw(self): ...
+
+class Gunslinger:
+    def draw(self): ...
+
+class Lottery:
+    def draw(self): ...
+----
+
+Obviamente, a mera existência de um método chamado `draw`, sem parâmetros,
+não é suficiente para garantir que dois objetos `x` e `y`,
+que aceitem as invocações `x.draw()` e `y.draw()`,
+são de qualquer forma intercambiáveis ou abstratamente equivalentes—nada
+pode ser inferido sobre a similaridade da semântica resultante de tais chamadas.
+Na verdade, é necessário um programador consciente para,
+de alguma forma, _assegurar_ afirmativamente que tal equivalência é verdadeira em algum nível.
+
+Em biologia (e outras disciplinas), este problema levou à emergência (e, em
+muitas facetas, à dominância) de uma abordagem alternativa à _fenética_,
+conhecida como ((("cladistics"))) __cladística__ — que baseia as escolhas
+taxonômicas em características herdadas de ancestrais comuns em vez daquelas que
+evoluíram de forma independente (o sequenciamento de DNA cada vez mais barato e
+rápido vem tornando a cladística bastante prática em mais casos).
+
+Por exemplo, os Chloephaga, gênero de gansos sul-americanos (antes classificados
+como próximos a outros gansos) e as tadornas (gênero de patos sul-americanos)
+estão agora agrupados juntos na subfamília Tadornidae (sugerindo que eles são
+mais próximos entre si do que de qualquer outro Anatidae, pois compartilham um
+ancestral comum mais próximo). Além disso, a análise de DNA mostrou que o
+Asarcornis (pato da floresta ou pato de asas brancas) não é tão próximo do
+Cairina moschata (pato-do-mato), esse último uma tadorna, como as similaridades
+corporais e comportamentais sugeriram por tanto tempo—então o pato da floresta
+foi reclassificado em um gênero próprio, inteiramente fora da subfamília!
+
+Isso importa? Depende do contexto! Para o propósito de decidir como cozinhar uma
+ave após caçá-la, por exemplo, características observáveis específicas (mas
+nem todas—a plumagem, por exemplo, é de mínima importância nesse contexto),
+especialmente textura e sabor (a boa e velha fenética), podem ser mais
+relevantes que a cladística. Mas para outros problemas, tal como a
+suscetibilidade a diferentes patógenos (se quiser criar aves aquáticas em
+cativeiro, ou preservá-las na natureza), a proximidade do DNA pode ser mais
+importante.
+
+Então, a partir dessa analogia aproximada com as revoluções taxonômicas no mundo
+das aves aquáticas, estou recomendando suplementar (não substituir
+inteiramente—em determinados contextos ela ainda servirá) o bom e velho _duck
+typing_ por... _goose typing_ (tipagem ganso)!
+
+_Goose typing_ significa o seguinte: `isinstance(obj, cls)` agora é plenamente
+aceitável... desde que `cls` seja uma classe base abstrata—em outras palavras, a
+metaclasse de `cls` é `abc.ABCMeta`.
+
+Você vai encontrar muitas classes abstratas prontas em `collections.abc` (e
+outras no módulo `numbers` da Biblioteca Padrão de Python)footnote:[Você também
+pode, claro, definir suas próprias ABCs—mas eu não recomendaria esse caminho a
+ninguém, exceto aos mais avançados pythonistas, da mesma forma que os
+desencorajaria de definir suas próprias metaclasses customizadas... e mesmo para
+os ditos "mais avançados pythonistas", aqueles que exibem o domínio de todos
+os recantos por mais obscuros da linguagem, essas não são ferramentas de
+uso frequente. Este tipo de "metaprogramação profunda", se alguma vez for
+apropriada, o será no contexto dos autores de frameworks abrangentes, projetados
+para serem estendidos de forma independente por inúmeras equipes de
+desenvolvimento diferentes... menos que 1% dos "mais avançados pythonistas"
+precisará disso alguma vez na vida!—_A.M_]
+
+Dentre as muitas vantagens conceituais das ABCs sobre classes concretas (e.g., a
+prescrição de Scott Meyer “toda classe não-final (não-folha) deveria ser
+abstrata”; veja o https://fpy.li/13-12[Item 33] de seu livro, _More Effective
+{cpp}_, Addison-Wesley), as ABCs de Python acrescentam uma grande vantagem
+prática: o método de classe `register`, que permite ao código da aplicação
+"declarar" que determinada classe é uma subclasse "virtual" de uma ABC (para
+este propósito, a classe registrada precisa cumprir os requisitos de nome de
+métodos e assinatura da ABC e, mais importante, o contrato semântico
+subjacente—mas não precisa ter sido desenvolvida com qualquer conhecimento da
+ABC, e especificamente não precisa herdar dela!). Isso é um longo caminho andado
+na direção de quebrar a rigidez e o acoplamento forte que torna herança algo
+para ser usado com mais cautela que aquela tipicamente praticada pela maioria
+dos programadores orientados a objetos.
+
+Em algumas ocasiões você sequer precisa registrar uma classe para que uma ABC a
+reconheça como uma subclasse!
+
+Esse é o caso das ABCs cuja essência se resume em alguns métodos especiais.
+Por exemplo:footnote:[NT: Outro exemplo intraduzível. A frase "class struggle"
+é uma referência bem humorada ao conceito marxista da "luta de classes".]
+
+[source, python]
+----
+>>> class Struggle:
+...     def __len__(self): return 23
+...
+>>> from collections import abc
+>>> isinstance(Struggle(), abc.Sized)
+True
+----
+
+Como se vê, `abc.Sized` reconhece `Struggle` como uma `subclasse`, sem
+necessidade de registro, já que implementar o método especial chamado
+`+__len__+` é o suficiente (o método deve ser implementado com a sintaxe e
+semântica corretas—deve poder ser chamado sem argumentos e retornar um inteiro
+não-negativo indicando o "comprimento" do objeto; mas qualquer código que
+implemente um método com nome especial, como `+__len__+`, com uma sintaxe e uma
+semântica arbitrárias e incompatíveis tem problemas bem maiores que estes).
+
+Então, aqui está minha mensagem de despedida: sempre que você estiver
+implementando uma classe que incorpore quaisquer dos conceitos representados nas
+ABCs de `number`, `collections.abc` ou em outro framework que estiver usando,
+assegure-se (caso necessário) de ser uma subclasse ou de registrar sua classe com a
+ABC correspondente. No início de seu programa que utiliza uma biblioteca ou
+framework que define classes que omitiram esse passo, registre você mesmo as
+classes. Daí, quando precisar checar se um argumento é, por
+exemplo, "uma sequência", verifique se:
+
+[source, python]
+----
+isinstance(the_arg, collections.abc.Sequence)
+----
+
+E _não_ defina ABCs customizadas (ou metaclasses) em código de produção. Se você
+sentir uma forte necessidade de fazer isso, aposto que é um caso da síndrome de
+"todos os problemas se parecem com um prego" em alguém que acabou de ganhar um
+novo martelo brilhante—você (e os futuros mantenedores de seu código) serão
+mais felizes se limitando a código simples e direto, e evitando((("",
+startref="GTabcs13")))((("", startref="ABCgoose13"))) tais profundezas. _Valē!_
+
+****
+
+Em((("goose typing", "overview of"))) resumo, tipagem ganso implica:
+
+* Criar subclasses de ABCs, para tornar explícito que você está implementando
+uma interface previamente definida.
+
+* Checagem de tipo durante a execução usando as ABCs em vez de classes concretas
+como segundo argumento para `isinstance` e `issubclass`.
+
+Alex também aponta que herdar de uma ABC é mais que implementar os métodos
+necessários: é também uma declaração de intenções clara da parte do
+desenvolvedor. A intenção também pode ficar explícita através do registro de uma
+subclasse virtual.
+
+[NOTE]
+====
+
+Detalhes sobre o uso de `register` são tratados na <>,
+mais adiante. Por hora, aqui está um pequeno exemplo: dada a classe
+`FrenchDeck`, se eu quiser que ela passe em uma checagem como
+`issubclass(FrenchDeck, Sequence)`, posso torná-la uma _subclasse virtual_ da
+ABC `Sequence` assim:
+
+[source, python]
+----
+from collections.abc import Sequence
+Sequence.register(FrenchDeck)
+----
+====
+
+O uso de `isinstance` e `issubclass` se torna mais aceitável se você está
+checando ABCs em vez de classes concretas. Se usadas com classes concretas,
+checagens de tipo limitam o polimorfismo—um recurso essencial da programação
+orientada a objetos. Mas com ABCs esses testes são mais flexíveis. Afinal, se um
+componente não implementa uma ABC sendo uma subclasse—mas implementa os métodos
+necessários—ele sempre pode ser registrado posteriormente e passar naquelas
+checagens de tipo explícitas.
+
+Entretanto, mesmo com ABCs, você deve se precaver contra o uso excessivo de
+checagens com `isinstance`, pois isso pode ser sintoma de um design ruim.
+
+Normalmente, não é bom ter uma série de `if/elif/elif` com checagens de
+`isinstance` executando ações diferentes, dependendo do tipo de objeto: neste
+caso você deveria estar usando polimorfismo—isto é, projetando suas classes para
+permitir ao interpretador invocar os métodos corretos, em vez de
+codificar diretamente a lógica de despacho em blocos `if/elif/elif`.
+
+Por outro lado, não há problema em executar uma checagem com `isinstance` contra
+uma ABC se você quer garantir um contrato de API: "Cara, você precisa
+implementar isso se quiser me chamar," como costuma dizer o revisor técnico
+Lennart Regebro. Isso é especialmente útil em sistemas com arquiteturas
+modulares extensíveis por plug-ins. Fora dos frameworks, tipagem pato muitas
+vezes é mais simples e flexível que checagens de tipo explícitas.
+
+Por fim, em seu ensaio Alex reforça mais de uma vez a necessidade de limitar a
+criação de ABCs. Uso excessivo de ABCs imporia cerimônia a uma linguagem que se
+tornou popular por ser prática e pragmática. Durante o processo de revisão do
+_Python Fluente_, Alex colocou num e-mail:
+
+[quote]
+____
+ABCs servem para encapsular conceitos muito genéricos, abstrações introduzidas
+por um framework—coisa como "uma sequência" e "um número exato". [Os leitores]
+quase certamente não precisam escrever alguma nova ABC, apenas usar as já
+existentes de forma correta, para obter 99% dos benefícios sem qualquer risco
+sério de design mal-feito.
+____
+
+Agora vamos ver a tipagem ganso na prática.
+
+==== Criando uma subclasse de uma ABC
+
+Seguindo((("inheritance and subclassing", "subclassing ABCs", id="IASabcs13")))((("goose typing", "subclassing ABCs", id="GTsub13")))((("ABCs (abstract base classes)", "subclassing ABCs", id="ABCsub13"))) o conselho de Martelli, vamos aproveitar uma ABC existente, `collections.MutableSequence`, antes de ousar inventar uma nova.
+No <>, `FrenchDeck2` é explicitamente declarada como subclasse de `collections.MutableSequence`.
+
+[[ex_pythonic_deck2]]
+.frenchdeck2.py: `FrenchDeck2`, uma subclasse de `collections.MutableSequence`
+====
+[source, python]
+----
+include::../code/13-protocol-abc/frenchdeck2.py[]
+----
+====
+[role="pagebreak-before less_space"]
+<1> `+__setitem__+` é tudo que precisamos para possibilitar o embaralhamento...
+<2> ...mas uma subclasse de `MutableSequence` é forçada a implementar `+__delitem__+`, um método abstrato daquela ABC.
+<3> Também precisamos implementar `insert`, o terceiro método abstrato de `MutableSequence`.
+
+Python não verifica a implementação de métodos abstratos durante a importação
+(quando o módulo _frenchdeck2.py_ é carregado na memória e compilado),
+mas apenas durante a execução, quando tentamos de fato instanciar `FrenchDeck2`.
+Ali, se deixamos de implementar qualquer um dos métodos abstratos,
+recebemos uma exceção de `TypeError` com uma mensagem como
+_Can't instantiate abstract class FrenchDeck2 with abstract methods `+__delitem__+`, ``insert``_
+(Impossível instanciar a classe abstrata `FrenchDeck2` com os métodos abstratos `+__delitem__+`, ``insert``).
+Por isso precisamos implementar `+__delitem__+` e `insert`,
+mesmo se nossos exemplos usando `FrenchDeck2` não precisem desses comportamentos:
+a ABC `MutableSequence` os exige.
+
+Como((("UML class diagrams", "MutableSequence ABC and superclasses")))
+mostra a <>, nem todos os métodos das ABCs `Sequence`
+e `MutableSequence` ABCs são abstratos.
+
+[[mutablesequence_uml]]
+.Diagrama de classe UML para a ABC `MutableSequence` e suas superclasses em `collections.abc` (as setas de herança apontam das subclasses para as ancestrais; nomes em itálico são classes e métodos abstratos).
+image::../images/flpy_1303.png[]
+
+Para escrever `FrenchDeck2` como uma subclasse de `MutableSequence`, tive que
+pagar o preço de implementar `+__delitem__+` e `insert`, desnecessários em meus
+exemplos. Em troca, `FrenchDeck2` herda cinco métodos concretos de `Sequence`:
+`+__contains__+`, `+__iter__+`, `+__reversed__+`, `index`, e `count`. De
+`MutableSequence`, ela recebe outros seis métodos: `append`, `reverse`,
+`extend`, `pop`, `remove`, e `+__iadd__+`— que suporta o operador `+=` para
+concatenação direta.
+
+Os métodos concretos em cada ABC de `collections.abc` são implementados nos
+termos da interface pública da classe, então funcionam sem qualquer conhecimento
+da estrutura interna das instâncias.
+
+
+[TIP]
+====
+Como programador de uma subclasse concreta, você pode sobrescrever os métodos
+concretos herdados das ABCs com implementações mais eficientes. Por exemplo,
+`+__contains__+` funciona executando uma busca sequencial, mas se a sua classe
+de sequência mantém os itens ordenados, você pode escrever um `+__contains__+`
+que executa uma busca binária usando a função https://fpy.li/13-13[`bisect`] da
+biblioteca padrão. Veja
+https://fpy.li/bisect[_Managing Ordered Sequences with Bisect_]
+em _fluentpython.com_ para conhecer mais sobre esta função.
+====
+
+
+Para usar bem as ABCs, você precisa saber o que está disponível. Vamos então
+revisar as ABCs de `collections` a seguir.((("", startref="GTsub13")))((("",
+startref="ABCsub13")))((("", startref="IASabcs13")))
+
+[[abc_in_stdlib_sec]]
+==== ABCs na Biblioteca Padrão
+
+Desde((("goose typing", "ABCs in Python standard library",
+id="GTstlib13")))((("ABCs (abstract base classes)",
+"in Python standard library", secondary-sortas="Python standard library",
+id="ABCstndlib13")))((("collections.abc module", "abstract base classes defined in")))
+Python 2.6, a biblioteca padrão oferece várias ABCs. A maioria está
+definida no módulo `collections.abc`, mas há outras nos pacotes `io` e `numbers`,
+por exemplo.
+A <> é((("UML class diagrams", "ABCs in collections.abc"))) um
+diagrama de classe resumido (sem os nomes dos atributos) das 17 ABCs definidas
+em `collections.abc`. 
+
+[[collections_uml]]
+.Diagrama de classes UML para as ABCs em `collections.abc`.
+image::../images/flpy_1304.png[align="center",pdfwidth=11cm]
+
+
+
+
+[TIP]
+====
+
+Há dois módulos chamados `abc` na biblioteca padrão.
+Aqui estamos falando sobre o `collections.abc`.
+Para reduzir o tempo de carregamento, desde o Python 3.4 aquele módulo é implementado fora do pacote `collections` — em https://fpy.li/13-14[_Lib/_collections_abc.py_] — então é importado separado de `collections`.
+O((("abc.ABC class"))) outro módulo `abc` é apenas `abc` (i.e., https://fpy.li/13-15[_Lib/abc.py_]), onde a classe `abc.ABC` é definida.
+Toda ABC depende do módulo `abc`, mas não precisamos importá-lo nós mesmos, exceto para criar uma nova ABC.
+
+====
+
+<<<
+A documentação de `collections.abc` inclui uma ótima 
+https://fpy.li/13-16[«tabela»] resumindo as ABCs, suas relações e seus
+métodos abstratos e concretos (chamados "métodos mixin"). Há muita herança
+múltipla acontecendo na <>. Vamos dedicar a maior parte do
+<> à herança múltipla, mas por hora é suficiente dizer que isso
+normalmente não causa problemas no caso das ABCs.footnote:[Herança múltipla foi
+_considerada nociva_ e excluída do Java, exceto para interfaces:
+Interfaces Java podem estender múltiplas interfaces,
+e classes Java podem implementar múltiplas interfaces.]
+Vamos rever os grupos na <>:
+
+`Iterable`, `Container`, `Sized`::
+Toda coleção deveria herdar destas ABCs ou implementar protocolos compatíveis.
+`Iterable` define `+__iter__+` para suportar iteração,
+`Container` define `+__contains__+` para o operador `in`,
+e `Sized` define `+__len__+` para `len()`.
+
+`Collection`::
+Essa ABC não tem nenhum método próprio, mas foi acrescentada no Python 3.6 para
+facilitar a criação de subclasses de `Iterable`, `Container`, e `Sized`.
+
+`Sequence`, `Mapping`, `Set`::
+Esses são os principais tipos de coleções imutáveis, e cada um tem uma subclasse
+mutável. Um diagrama detalhado de `MutableSequence` é apresentado na
+<>; para `MutableMapping` e `MutableSet`, veja os
+diagramas UML no https://fpy.li/3[«Capítulo 3»] (vol.1).
+
+`MappingView`::
+No Python 3, os objetos devolvidos pelos métodos de mapeamentos `.items()`,
+`.keys()`, e `.values()` implementam as interfaces definidas em `ItemsView`,
+`KeysView`, e `ValuesView`, respectivamente. Os dois primeiros também
+implementam a rica interface de `Set`, com todos os operadores que vimos na
+https://fpy.li/8n[«Seção 3.11.1»] (vol.1).
+
+`Iterator`::
+Observe que iterator é subclasse de `Iterable`.
+Discutiremos este detalhe em https://fpy.li/17[«Capítulo 17»] (vol.3).
+
+`Callable`, `Hashable`::
+Estas não são coleções, mas `collections.abc` foi o primeiro pacote a definir
+ABCs na biblioteca padrão, e estas duas foram incluídas por serem importantes.
+Elas suportam a checagem de tipos de objetos
+que precisam ser invocáveis ou _hashable_.
+
+Para a detecção de invocável, a função embutida `callable(obj)` é mais
+conveniente que `insinstance(obj, Callable)`.
+
+Se `insinstance(obj, Hashable)` devolver `False`, pode ter certeza de que
+`obj` não é _hashable_. Mas se ela devolver `True`, pode ser um falso positivo.
+Isso é explicado no box seguinte.
+
+[[isinstance_mislead_box]]
+.`isinstance` com `Hashable` e `Iterable` pode te enganar
+****
+
+É fácil interpretar errado os resultados de testes usando `isinstance` e
+`issubclass` com as ABCs `Hashable` e `Iterable`. Quando
+`isinstance(obj, Hashable)` devolve `True`, significa apenas que a classe de `obj` implementa
+ou herda `+__hash__+`. Mas se `obj` é uma tupla contendo itens _unhashable_,
+então `obj` não é _hashable_, apesar do resultado positivo da checagem com
+`isinstance`.
+O revisor técnico Jürgen Gmach mostrou que a tipagem pato oferece
+o modo mais preciso de determinar se uma instância é _hashable_: chamar `hash(obj)`.
+Essa chamada vai levantar um `TypeError` se `obj` não for _hashable_.
+
+Por outro lado, mesmo quando `isinstance(obj, Iterable)` retorna `False`,
+o Python ainda pode ser capaz de iterar sobre `obj` usando `+__getitem__+`
+com índices baseados em 0, como vimos em https://fpy.li/1[«Capítulo 1»] (vol.1) e
+na <>. A documentação de
+https://fpy.li/6q[`collections.abc.Iterable`]
+afirma:
+
+[quote]
+____
+A única maneira confiável de determinar se um objeto é iterável é chamar `iter(obj)`.
+____
+
+****
+
+Após vermos algumas das ABCs existentes, vamos praticar tipagem ganso
+implementando uma ABC do zero, e a colocando em uso. O objetivo aqui não é
+encorajar todo mundo a criar ABCs a torto e a direito, mas mostrar como ler o
+código-fonte das ABCs encontradas na biblioteca padrão e em outros
+pacotes.((("", startref="GTstlib13")))((("", startref="ABCstndlib13")))
+
+<<<
+[[defining_using_abc_sec]]
+==== Definindo e usando uma ABC
+
+Escrevi esta((("goose typing", "defining and using ABCs", id="GTdef13")))((("ABCs (abstract base classes)",
+"defining and using ABCs", id="ABCdef13")))
+advertência no capítulo "Interfaces" da primeira edição deste livro:
+
+[quote]
+____
+
+ABCs, como os descritores e as metaclasses, são ferramentas para criar
+frameworks. Assim, só uma pequena minoria dos desenvolvedores Python tem
+a oportunidade de criar ABCs sem impor limitações pouco razoáveis e
+trabalho desnecessário a seus colegas programadores.
+____
+
+Agora ABCs têm mais casos de uso potenciais, em dicas de tipo para permitir
+tipagem estática. Como discutido na https://fpy.li/8r[«Seção 8.5.7»] (vol.1), usar ABCs em vez de
+tipos concretos em dicas de tipos de argumentos de função dá mais flexibilidade a
+quem chama a função.
+
+Para justificar a criação de uma ABC, precisamos pensar em um contexto para
+usá-la como um ponto de extensão em um framework. Então aqui está nosso
+contexto: imagine que você precisa exibir publicidade em um site ou em uma app
+de celular, em ordem aleatória, mas sem repetir um anúncio antes que o
+inventário completo de anúncios tenha sido exibido. Agora vamos presumir que
+estamos desenvolvendo um gerenciador de publicidade. Um dos
+requisitos é permitir o uso de classes de escolha aleatória não repetida
+fornecidas pelo usuário.footnote:[Talvez o cliente precise auditar o
+randomizador ou a agência queira fornecer um randomizador "viciado". Nunca se
+sabe...] Para deixar claro aos usuários do framework de anúncios o que se espera
+de um componente de "escolha aleatória não repetida", vamos definir uma ABC.
+
+Na bibliografia sobre estruturas de dados, _stack_ (pilha) e _queue_ (fila) descrevem
+interfaces abstratas em termos dos arranjos físicos dos objetos. Vamos seguir o
+mesmo caminho e usar uma metáfora do mundo real para batizar nossa ABC: gaiolas
+de bingo e sorteadores de loteria são máquinas projetadas para escolher
+aleatoriamente itens de um conjunto finito, sem repetir, até o conjunto ser
+esgotado. Vamos chamar a ABC de `Tombola`, seguindo o nome italiano do bingo, e
+do recipiente giratório que mistura os números.
+
+<<<
+A ABC `Tombola` tem quatro métodos.
+Os dois métodos abstratos são:
+
+`.load(…)`:: Coloca itens na coleção.
+`.pick()`:: Remove e devolve um item aleatório da coleção.
+
+Os métodos concretos são:
+
+`.loaded()`:: Devolve `True` se existir pelo menos um item na coleção.
+`.inspect()`:: Devolve uma `tuple` construída a partir dos itens atualmente na coleção,
+sem modificar o conteúdo (a ordem interna não é preservada).
+
+A <> mostra a ABC `Tombola` e três implementações concretas.
+Vale notar que _registered_ (registrada) e _virtual subclass_ (subclasse virtual)
+não são termos da UML padrão, mas representam uma relação de classe específica de Python,
+como veremos na <>.
+
+[role="width-80"]
+[[tombola_uml]]
+.Diagrama UML para uma ABC e três subclasses. O nome da ABC `Tombola` e de seus métodos abstratos estão escritos em _itálico_, segundo as convenções da UML. A seta tracejada indica que `TomboList` implementa a interface `Tombola`, e também está registrada como _subclasse virtual_ daquela ABC.
+image::../images/flpy_1305.png[align="center",pdfwidth=9cm]
+
+<<<
+O <> mostra a definição da ABC `Tombola`.
+
+[[ex_tombola_abc]]
+.tombola.py: ABC com dois métodos abstratos e dois métodos concretos.
+====
+[source, python]
+----
+include::../code/13-protocol-abc/tombola.py[tags=TOMBOLA_ABC]
+----
+====
+<1> Para definir uma ABC, crie uma subclasse de `abc.ABC`.
+
+<2> Um método abstrato é marcado com o decorador `@abstractmethod`, e muitas
+vezes seu corpo é vazio, exceto por uma docstring.footnote:[Antes das ABCs
+existirem, métodos abstratos levantariam um `NotImplementedError` para sinalizar
+que as subclasses eram responsáveis por suas implementações. No Smalltalk-80, o
+corpo dos métodos abstratos invocaria `subclassResponsibility`, um método
+herdado de `object` que gerava um erro com a mensagem "Minha subclasse deveria
+ter sobrescrito uma de minhas mensagens."]
+
+<3> A docstring instrui os implementadores a levantarem `LookupError` se não
+existirem itens para escolher.
+
+<4> Uma ABC pode incluir métodos concretos.
+
+<5> Métodos concretos em uma ABC devem depender apenas da interface definida
+pela ABC (isto é, outros métodos concretos ou abstratos ou propriedades da ABC).
+
+<6> Não sabemos como as subclasses concretas vão armazenar os itens, mas podemos
+escrever o resultado de `inspect` esvaziando a `Tombola` com chamadas sucessivas
+a `.pick()`...
+
+<7> ...e então usando `.load(…)` para colocar tudo de volta.
+
+
+[role="pagebreak-before less_space"]
+[TIP]
+====
+Um método abstrato pode ter uma implementação.
+Mas mesmo que tenha, as subclasses ainda são obrigadas a sobrescrevê-lo,
+mas poderão invocar o método abstrato com `super()`,
+acrescentando funcionalidade em vez de implementar do zero.
+Veja os detalhes do uso de `@abstractmethod` na
+https://fpy.li/6r[documentação do módulo `abc`].
+====
+
+O código do método `.inspect()` é ridículo mas funciona.
+Ele serve para mostrar que podemos usar `.pick()` e `.load(…)`
+para inspecionar o que está dentro de `Tombola`, puxando
+e devolvendo os itens—sem saber como eles são realmente armazenados. O
+objetivo deste exemplo é ressaltar que não há problema em oferecer métodos
+concretos em ABCs, desde que eles dependam apenas de outros métodos na
+interface. Conhecendo suas estruturas de dados internas, as subclasses concretas
+de `Tombola` podem sobrescrever `.inspect()` com uma implementação mais
+eficiente, mas não são obrigadas a fazer isso.
+
+O método `.loaded()` no <> tem uma linha, mas é custoso:
+ele chama `.inspect()` para criar a `tuple` apenas para aplicar `bool()` nela.
+Funciona, mas subclasses concretas podem fazer bem melhor, como veremos.
+
+Observe que nossa implementação tortuosa de `.inspect()` exige a captura de um
+`LookupError` lançado por `self.pick()`. O fato de `self.pick()` poder disparar
+um `LookupError` também é parte de sua interface, mas não há como tornar isso
+explícito em Python, exceto na documentação (veja a docstring para o método
+abstrato `pick` no <>).
+
+<<<
+Escolhi a exceção `LookupError` por sua posição na hierarquia de exceções em
+relação a `IndexError` e `KeyError`, as exceções mais comuns de ocorrerem nas
+estruturas de dados usadas para implementar uma `Tombola` concreta. Dessa forma,
+as implementações podem lançar `LookupError`, `IndexError`, `KeyError`, ou uma
+subclasse customizada de `LookupError` para atender à interface. Veja o
+<>.
+
+
+[[exc_tree_part]]
+.Parte da hierarquia de classes de exceção.
+====
+----
+BaseException
+ ├── GeneratorExit
+ ├── KeyboardInterrupt
+ ├── SystemExit
+ └── Exception
+      ├── ArithmeticError
+      │    ├── FloatingPointError
+      │    ├── OverflowError
+      │    └── ZeroDivisionError
+      ├── AssertionError
+      ├── AttributeError
+      ├── BufferError
+      ├── EOFError
+      ├── ImportError
+      ├── LookupError       <1>
+      │    ├── IndexError  <2>
+      │    └── KeyError    <3>
+      ├── MemoryError
+      ... etc.
+----
+====
+<1> `LookupError` é a exceção que tratamos em `Tombola.inspect`.
+<2> `IndexError` é a subclasse de `LookupError` gerada quando tentamos
+acessar um item em uma sequência com um índice além da última posição.
+<3> `KeyError` ocorre quando usamos uma chave inexistente para acessar um item em um
+mapeamento (`dict` etc.).
+
+Agora temos nossa própria ABC `Tombola`. Para observar a checagem da interface
+feita por uma ABC, vamos tentar enganar `Tombola` com uma implementação
+defeituosa no <>.
+
+[[fake_tombola_ex]]
+.Uma `Tombola` falsa não passa desapercebida
+====
+[source, python]
+----
+>>> from tombola import Tombola
+>>> class Fake(Tombola):  # <1>
+...     def pick(self):
+...         return 13
+...
+>>> Fake  # <2>
+
+>>> f = Fake()  # <3>
+Traceback (most recent call last):
+  File "", line 1, in 
+TypeError: Can't instantiate abstract class Fake with abstract method load
+----
+====
+<1> Declara `Fake` como subclasse de `Tombola`.
+<2> A classe é criada, nenhum erro até agora.
+<3> Um `TypeError` é sinalizado quando tentamos instanciar `Fake`.
+A mensagem é bastante clara: `Fake` é considerada abstrata porque deixou de implementar `load`, um dos métodos abstratos declarados na ABC `Tombola`.
+
+Então definimos nossa primeira ABC, e a usamos para validar uma classe.
+Logo vamos criar uma subclasse de `Tombola`, mas primeiro temos que falar sobre algumas regras para a programação de ABCs.((("", startref="ABCdef13")))((("", startref="GTdef13")))
+
+[[abc_syntax_section]]
+==== Detalhes da Sintaxe das ABCs
+
+A((("goose typing", "ABC syntax details")))((("ABCs (abstract base classes)", "ABC syntax details"))) forma padrão de declarar uma ABC é criar uma subclasse de `abc.ABC` ou de alguma outra ABC.
+
+Além da classe base ABC e do decorador `@abstractmethod`, o módulo `abc` define
+os decoradores `@abstractclassmethod`, `@abstractstaticmethod`, e `@abstractproperty`.
+Entretanto, os três últimos foram descontinuados no Python 3.3,
+quando se tornou possível empilhar decoradores sobre `@abstractmethod`, tornando os outros redundantes.
+
+<<<
+Por exemplo, a maneira preferível de declarar um método de classe abstrato é:
+
+[source, python]
+----
+class MyABC(abc.ABC):
+    @classmethod
+    @abc.abstractmethod
+    def an_abstract_classmethod(cls, ...):
+        pass
+----
+
+[WARNING]
+====
+A ordem dos decoradores de função empilhados importa, e no caso de `@abstractmethod`, a documentação é explícita:
+
+[quote]
+____
+Quando `@abstractmethod` é aplicado em combinação com outros descritores de
+método, ele deve ser aplicado como o decorador mais interno...footnote:[O
+verbete https://fpy.li/6s[`@abc.abstractmethod`] na
+https://docs.python.org/pt-br/dev/library/abc.html[documentação do módulo
+`abc`].]
+____
+
+Em outras palavras, nenhum outro decorador pode aparecer entre `@abstractmethod` e a instrução `def`.
+====
+
+Agora que abordamos essas questões de sintaxe das ABCs, vamos colocar `Tombola`
+em uso, implementando duas subclasses concretas.
+
+==== Criando uma subclasse de `Tombola`
+
+Dada((("goose typing", "subclassing ABCs", id="GTsubclass13")))((("ABCs (abstract base classes)",
+"subclassing ABCs", id="ABCsubclass13")))((("inheritance and subclassing", "subclassing ABCs",
+id="IASsubclass13"))) a ABC `Tombola`, vamos desenvolver duas subclasses
+concretas que satisfazem a interface. Essas classes estão ilustradas na
+<>, junto com a subclasse virtual que será discutida na seção
+seguinte.
+
+A classe `BingoCage` no <> é uma variação do
+https://fpy.li/8y[«Exemplo 8 do Capítulo 7»] (vol.1) usando um randomizador melhor. `BingoCage` implementa os
+métodos abstratos obrigatórios `load` e `pick`.
+
+[[ex_tombola_bingo]]
+.bingo.py: `BingoCage` é uma subclasse concreta de `Tombola`
+====
+[source, python]
+----
+include::../code/13-protocol-abc/bingo.py[tags=TOMBOLA_BINGO]
+----
+====
+<1> Essa classe `BingoCage` estende `Tombola` explicitamente.
+
+<2> `random.SystemRandom()`
+implementa a API `random` sobre a função `os.urandom()`, que fornece bytes
+aleatórios "adequados para uso em criptografia", segundo https://fpy.li/6t[a
+documentação do módulo `os`].
+
+<3> Delega o carregamento inicial para o método `.load()`
+
+<4> Em vez da função `random.shuffle()` normal, usamos o método `.shuffle()` de
+nossa instância de `SystemRandom`.
+
+<5> `pick` é implementado como no https://fpy.li/8y[«Exemplo 8 do Capítulo 7»] (vol.1).
+
+<6> `+__call__+` também é do https://fpy.li/8y[«Exemplo 8 do Capítulo 7»] (vol.1). Ele não é necessário para
+satisfazer a interface de `Tombola`, mas é comum que subclasses tenham mais
+métodos.
+
+`BingoCage` herda o custoso método `loaded` e o tolo `inspect` de `Tombola`.
+Ambos poderiam ser sobrescritos com métodos de uma linha mais rápidos, como no
+<>. A questão é: podemos decidir apenas herdar os
+métodos concretos de uma ABC. Os métodos herdados de `Tombola`
+não são tão rápidos quanto poderiam ser na `BingoCage` concreta,
+mas fornecem os resultados esperados para qualquer subclasse de
+`Tombola` que implemente `pick` e `load` corretamente.
+
+O <> mostra uma implementação muito diferente, mas também válida,
+da interface de `Tombola`. Em vez de misturar as "bolas" e tirar a última,
+`LottoBlower` tira um item de uma posição aleatória..
+
+[[ex_lotto]]
+.lotto.py: `LottoBlower` é uma subclasse concreta que sobrecarrega os métodos `inspect` e `loaded` de `Tombola`
+====
+[source, python]
+----
+include::../code/13-protocol-abc/lotto.py[tags=LOTTERY_BLOWER]
+----
+====
+
+<1> O construtor aceita qualquer iterável: o argumento é usado para construir
+uma lista.
+<2> A função `random.randrange(…)` levanta um `ValueError` se a faixa de valores
+estiver vazia, então capturamos esse erro e trocamos por `LookupError`, para ser
+compatível com `Tombola`.
+<3> Caso contrário, o item selecionado aleatoriamente é retirado de
+`self._balls`.
+<4> Sobrescreve `loaded` para evitar a chamada a `inspect` (como `Tombola.loaded`
+faz no <>). Podemos fazer isso mais rápido acessando
+`self._balls` diretamente—não precisamos criar uma nova tupla.
+<5> Sobrescreve `inspect` com uma linha de código.
+
+O <> ilustra um idioma que vale a pena mencionar:
+em `+__init__+`, `self._balls` armazena `list(iterable)`, e não apenas uma
+referência para `iterable` (isto é, nós não apenas atribuímos
+`self._balls = iterable`, apelidando o argumento).
+Como mencionado na <>, isso torna a `LottoBlower`
+flexível, pois o argumento `iterable` pode ser de qualquer tipo iterável.
+Ao mesmo tempo, garantimos que os itens serão armazenados em uma `list`,
+de onde podemos retirar itens com `.pop()`.
+E mesmo quando recebemos uma lista no argumento `iterable`,
+`list(iterable)` produz uma cópia, o que é uma boa prática,
+considerando que vamos remover itens da lista,
+e o cliente pode não estar esperando
+que a lista passada seja modificada.footnote:[A https://fpy.li/8z[«Seção 6.5.2»] (vol.1)
+trata do problema de apelidamento que acabamos de evitar aqui.]
+
+Chegamos agora à característica dinâmica da tipagem ganso:
+declarar subclasses virtuais com o método `register`.
+((("", startref="GTsubclass13")))((("", startref="ABCsubclass13")))((("", startref="IASsubclass13")))
+
+[[virtual_subclass_sec]]
+==== Uma subclasse virtual de uma ABC
+
+Uma((("goose typing", "virtual subclasses of ABCs", id="GTvsub13")))((("ABCs
+(abstract base classes)", "virtual subclasses of ABCs",
+id="ABCvirt13")))((("virtual subclasses", id="virtsub13")))((("inheritance and subclassing",
+"virtual subclasses of ABCs", id="IASvirtualabc13")))
+característica essencial da tipagem ganso—e uma razão pela qual ela merece um
+nome de ave aquática—é a habilidade de registrar uma classe como uma _subclasse
+virtual_ de uma ABC, mesmo se a classe não herde da ABC. Ao fazer isso,
+prometemos que a classe implementa fielmente a interface definida na ABC—e
+Python vai acreditar em nós sem checar. Se mentirmos, vamos enfrentar
+exceções de tempo de execução.
+
+Isso é feito invocando um método de classe `register` da ABC.
+A subclasse registrada será reconhecida por `issubclass`,
+mas herdará qualquer método ou atributo da ABC.
+
+[WARNING]
+====
+Subclasses virtuais não herdam da ABC na qual se registram,
+e sua conformidade com a interface da ABC nunca é checada,
+nem quando são instanciadas.
+E mais, neste momento checadores de tipos estáticos não conseguem tratar subclasses virtuais.
+Mais detalhes em https://fpy.li/13-22[Mypy issue 2922—ABCMeta.register support].
+====
+
+O método `register` é normalmente invocado como uma função comum
+(veja a <>), mas também pode ser usado como decorador.
+No ((("UML class diagrams", "TomboList"))) <>,
+usamos a sintaxe de decorador e implementamos `TomboList`,
+uma subclasse virtual de `Tombola`, ilustrada na <>.
+
+[role="width-50"]
+[[tombolist_uml]]
+.Diagrama de classe UML para `TomboList`, subclasse real de `list` e subclasse virtual de `Tombola`.
+image::../images/flpy_1307.png[align="center",pdfwidth=5.5cm]
+
+[[ex_tombolist]]
+.tombolist.py: a classe `TomboList` é uma subclasse virtual de `Tombola`
+====
+[source, python]
+----
+include::../code/13-protocol-abc/tombolist.py[]
+----
+====
+<1> `TomboList` é registrada como subclasse virtual de `Tombola`.
+
+<2> `TomboList` estende `list`.
+
+<3> `TomboList` herda seu comportamento booleano de `list`, devolvendo
+`True` se a lista não estiver vazia.
+
+<4> Nosso `pick` invoca `self.pop`, herdado de `list`, passando um índice
+aleatório para um item.
+
+<5> `TomboList.load` é o mesmo que `list.extend`.
+
+<6> `loaded` delega para `bool`.footnote:[O truque usado com `load()` não
+funciona com `loaded()`, pois o tipo `list` não implementa `+__bool__+`, o
+método que eu teria de vincular a `loaded`. O `bool()` nativo não precisa de
+`+__bool__+` para funcionar, porque pode também usar `+__len__+`. Veja
+https://fpy.li/2g[4.1. Teste do Valor Verdade] na documentação de Python.]
+
+<7> É sempre possível invocar `register` dessa forma, e é útil fazer assim
+quando você precisa registrar uma classe cujo código você não mantém,
+mas que implementa a interface.
+
+Note que, por causa do registro, as funções `issubclass` e `isinstance` agem
+como se `TomboList` fosse uma subclasse de `Tombola`:
+
+[source, python]
+----
+>>> from tombola import Tombola
+>>> from tombolist import TomboList
+>>> issubclass(TomboList, Tombola)
+True
+>>> t = TomboList(range(100))
+>>> isinstance(t, Tombola)
+True
+----
+
+Entretanto, a herança é guiada por um atributo de classe especial chamado
+`+__mro__+`—sigla de _Method Resolution Order_
+(Ordem de Resolução de Métodos). Esse atributo lista a
+classe e suas superclasses na ordem que Python segue para procurar
+métodos.footnote:[Há toda uma explicação
+sobre o atributo de classe `+__mro__+` na <>. Por agora, essas
+informações básicas são o suficiente.] Se você inspecionar o `+__mro__+` de
+`TomboList`, verá que ele lista apenas as superclasses "reais"—`list` e
+`object`:
+
+[source, python]
+----
+>>> TomboList.__mro__
+(, , )
+----
+
+`Tombola` não está em `+TomboList.__mro__+`, então `TomboList` não herda nenhum método de `Tombola`.
+
+Isso conclui nosso estudo de caso da ABC `Tombola`.
+Na próxima seção, vamos falar sobre como a função `register` das ABCs é usada na vida real.((("", startref="GTvsub13")))((("", startref="ABCvirt13")))((("", startref="virtsub13")))((("", startref="IASvirtualabc13")))
+
+[[register_usage]]
+==== O uso de `register` na prática
+
+No <>, usamos((("goose typing", "usage of register")))((("ABCs (abstract base classes)",
+"usage of register"))) `Tombola.register` como um
+decorador de classe. Antes de Python 3.3, `register` não podia ser usado dessa
+forma—ele tinha que ser invocado como uma função normal após a definição da
+classe, como sugerido pelo comentário no final do <>. Mas `register`
+continua sendo usado como uma função para registrar classes definidas em
+outro lugar. Por exemplo, no https://fpy.li/13-24[código-fonte] do módulo
+`collections.abc`, os tipos nativos `tuple`, `str`, `range`, e `memoryview` são
+registrados como subclasses virtuais de `Sequence` assim:
+
+[source, python]
+----
+Sequence.register(tuple)
+Sequence.register(str)
+Sequence.register(range)
+Sequence.register(memoryview)
+----
+
+Vários outros tipo nativos estão registrados com as ABCs em __collections_abc.py_.
+Esses registros ocorrem apenas quando aquele módulo é importado,
+o que não causa problema, pois você terá mesmo que importar o módulo para obter as ABCs.
+Por exemplo, você precisa importar `MutableMapping` de `collections.abc` para checar algo como `isinstance(my_dict, MutableMapping)`.
+
+Criar uma subclasse de uma ABC ou se registrar com uma ABC são duas maneiras explícitas de fazer nossas classes passarem checagens com `issubclass` e `isinstance` (que também se apoia em `issubclass`).
+Mas algumas ABCs também suportam tipagem estrutural.
+A próxima seção explica isso.
+
+[[subclasshook_sec]]
+==== Tipagem estrutural com ABCs
+
+As ABCs((("goose typing", "structural typing with ABCs", id="GTstruct13")))((("ABCs (abstract base classes)",
+"structural typing with", id="ABCstruct13")))((("structural typing", id="strtype13")))
+são usadas principalmente com tipagem nominal.
+
+Quando uma classe `Sub` herda explicitamente de `UmaABC`, ou está registrada com
+`UmaABC`, o nome de `UmaABC` fica ligado ao da classe `Sub`—é assim que, durante
+a execução, `issubclass(UmaABC, Sub)` devolve `True`.
+
+Em contraste, a tipagem estrutural diz respeito a olhar para a estrutura da
+interface pública de um objeto para determinar seu tipo: um objeto é
+_consistente-com_ um tipo se implementa os métodos definidos no tipo.footnote:[O
+conceito de consistência de tipo é explicado na https://fpy.li/83[«Seção 8.5.1.1»] (vol.1).] A
+tipagem pato estática e a tipagem pato dinâmica são duas abordagens à tipagem
+estrutural.
+
+Acontece que algumas ABCs também suportam tipagem estrutural.
+Em seu ensaio _<>_, Alex mostra que uma classe pode ser
+reconhecida como subclasse de uma ABC mesmo sem registro. Aqui está novamente o
+exemplo dele, com um teste adicional usando `issubclass`:
+
+[source, python]
+----
+>>> class Struggle:
+...     def __len__(self): return 23
+...
+>>> from collections import abc
+>>> isinstance(Struggle(), abc.Sized)
+True
+>>> issubclass(Struggle, abc.Sized)
+True
+----
+
+A classe `Struggle` é considerada uma subclasse de `abc.Sized` pela função
+`issubclass` (e, consequentemente, também por `isinstance`) porque `abc.Sized`
+implementa um método de classe especial chamado `+__subclasshook__+`.
+
+O `+__subclasshook__+` de `Sized` verifica se o argumento classe tem um atributo
+chamado `+__len__+`. Se tiver, então a classe é considerada uma subclasse
+virtual de `Sized`. Veja o <>.
+
+<<<
+[[sized_source_code]]
+.Definição de `Sized` em https://fpy.li/13-25[Lib/_collections_abc.py]
+====
+[source, python]
+----
+class Sized(metaclass=ABCMeta):
+
+    __slots__ = ()
+
+    @abstractmethod
+    def __len__(self):
+        return 0
+
+    @classmethod
+    def __subclasshook__(cls, C):
+        if cls is Sized:
+            if any("__len__" in B.__dict__ for B in C.__mro__):  # <1>
+                return True  # <2>
+        return NotImplemented  # <3>
+----
+====
+<1> Se há um atributo chamado `+__len__+` no `+__dict__+` de qualquer classe
+listada em `+C.__mro__+` (isto é, `C` e suas superclasses)...
+<2> ...devolve `True`, sinalizando que `C` é uma subclasse virtual de `Sized`.
+<3> Caso contrário devolve `NotImplemented`, para permitir que a checagem de subclasse continue.
+
+[NOTE]
+====
+Se você tiver interesse nos detalhes da checagem de subclasse,
+estude o código-fonte do método `+ABCMeta.__subclasscheck__+` no Python 3.6:
+https://fpy.li/13-26[_Lib/abc.py_].
+Saiba que é complicado: lá há muitos ifs e duas chamadas recursivas.
+No Python 3.7, Ivan Levkivskyi e Inada Naoki reescreveram em C
+a maior parte da lógica do módulo `abc`, para melhorar o desempenho.
+Veja https://fpy.li/13-27[Python issue #31333].
+A implementação atual de `+ABCMeta.__subclasscheck__+` simplesmente chama `_abc_subclasscheck`.
+O código-fonte em C relevante está em https://fpy.li/13-28[_cpython/Modules/_abc.c#L605_].
+====
+
+É assim que `+__subclasshook__+` permite às ABCs suportarem a tipagem
+estrutural. Você pode formalizar uma interface com uma ABC, pode fazer checagens
+`isinstance` com aquela ABC, e ainda ter uma classe sem qualquer relação de
+herança aprovada por uma checagem de `issubclass` porque ela implementa um certo
+método (ou porque ela faz o necessário para convencer o
+`+__subclasshook__+` da ABC aprová-la como subclasse virtual).
+
+É uma boa ideia implementar `+__subclasshook__+` em nossas próprias ABCs?
+Provavelmente não. Todas as implementações de `+__subclasshook__+` que vi no
+código-fonte de Python estão em ABCs como `Sized`, que declara apenas um método
+especial, e elas simplesmente verificam a presença do nome daquele método
+especial. Dado seu status "especial", é quase certeza que qualquer método
+chamado `+__len__+` faz o que se espera. Mas mesmo no reino dos métodos
+especiais e ABCs fundamentais, pode ser arriscado fazer tais suposições. Por
+exemplo, mapeamentos implementam `+__len__+`, `+__getitem__+`, e `+__iter__+`,
+mas corretamente não são considerados subtipos de `Sequence`, pois não podemos
+recuperar itens usando índices a partir de zero ou obter fatias. Por isso a classe
+https://fpy.li/13-29[`abc.Sequence`] não implementa `+__subclasshook__+`.
+
+Para ABCs que você ou eu podemos escrever, um `+__subclasshook__+` seria ainda
+menos confiável. Não estou preparado para acreditar que qualquer classe chamada
+`Spam` que implemente ou herde `load`, `pick`, `inspect`, e `loaded` vai
+necessariamente se comportar como uma `Tombola`. É melhor deixar o programador
+afirmar isso, fazendo de `Spam` uma subclasse de `Tombola`, ou registrando a
+classe com `Tombola.register(Spam)`. Claro, o seu `+__subclasshook__+` poderia
+também verificar assinaturas de métodos e outras características, mas não creio
+que valha o esforço.((("", startref="GTstruct13")))((("",
+startref="ABCstruct13")))((("", startref="strtype13")))
+
+
+[[static_protocols_sec]]
+=== Protocolos estáticos
+
+[NOTE]
+====
+
+Vimos algo sobre protocolos estáticos((("protocols", "static protocols",
+id="Pstatic13"))) na https://fpy.li/8m[«Seção 8.5.10»] (vol.1). Pensei em deixar toda a discussão
+sobre protocolos para este capítulo, mas decidi que a apresentação inicial de
+dicas de tipo em funções precisava incluir protocolos, pois a tipagem pato é uma
+parte essencial de Python, e a checagem de tipos estática sem protocolos não
+consegue lidar muito bem com muitas APIs pythônicas.
+
+====
+
+Vamos encerrar este capítulo ilustrando os protocolos estáticos com dois
+exemplos simples, e uma discussão sobre as ABCs numéricas e protocolos.
+Começaremos mostrando como um protocolo estático possibilita anotar e checar
+tipos na função `double()`, que vimos antes na https://fpy.li/8s[«Seção 8.4»] (vol.1).
+
+[[typed_double_sec]]
+==== A função double tipada
+
+Quando((("static protocols", "typed double function",
+id="SPtypeddouble13")))((("typed double function", id="typdblf13")))((("double()
+function", id="double13")))((("functions", "double() function"))) apresento
+Python para programadores mais habituados à tipagem estática, um de meus
+exemplos é esta função `double` capaz de lidar com uma variedade de tipos:
+
+[source, python]
+----
+>>> def double(x):
+...     return x * 2
+...
+>>> double(1.5)
+3.0
+>>> double('A')
+'AA'
+>>> double([10, 20, 30])
+[10, 20, 30, 10, 20, 30]
+>>> from fractions import Fraction
+>>> double(Fraction(2, 5))
+Fraction(4, 5)
+----
+
+Antes da introdução dos protocolos estáticos, não havia uma forma prática de
+acrescentar dicas de tipo a `double` sem limitar seus usos
+possíveis.footnote:[Concordo que `double()` não é muito útil, exceto como um exemplo.
+Mas a biblioteca padrão de Python tem muitas funções que não poderiam ser
+anotadas de modo apropriado antes dos protocolos estáticos serem adicionados, no
+Python 3.8. Ajudei a corrigir alguns bugs no _typeshed_ acrescentando dicas de
+tipo com protocolos. Por exemplo, no _pull request_ onde consertei
+https://fpy.li/shed4051[_Should Mypy warn about potential invalid arguments to `+max+`?_]
+(Deveria o Mypy avisar sobre argumentos potencialmente inválidos passados a `max`?)
+defini um protocolo `_SupportsLessThan`, que usei para melhorar
+as anotações de `max`, `min`, `sorted`, e `list.sort`.]
+
+
+Graças à tipagem pato, `double` funciona mesmo com tipos inventados depois, tal
+como a classe `Vector` aprimorada que veremos na <>:
+
+[source, python]
+----
+>>> from vector_v7 import Vector
+>>> double(Vector([11.0, 12.0, 13.0]))
+Vector([22.0, 24.0, 26.0])
+----
+
+A implementação inicial de dicas de tipo no Python era um sistema de tipos
+nominal: o nome de um tipo em uma anotação tinha que corresponder ao nome do
+tipo do argumento real—ou com o nome de uma de suas superclasses. Como é
+impossível nomear todos os tipos que implementam um protocolo (suportando as
+operações requeridas), a tipagem pato não podia ser descrita por dicas de tipo
+antes do Python 3.8.
+
+Agora, com `typing.Protocol`, podemos informar ao Mypy que `double` recebe um
+argumento `x` que suporta `x * 2`.
+O <> mostra como.
+
+[[repeatable_protocol_ex]]
+._double_protocol.py_: a definição de `double` usando um `Protocol`.
+====
+[source, python]
+----
+include::../code/13-protocol-abc/double/double_protocol.py[]
+----
+====
+<1> Vamos usar esse `T` na assinatura de `+__mul__+`.
+<2> `+__mul__+` é a essência do protocolo `Repeatable`.
+O parâmetro `self` normalmente não é anotado—presume-se que seu tipo seja a classe.
+Aqui usamos `T` para assegurar que o tipo do resultado é o mesmo tipo de `self`.
+Além disso observe que decidi limitar `repeat_count` ao tipo `int` neste protocolo.
+<3> A variável de tipo `RT` é vinculada pelo protocolo `Repeatable`:
+o checador de tipos vai exigir que o tipo efetivo implemente `Repeatable`.
+<4> Agora o checador de tipos pode checar que o parâmetro `x` é um objeto que
+pode ser multiplicado por um inteiro, e que o valor retornado tem o mesmo tipo
+que `x`.
+
+Este exemplo mostra por que o subtítulo da https://fpy.li/pep544[PEP 544] é
+_static duck typing_ (tipagem pato estática). O tipo nominal do argumento
+concreto `x` passado a `double`, é irrelevante, desde que grasne—ou seja,
+desde que implemente `+__mul__+`.((("", startref="SPtypeddouble13")))((("",
+startref="typdblf13")))((("", startref="double13")))
+
+
+[[runtime_checkable_proto_sec]]
+==== Protocolos estáticos checados durante a execução
+
+No ((("static protocols", "runtime checkable", id="SPruntime13"))) Mapa de
+Tipagem (<>), `typing.Protocol` aparece na área de
+checagem estática—a metade inferior do diagrama. Entretanto, ao definir uma
+subclasse de `typing.Protocol`, você pode usar o decorador `@runtime_checkable`
+para fazer aquele protocolo aceitar checagens com `isinstance/issubclass`
+durante a execução. Isso funciona porque `typing.Protocol` é uma ABC, assim
+suporta o `+__subclasshook__+` que vimos na <>.
+
+No Python 3.9, o módulo `typing` inclui sete protocolos prontos para uso que são
+verificáveis durante a execução. Aqui estão dois deles, citados diretamente da
+https://fpy.li/gv[documentação de `typing`]:
+
+`class typing.SupportsComplex`::
+    Um ABC com um método abstrato __complex__.
+
+`class typing.SupportsFloat`::
+    Um ABC com um método abstrato __float__.
+
+Estes((("numeric types", "checking for convertibility"))) protocolos foram
+projetados para checar a "convertibilidade" de tipos numéricos: se um objeto `n`
+implementa `+__complex__+`, então deveria ser possível obter um `complex`
+invocando `complex(n)`, pois o método especial `+__complex__+` existe para
+suportar a função embutida `complex()`.
+
+<> mostra o
+https://fpy.li/13-31[código-fonte]
+do protocolo `typing.SupportsComplex`.
+
+[[supportscomplex_ex]]
+.código-fonte do protocolo `typing.SupportsComplex`
+====
+[source, python]
+----
+@runtime_checkable
+class SupportsComplex(Protocol):
+    """An ABC with one abstract method __complex__."""
+    __slots__ = ()
+
+    @abstractmethod
+    def __complex__(self) -> complex:
+        pass
+----
+====
+
+A chave é o método abstrato `+__complex__+`.footnote:[O atributo `+__slots__+` é
+irrelevante para nossa discussão aqui—é uma otimização sobre a qual falamos na
+<>.] Durante a checagem de tipo estática, um objeto será considerado
+_consistente-com_ o protocolo `SupportsComplex` se implementar um método
+`+__complex__+` que recebe apenas `self` e retorna um `complex`.
+
+Graças ao decorador de classe `@runtime_checkable`, aplicado a
+`SupportsComplex`, aquele protocolo também pode ser utilizado em checagens com
+`isinstance` no <>.
+
+[[repeatable_protocol_demo_ex]]
+.Usando `SupportsComplex` durante a execução
+====
+[source, python]
+----
+>>> from typing import SupportsComplex
+>>> import numpy as np
+>>> c64 = np.complex64(3+4j)  # <1>
+>>> isinstance(c64, complex)   # <2>
+False
+>>> isinstance(c64, SupportsComplex)  # <3>
+True
+>>> c = complex(c64)  # <4>
+>>> c
+(3+4j)
+>>> isinstance(c, SupportsComplex) # <5>
+False
+>>> complex(c)
+(3+4j)
+----
+====
+<1> `complex64` é um dos cinco tipos de números complexos fornecidos pelo NumPy.
+<2> Nenhum dos tipos complexos da NumPy é subclasse do `complex` embutido.
+<3> Mas os tipos complexos de NumPy implementam `+__complex__+`, então cumprem o protocolo `SupportsComplex`.
+<4> Portanto, você pode criar objetos `complex` a partir deles.
+<5> O tipo `complex` embutido não implementa `+__complex__+`,
+mas `complex(c)` funciona sem problemas se `c` for uma instância de
+`complex`.
+
+Como consequência deste último ponto, se você quiser testar se um objeto `c` é
+um `complex` ou `SupportsComplex`, você deve passar uma tupla de tipos como
+segundo argumento para `isinstance`, assim:
+
+[source, python]
+----
+isinstance(c, (complex, SupportsComplex))
+----
+
+Uma outra alternativa seria usar a ABC `Complex`, definida no módulo `numbers`.
+O tipo embutido `complex` e os tipos `complex64` e `complex128` da NumPy são
+todos registrados como subclasses virtuais de `numbers.Complex`, então
+o código a seguir funciona:
+
+<<<
+
+[source, python]
+----
+>>> import numbers
+>>> isinstance(c, numbers.Complex)
+True
+>>> isinstance(c64, numbers.Complex)
+True
+----
+
+Na primeira edição de _Python Fluente_ eu recomendava o uso das ABCs de
+`numbers`, mas agora esse não é mais um bom conselho, pois aquelas ABCs não são
+reconhecidas pelos checadores de tipos estáticos, como veremos na
+<>.
+
+Nesta seção eu queria demonstrar que um protocolo verificável durante a execução
+funciona com `isinstance`, mas na verdade esse exemplo não é um caso de uso
+particularmente bom de `isinstance`, como a barra lateral
+<> explica.
+
+[TIP]
+====
+
+Se você estiver usando o Mypy, há uma vantagem nas checagens explícitas com
+`isinstance`: quando você escreve uma instrução `if` onde a condição é
+`isinstance(n, MyType)`, então o Mypy infere que dentro do bloco `if`, o tipo do
+objeto `n` é _consistente-com_ `MyType`.
+
+====
+
+[[duck_typing_friend_box]]
+.Confie no pato
+****
+
+Durante((("duck typing"))) a execução, muitas vezes a tipagem pato é a melhor
+abordagem para checagem de tipos: em vez de chamar `isinstance` ou `hasattr`,
+apenas tente realizar as operações que você precisa com o objeto, e trate as
+exceções conforme necessário. Segue um exemplo concreto.
+
+Continuando a discussão anterior,
+dado um objeto `n` que preciso usar como número complexo,
+essa seria uma abordagem:
+
+[source, python]
+----
+if isinstance(n, (complex, SupportsComplex)):
+    # código que precisa converter `n` para `complex`
+else:
+    raise TypeError('n must be convertible to complex')
+----
+
+<<<
+A abordagem da tipagem ganso seria usar a ABC `numbers.Complex`:
+
+[source, python]
+----
+if isinstance(n, numbers.Complex):
+    # código que assume que `n` é instância de `Complex`
+else:
+    raise TypeError('n must be an instance of Complex')
+----
+
+Mas eu prefiro aproveitar a tipagem pato e pedir perdão
+em vez de permissão (Princípio de Hopper):
+
+[source, python]
+----
+try:
+    c = complex(n)
+except TypeError as exc:
+    raise TypeError('n must be convertible to complex') from exc
+----
+
+Mas se o único tratamento que você vai dar para o `TypeError`
+é levantar `TypeError`, eu escreveria só isso:
+
+[source, python]
+----
+c = complex(n)
+----
+
+Neste último caso, se `n` não é de um tipo aceitável,
+o Python levantará uma exceção com uma mensagem bem clara.
+Por exemplo, se `n` é uma `tuple`, esse é o resultado:
+
+[source]
+----
+TypeError: complex() first argument must be a string or a number, not 'tuple'
+----
+
+Em português: "O primeiro argumento de `complex()` deve ser uma string ou um número, não 'tuple'".
+
+A abordagem da tipagem pato é simples e correta neste caso.
+****
+
+Agora que vimos como usar protocolos estáticos durante a execução com tipos
+pré-existentes como `complex` e `numpy.complex64`, precisamos discutir as
+limitações de protocolos verificáveis durante a execução.((("",
+startref="SPruntime13")))
+
+[[protocol_type_hints_ignored]]
+==== Limitações das checagens de protocolo durante a execução
+
+Vimos((("static protocols", "limitations of runtime protocol checks"))) que
+dicas de tipo são geralmente ignoradas durante a execução, e isso também afeta o
+uso de checagens com `isinstance` ou `issubclass` com protocolos estáticos.
+
+Por exemplo, qualquer classe com um método `+__float__+`
+é considerada—durante a execução—uma subclasse virtual de `SupportsFloat`,
+mesmo se seu método `+__float__+` não devolver um `float`.
+
+Veja essa sessão no console:
+
+[source, python]
+----
+>>> import sys
+>>> sys.version
+'3.9.5 (v3.9.5:0a7dcbdb13, May 3 2021, 13:17:02) \n[Clang 6.0 (clang-600.0.57)]'
+>>> c = 3+4j
+>>> c.__float__
+
+>>> c.__float__()
+Traceback (most recent call last):
+  File "", line 1, in 
+TypeError: can't convert complex to float
+----
+
+Em Python 3.9, o tipo `complex` tem um método `+__float__+`, mas ele existe
+apenas para gerar `TypeError` com uma mensagem de erro explícita. Se aquele
+método `+__float__+` tivesse anotações, o tipo de retorno seria `NoReturn`— que
+vimos na https://fpy.li/8f[«Seção 8.5.12»] (vol.1).
+
+Mas incluir dicas de tipo em `+complex.__float__+` no _typeshed_ não resolveria
+esse problema, porque o interpretador Python em geral ignora dicas de tipo—e
+também não acessa os arquivos de anotações de tipo do _typeshed_.
+
+Continuando da sessão anterior de Python 3.9:
+
+[source, python]
+----
+>>> from typing import SupportsFloat
+>>> c = 3+4j
+>>> isinstance(c, SupportsFloat)
+True
+>>> issubclass(complex, SupportsFloat)
+True
+----
+
+Então temos resultados enganosos: as checagens durante a execução usando
+`SupportsFloat` sugerem que você pode converter um `complex` para `float`, mas
+na verdade isso gera um erro de tipo.
+
+
+[WARNING]
+====
+
+O problema específico com o tipo `complex` foi resolvido no Python 3.10, com
+a remoção do método `+complex.__float__+`.
+
+Mas o problema geral persiste: checagens com `isinstance`/`issubclass` só olham
+para a presença ou ausência de métodos, sem checar sequer suas assinaturas,
+muito menos suas anotações de tipo. E isso não vai mudar tão cedo, porque este
+tipo de checagem de tipos durante a execução traria um custo de processamento
+inaceitável.footnote:[Agradeço a Ivan Levkivskyi, co-autor da
+https://fpy.li/pep544[«PEP 544 (sobre protocolos)»], por apontar que checagem de
+tipo não é apenas uma questão de checar se o tipo de `x` é `T`: é sobre
+determinar que o tipo de `x` é _consistente-com_ `T`, o que pode ser custoso.
+Não é de se espantar que o Mypy leve alguns segundos para fazer uma checagem
+de tipos, mesmo em scripts Python curtos.]
+
+====
+
+Agora vamos implementar um protocolo estático em uma classe concreta.
+
+[[support_typing_proto]]
+==== Suportando um protocolo estático
+
+Lembra((("static protocols", "supporting", id="SPsupport13"))) da classe
+`Vector2d`, que desenvolvemos no <>? Dado que um número
+`complex` e uma instância de `Vector2d` contém um par de números de
+ponto flutuante, faz sentido suportar a conversão de `Vector2d` para `complex`.
+
+O <> mostra a implementação do método `+__complex__+`,
+para melhorar a última versão de `Vector2d`, vista no <> do <>.
+Para deixar o serviço completo, podemos suportar a operação inversa, com um
+método de classe `fromcomplex`, que constrói um `Vector2d` a partir de um
+`complex`.
+
+[[ex_vector2d_complex_v4]]
+._vector2d_v4.py_: métodos para conversão de e para `complex`
+====
+[source, python]
+----
+include::../code/13-protocol-abc/typing/vector2d_v4.py[tags=VECTOR2D_V4_COMPLEX]
+----
+====
+
+<1> Presume que `n` tem atributos `.real` e `.imag`. Veremos uma
+implementação melhor no <>.
+
+Dado o código acima, e o método `+__abs__+` que o `Vector2d` já tinha em
+<> do <>, temos o seguinte:
+
+[source, python]
+----
+>>> from typing import SupportsComplex, SupportsAbs
+>>> from vector2d_v4 import Vector2d
+>>> v = Vector2d(3, 4)
+>>> isinstance(v, SupportsComplex)
+True
+>>> isinstance(v, SupportsAbs)
+True
+>>> complex(v)
+(3+4j)
+>>> abs(v)
+5.0
+>>> Vector2d.fromcomplex(3+4j)
+Vector2d(3.0, 4.0)
+----
+
+Para checagem de tipos durante a execução, o <> serve
+bem, mas para uma cobertura estática e relatório de erros melhores com o Mypy,
+os métodos `+__abs__+`, `+__complex__+`, e `fromcomplex` deveriam receber dicas
+de tipo, como mostrado no <>.
+
+[[ex_vector2d_complex_v5]]
+._vector2d_v5.py_: acrescentando anotações aos métodos mencionados
+====
+[source, python]
+----
+include::../code/13-protocol-abc/typing/vector2d_v5.py[tags=VECTOR2D_V5_COMPLEX]
+----
+====
+
+<1> A anotação de resultado `float` é necessária, senão o Mypy infere `Any`, e não
+checa o corpo do método.
+
+<2> Mesmo sem a anotação, o Mypy inferiu que isto devolve um
+`complex`. A anotação evita um aviso, dependendo da configuração do Mypy.
+
+<3> Aqui `SupportsComplex` garante que `n` é conversível.
+
+<4> Esta conversão explícita é necessária, pois um tipo _consistente-com_ 
+`SupportsComplex` não necessariamente tem os atributos `.real` e `.img`,
+que usamos na linha seguinte. A própria classe `Vector2d` não tem estes
+atributos, mas implementa `+__complex__+`.
+
+O tipo de retorno de `fromcomplex` pode ser `Vector2d` se a linha `from
+{dunder}future{dunder} import annotations` aparecer no início do módulo. Aquela
+importação faz as dicas de tipo serem armazenadas como strings, sem serem
+processadas durante a importação, quando as definições de função são tratadas.
+Sem o `+__future__+` import of `annotations`, `Vector2d` é uma referência
+inválida neste momento (a classe não está inteiramente definida ainda) e deveria
+ser escrita como uma string: `'Vector2d'`, como se fosse uma referência
+adiantada. Essa importação de `+__future__+` foi introduzida na
+https://fpy.li/pep563[PEP 563—Postponed Evaluation of Annotations], implementada
+no Python 3.7. Aquele comportamento estava marcado para se tornar default no
+3.10, mas a mudança foi adiada para uma versão futura.footnote:[Leia a
+https://fpy.li/13-32[decisão] do Python Steering Council na lista _python-dev_.]
+Quando isso acontecer, a importação será redundante mas inofensiva.
+
+Agora vamos criar—e depois estender—um novo protocolo estático.((("",
+startref="SPsupport13")))
+
+[[designing_static_proto_sec]]
+==== Projetando um protocolo estático
+
+Quando((("static protocols", "designing", id="SPdesign13"))) estudamos tipagem
+ganso, vimos a ABC `Tombola` na <>. Aqui vamos ver como
+definir uma interface similar usando um protocolo estático.
+
+A ABC `Tombola` especifica dois métodos: `pick` e `load`. Poderíamos também
+definir um protocolo estático com esses dois métodos, mas aprendi com a
+comunidade Go que protocolos de apenas um método tornam a tipagem pato estática
+mais útil e flexível. A biblioteca padrão do Go tem inúmeras interfaces, como
+`Reader`, uma interface para E/S que requer apenas um método `read`. 
+Depois, se você concluir que um protocolo mais complexo é necessário,
+pode combinar dois ou mais protocolos para definir um novo.
+
+<<<
+Usar um componente que escolhe itens aleatoriamente pode ou não exigir o
+recarregamento do componente, mas ele certamente precisa de um método para 
+sortear um item, então escolhi o método `pick` para o
+protocolo mínimo `RandomPicker`. O código do protocolo está no
+<>, e seu uso é demonstrado por testes no
+<>.
+
+[[ex_randompick_protocol]]
+._randompick.py_: definição de `RandomPicker`
+====
+[source, python]
+----
+include::../code/13-protocol-abc/typing/randompick.py[]
+----
+====
+
+[NOTE]
+====
+
+O método `pick` retorna `Any`. Na <>
+veremos como tornar `RandomPicker` um tipo genérico, com um parâmetro que
+permite aos usuários do protocolo especificarem o tipo de retorno do método
+`pick`.
+
+====
+
+[[ex_randompick_protocol_demo]]
+._randompick_test.py_: `RandomPicker` em uso
+====
+[source, python]
+----
+include::../code/13-protocol-abc/typing/randompick_test.py[]
+----
+====
+
+<1> Não é necessário importar um protocolo estático para definir uma classe que
+o implementa; aqui eu importei `RandomPicker` apenas para usá-lo em
+`test_isinstance` mais tarde.
+
+<2> `SimplePicker` implementa `RandomPicker`, mas não é uma subclasse dele.
+Isso é a tipagem pato estática em ação.
+
+<3> `Any` é o tipo de retorno default, então essa anotação não é estritamente
+necessária, mas deixa mais claro que estamos implementando o protocolo
+`RandomPicker`, como definido em <>.
+
+<4> Não esqueça de acrescentar dicas `-> None` aos seus testes, se quiser
+que o Mypy olhe para eles.
+
+<5> Acrescentei uma dica de tipo para a variável `popper`, para mostrar que o
+Mypy entende que o `SimplePicker` é _consistente-com_.
+
+<6> Teste provando que uma instância de `SimplePicker` também é uma instância
+de `RandomPicker`. Isso funciona por causa do decorador `@runtime_checkable`
+aplicado a `RandomPicker`, e porque o `SimplePicker` tem um
+método `pick`, como exigido.
+
+<7> Este teste invoca o método `pick` de `SimplePicker`, verifica que ele
+retorna um dos itens dados a `SimplePicker`, e então realiza testes estáticos e
+de execução sobre o item obtido.
+
+<8> Esta linha gera uma observação no relatório do Mypy.
+
+Como vimos no https://fpy.li/92[«Exemplo 22 do Capítulo 8»] (vol.1), `reveal_type` é uma função "mágica"
+reconhecida pelo Mypy. Ela não é importada, e só conseguimos invocá-la
+de dentro de blocos `if` cuja condição é `typing.TYPE_CHECKING`, uma constante 
+que é sempre considerada `True` pelos checadores de tipos estáticos,
+mas é `False` durante a execução do programa.
+
+Os dois testes no <> passam.
+O Mypy também não encontra erro naquele código,
+e mostra o resultado de `reveal_type` sobre o `item`
+retornado por `pick`:
+
+[source, shell]
+----
+$ mypy randompick_test.py
+randompick_test.py:24: note: Revealed type is 'Any'
+----
+
+Tendo criado nosso primeiro protocolo, vamos estudar algumas recomendações sobre
+essa prática.((("", startref="SPdesign13")))
+
+[[best_protocol_design_sec]]
+==== Melhores práticas no desenvolvimento de protocolos
+
+Após((("static protocols", "best practices for protocol design"))) 10 anos de
+experiência com tipagem pato estática em Go, está claro que protocolos estreitos
+são mais úteis—muitas vezes tais protocolos têm um único método, raramente mais
+que um par de métodos. Martin Fowler descreve uma boa ideia para se ter em mente
+ao desenvolver protocolos: a https://fpy.li/13-33[_Role Interface_],
+(interface papel—no sentido de incorporar uma personagem).
+A ideia é que um protocolo deve ser definido em termos de um papel
+que um objeto pode desempenhar, e não em termos de uma classe específica.
+
+Além disso, é comum ver um protocolo definido próximo a uma função que o usa
+para anotar um argumento, em, vez de forçar os clientes da função a importar
+uma definição de interface de alguma biblioteca central.
+Isso facilita a criação de novos tipos compatíveis com aquela função,
+favorecendo a extensibilidade e facilitando testes com _mocks_
+(simulacros).
+
+As duas práticas, protocolos estreitos e protocolos em código cliente, evitam
+um acoplamento muito forte, em acordo com o https://fpy.li/6v[Princípio da
+Segregação de Interface], que podemos resumir como "Clientes não devem ser
+forçados a depender de interfaces que não usam."
+
+A página https://fpy.li/13-35[_Contributing to typeshed_] (Colaborando com o typeshed)
+recomenda a seguinte convenção de nomenclatura para protocolos estáticos:
+
+* Use nomes simples para protocolos que representam um conceito claro (e.g.,
+`Iterator`, `Container`).
+
+* Use `SupportsX` para protocolos que oferecem métodos que podem ser chamados
+(e.g., `SupportsInt`, `SupportsRead`, `SupportsReadSeek`).footnote:[Qualquer
+método pode ser chamado, então essa recomendação não diz muito. Talvez "forneça
+um ou dois métodos"? De qualquer forma, é uma recomendação, não uma regra
+absoluta.]
+
+* Use `HasX` para protocolos que têm atributos de dados que podem ser lidos ou
+escritos, ou métodos _getter/setter_ (e.g., `HasItems`, `HasFileno`).
+
+A biblioteca padrão do Go tem uma convenção de nomenclatura que eu gosto: para
+protocolos de método único, se o nome do método é um verbo, acrescente o sufixo
+adequado (em inglês, "-er" ou "-or", em geral) para torná-lo um substantivo. Por
+exemplo, em vez de `SupportsRead`, temos `Reader`. Outros exemplos incluem
+`Formatter`, `Animator`, e `Scanner`. Para se inspirar, veja
+https://fpy.li/13-36["Go (Golang) Standard Library Interfaces (Selected)"]
+de Asuka Kenji.
+
+Uma boa razão para se criar protocolos minimalistas é que eles servem de base
+para protocolos mais complexos, quando necessário. Veremos a seguir que não é
+difícil criar um protocolo derivado com um método adicional.
+
+==== Estendendo um protocolo
+
+Como((("static protocols", "extending"))) mencionei na seção anterior, os
+desenvolvedores Go defendem que, na dúvida, melhor escolher o minimalismo
+ao definir interfaces—o nome usado para protocolos estáticos naquela linguagem.
+Muitas das interfaces Go mais usadas têm um único método.
+
+Quando a prática revela que um protocolo com mais métodos seria útil, em vez de
+adicionar métodos ao protocolo original, é melhor derivar dali um novo
+protocolo. Estender um protocolo estático em Python tem algumas ressalvas, como
+mostra o <>.
+
+[[ex_randompickload_protocol]]
+._randompickload.py_: estendendo `RandomPicker`
+====
+[source, python]
+----
+include::../code/13-protocol-abc/typing/randompickload.py[]
+----
+====
+
+<1> Se você quer que o protocolo derivado possa ser checado durante a execução,
+precisa aplicar o decorador `@runtime_checkable` novamente—pois os
+comportamentos definidos em decoradores de classes não são
+herdados.footnote:[Para detalhes e justificativa, veja a seção sobre
+https://fpy.li/13-37[`@runtime_checkable`] na PEP 544—Protocols: Structural
+subtyping (static duck typing).]
+
+<2> Todo protocolo deve nomear explicitamente `typing.Protocol` como uma de suas
+classes base, além do protocolo que estamos estendendo. Isto é diferente da
+forma como herança funciona de modo geral.footnote:[Novamente, leia 
+https://fpy.li/13-38[_Merging and extending protocols_] na PEP 544 para os
+detalhes e justificativa.]
+
+<3> De volta à programação orientada a objetos "normal": só precisamos declarar
+o método novo no protocolo derivado. A declaração do método `pick` é herdada de
+`RandomPicker`.
+
+Isto conclui o último exemplo sobre definir e usar um protocolo estático neste
+capítulo. Para encerrar, vamos olhar as ABCs numéricas e sua possível
+substituição por protocolos numéricos.
+
+
+[[numbers_abc_proto_sec]]
+==== As ABCs em numbers e os novos protocolos numéricos
+
+Como((("static protocols", "numbers ABCS and numeric protocols",
+id="SPnumbers13")))((("numbers ABCs", id="number13")))((("numeric protocols",
+id="numpro13")))((("protocols", "numeric", id="Pnum13"))) vimos na
+https://fpy.li/8g[«Seção 8.5.7.1»] (vol.1), as ABCs no pacote `numbers` da biblioteca padrão
+funcionam bem para checagem de tipos durante a execução.
+
+Se você precisa checar um inteiro, pode usar `isinstance(x, numbers.Integral)`
+para aceitar `int`, `bool` (que é subclasse de `int`) ou outros tipos inteiros
+oferecidos por bibliotecas externas que registram seus tipos como subclasses
+virtuais das ABCs de `numbers`. Por exemplo, a NumPy tem
+https://fpy.li/13-39[21 tipos inteiros]—bem como diversos tipos de ponto flutuante
+registrados como `numbers.Real`, e números complexos com várias amplitudes de
+bits, registrados como `numbers.Complex`.
+
+[TIP]
+====
+
+Vale notar que `decimal.Decimal` não é registrado como uma subclasse virtual de
+`numbers.Real`. A razão é que, se você precisa da precisão de `Decimal`, então
+vai querer evitar mistura acidental de números decimais com números de ponto
+flutuante (que são menos precisos).
+
+====
+
+Infelizmente, a torre numérica não foi projetada para checagem de tipo estática.
+A ABC raiz—`numbers.Number`—não tem métodos, então se você declarar `x: Number`,
+o Mypy não vai deixar você fazer operações aritméticas ou chamar qualquer método
+com `X`.
+
+Quando as ABCs de `numbers` não servem, quais as opções?
+Um bom lugar para procurar soluções de tipagem é no projeto _typeshed_. Como
+parte da biblioteca padrão de Python, o módulo `statistics` tem um arquivo stub
+correspondente no _typeshed_ com dicas de tipo, o
+https://fpy.li/13-40[_statistics.pyi_].
+
+Lá você encontrará as seguintes definições, que são usadas para anotar diversas funções:
+
+[source, python]
+----
+_Number = Union[float, Decimal, Fraction]
+_NumberT = TypeVar('_NumberT', float, Decimal, Fraction)
+----
+
+Essa abordagem está correta, mas é limitada.
+Ela não suporta((("numeric types", "support for"))) tipos numéricos
+fora da biblioteca padrão, que as ABCs de `numbers` suportam durante a
+execução—quando tipos numéricos são registrados como subclasses virtuais.
+
+A tendência atual é recomendar os protocolos numéricos fornecidos pelo módulo `typing`,
+como `SupportsFloat`, que discutimos na <>.
+
+Infelizmente, durante a execução os protocolos numéricos podem deixar você na
+mão. Como mencionado na <>, o tipo `complex` no
+Python 3.9 implementa `+__float__+`, mas o método existe apenas para lançar uma
+`TypeError` com uma mensagem explícita: "can't convert complex to float" (não é
+possível converter complex para float). Por alguma razão, ele também implementa
+`+__int__+`. A presença destes métodos faz `isinstance` produzir resultados
+enganosos no Python 3.9. No Python 3.10, os métodos de `complex` que geravam
+`TypeError` incondicionalmente foram removidos.footnote:[ver
+https://fpy.li/13-41[Issue #41974—Remove `+complex.__float__+`,
+`+complex.__floordiv__+`, etc].]
+
+Por outro lado, os tipos complexos da NumPy implementam métodos `+__float__+` e
+`+__int__+` que funcionam, emitindo apenas um aviso quando cada um deles é usado
+pela primeira vez:
+
+[source, python]
+----
+>>> import numpy as np
+>>> cd = np.cdouble(3+4j)
+>>> cd
+(3+4j)
+>>> float(cd)
+:1: ComplexWarning: Casting complex values to real
+discards the imaginary part
+3.0
+----
+
+O problema oposto também acontece:
+os tipos embutidos `complex`, `float`, e `int`, bem como `numpy.float16` e
+`numpy.uint8`, não têm um método `+__complex__+`, então `isinstance(x,
+SupportsComplex)` retorna `False` para eles.footnote:[Eu não testei todas as
+outras variantes de _float_ e _integer_ que a NumPy oferece.] Os tipos complexos
+da NumPy, tal como `np.complex64`, implementam `+__complex__+` para conversão em
+um `complex` embutido.
+
+Entretanto, na prática, o construtor embutido `complex()` trabalha com
+instâncias de todos esses tipos sem erros ou avisos.
+
+[source, python]
+----
+>>> import numpy as np
+>>> from typing import SupportsComplex
+>>> sample = [1+0j, np.complex64(1+0j), 1.0, np.float16(1.0), 1, np.uint8(1)]
+>>> [isinstance(x, SupportsComplex) for x in sample]
+[False, True, False, False, False, False]
+>>> [complex(x) for x in sample]
+[(1+0j), (1+0j), (1+0j), (1+0j), (1+0j), (1+0j)]
+----
+
+Isso mostra que checagens de `SupportsComplex` com `isinstance` sugerem que
+todas aquelas conversões para `complex` falhariam, mas elas funcionam. Na
+lista de discussão _typing-sig_, Guido van Rossum indicou que o `complex` embutido
+aceita um único argumento, e por isso as conversões funcionam.
+
+Por outro lado, o Mypy aceita argumentos de todos esses seis tipos em uma
+chamada à função `to_complex()`, definida assim:
+
+[source, python]
+----
+def to_complex(n: SupportsComplex) -> complex:
+    return complex(n)
+----
+
+No momento em que escrevo isso, a NumPy não tem dicas de tipo, então seus tipos
+numéricos são todos `Any`.footnote:[Os tipos numéricos da NumPy são todos
+registrados com as ABCs apropriadas de `numbers`, que o Mypy ignora.] Por outro
+lado, o Mypy de alguma maneira "sabe" que o `int` e o `float` embutidos podem
+ser convertidos para `complex`, apesar de, no _typeshed_, apenas a classe
+embutida `complex` ter o método `+__complex__+`.footnote:[Isso é uma mentira bem
+intencionada da parte do typeshed: a partir de Python 3.9, o tipo embutido
+`complex` na verdade não tem mais um método `+__complex__+`.]
+
+Concluindo, apesar((("numeric types", "checking for convertibility"))) da
+expectativa de que a checagem de tipos numéricos não seria difícil, a situação
+atual é a seguinte: as dicas de tipo da PEP 484
+https://fpy.li/cardxvi[desprezam] a torre numérica e recomendam implicitamente
+que os checadores de tipos tratem como casos especiais as relações de tipo entre
+os `complex`, `float`, e `int` embutidos. O Mypy faz isso, e também,
+pragmaticamente, aceita que `int` e `float` são _consistente-com_
+`SupportsComplex`, apesar deles não implementarem `+__complex__+`.
+
+[TIP]
+====
+Só encontrei resultados inesperados usando checagens com `isinstance` em
+conjunto com os protocolos numéricos `Supports*` quando fiz experiências de
+conversão de ou para `complex`. Se você não usa números complexos, pode confiar
+naqueles protocolos em vez das ABCs de `numbers`.
+====
+
+As principais lições dessa seção são:
+
+* As ABCs de `numbers` são boas para checagem de tipos durante a execução, mas
+não servem para tipagem estática.
+
+* Os protocolos numéricos estáticos `SupportsComplex`, `SupportsFloat`, etc.
+funcionam bem para tipagem estática, mas são pouco confiáveis para checagem de
+tipos durante a execução se números complexos estiverem envolvidos.
+
+Estamos agora prontos para a revisão dos temas deste capítulo.((("",
+startref="Pstatic13")))((("", startref="Pnum13")))((("",
+startref="numpro13")))((("", startref="number13")))((("",
+startref="SPnumbers13")))
+
+
+=== Resumo do capítulo
+
+O((("interfaces", "overview of")))((("protocols", "overview of")))((("ABCs (abstract base classes)",
+"overview of"))) Mapa de Tipagem (<>) 
+é a chave para entender este capítulo. Após uma breve introdução às quatro
+abordagens da tipagem, comparamos protocolos dinâmicos e estáticos, que
+suportam tipagem pato e tipagem pato estática, respectivamente. Os dois tipos de
+protocolo compartilham uma característica essencial: nunca é exigido de uma
+classe que ela declare explicitamente o suporte a qualquer protocolo.
+Uma classe suporta um protocolo apenas implementando os métodos necessários.
+
+A próxima parada foi a <>, onde exploramos os esforços que o
+interpretador Python faz para que os protocolos dinâmicos de sequência e
+iterável funcionem, incluindo a implementação parcial de ambos. Então vimos como
+fazer uma classe implementar um protocolo durante a execução, através da adição
+de métodos via _monkey patching_. A seção sobre tipagem pato terminou com
+sugestões de programação defensiva, incluindo a detecção de tipos estruturais
+sem checagens explícitas com `isinstance` ou `hasattr`, usando `try/except` e
+falhando logo.
+
+Após Alex Martelli introduzir a tipagem ganso em _<>_, vimos
+como criar subclasses de ABCs existentes, examinamos algumas ABCs importantes da
+biblioteca padrão, e criamos uma ABC do zero, que então implementamos por
+herança e por registro. Finalizamos aquela seção vendo como o método especial
+`+__subclasshook__+` permite que ABCs suportem a tipagem estrutural, pelo
+reconhecimento de classes não-relacionadas, mas que fornecem os métodos 
+exigidos pela interface declarada na ABC.
+
+Retomamos o estudo da tipagem pato estática na <>, que
+iniciamos na https://fpy.li/8m[«Seção 8.5.10»] (vol.1). Vimos como
+o decorador `@runtime_checkable` também aproveita o `+__subclasshook__+` para
+suportar tipagem estrutural durante a execução—mesmo que o melhor uso dos
+protocolos estáticos seja com checadores de tipos estáticos, que podem levar em
+consideração as dicas de tipo, tornando a tipagem estrutural mais confiável.
+Então falamos sobre o projeto e a codificação de um protocolo estático e como
+estendê-lo. O capítulo terminou com a triste história do abandono da torre
+numérica e das limitações da alternativa proposta: os protocolos numéricos
+estáticos, tal como `SupportsFloat` e outros adicionados ao módulo `typing` no
+Python 3.8.
+
+A mensagem principal deste capítulo é que temos quatro maneiras complementares
+de programar com interfaces no Python moderno, cada uma com diferentes vantagens
+e deficiências. Você encontrará casos de uso adequados para cada esquema de
+tipagem em qualquer base de código moderna de tamanho significativo. Rejeitar
+qualquer destas abordagens tornará seu trabalho como programador Python mais
+difícil e limitado.
+
+Dito isso, Python ganhou sua enorme popularidade enquanto suportava apenas
+tipagem pato. Outras linguagens populares que aproveitam o poder e a
+simplicidade da tipagem pato são Javascript, PHP e Ruby, e outras,
+menos populares mas muito influentes, como 
+Lisp, Smalltalk, Erlang, Elixir e Clojure.
+
+[[interfaces_further_reading]]
+=== Para saber mais
+
+Para((("interfaces", "further reading on")))((("protocols",
+"further reading on")))((("ABCs (abstract base classes)", "further reading on")))
+uma rápida revisão dos prós e contras da tipagem, bem como da importância de
+`typing.Protocol` para a saúde de bases de código checadas estaticamente,
+recomendo fortemente o post de Glyph Lefkowitz
+https://fpy.li/13-42[_I Want A New Duck: `typing.Protocol` and the future of duck typing_]
+(Quero um novo pato: `typing.Protocol` e o futuro da tipagem pato).
+Também aprendi bastante em seu post
+https://fpy.li/13-43[_Interfaces and Protocols_],
+comparando `typing.Protocol` com `zope.interface`—um mecanismo mais antigo
+para definir interfaces em sistemas que suportam plug-in fracamente acoplados, usado no
+https://fpy.li/13-44[_Plone CMS_], no framework Web
+na https://fpy.li/13-45[_Pyramid_], e no framework de programação assíncrona
+https://fpy.li/13-46[_Twisted_],
+um projeto fundado por Glyph.footnote:[Agradeço ao revisor técnico Jürgen Gmach
+por ter recomentado o post _Interfaces and Protocols_.]
+
+Bons livros sobre Python têm—quase que por definição—uma ótima cobertura de
+tipagem pato. Dois de meus livros favoritos de Python tiveram atualizações
+lançadas após a primeira edição de _Python Fluente_: _The Quick Python Book_,
+3rd ed., (Manning), de Naomi Ceder; e _Python in a Nutshell_, 3rd ed., de
+Alex Martelli, Anna Ravenscroft, e Steve Holden (O'Reilly).
+
+Para uma discussão sobre os prós e contras da tipagem dinâmica, veja a
+entrevista de Guido van Rossum com Bill Venners em
+https://fpy.li/13-47[_Contracts in Python: A Conversation with Guido van Rossum, Part IV_]
+(Contratos em Python: uma conversa com Guido van Rossum).
+O post https://fpy.li/13-48[_Dynamic Typing_], de Martin Fowler,
+traz uma avaliação perspicaz e equilibrada deste debate.
+Ele também escreveu
+https://fpy.li/13-33[_Role Interface_] (interface papel), que
+mencionei na <>. Apesar de não ser sobre tipagem pato,
+aquele post é altamente relevante para o projeto de protocolos em Python, pois
+ele contrasta as interfaces papel estreitas com as interfaces públicas bem mais
+abrangentes de classes em geral.
+
+A documentação do Mypy é, muitas vezes, a melhor fonte de informação sobre
+qualquer tema relacionado a tipagem estática em Python,
+incluindo à tipagem pato estática, tratada em
+https://fpy.li/13-50[_Protocols and structural subtyping_].
+
+As demais referências são sobre tipagem ganso.
+
+O _Python Cookbook_, 3rd ed. de Beazley & Jones (O'Reilly) tem uma seção sobre
+como definir uma ABC (Recipe 8.12). O livro foi escrito antes de Python 3.4,
+então eles não usam a atual sintaxe preferida para declarar ABCs, criar uma
+subclasse de `abc.ABC` (em vez disso, eles usam a palavra-chave `metaclass`, da
+qual só vamos precisar mesmo no https://fpy.li/24[«Capítulo 24»] (vol.3)). Tirando esse pequeno
+detalhe, a receita cobre os principais recursos das ABCs muito bem.
+
+_The Python Standard Library by Example_ de Doug Hellmann (Addison-Wesley), tem
+um capítulo sobre o módulo `abc`. Ele também está disponível na web, em
+https://fpy.li/13-51[_PyMOTW—Python Module of the Week_]. Hellmann usa a
+declaração de ABC no estilo antigo: `++PluginBase(metaclass=abc.ABCMeta)++` em vez de
+`PluginBase(abc.ABC)`, suportada desde o Python 3.4.
+
+Quando usamos ABCs, herança múltipla não é apenas comum, mas praticamente
+inevitável, pois cada uma das ABCs fundamentais de coleções (`Sequence`,
+`Mapping`, `Set`) estende `Collection`, que por sua vez estende múltiplas ABCs
+(veja <>). Assim, o <> é um complemento
+importante ao presente capítulo.
+
+A https://fpy.li/13-52[_PEP 3119–Introducing Abstract Base Classes_]
+apresenta a justificativa para as ABCs.
+A https://fpy.li/13-53[_PEP 3141–A Type Hierarchy for Numbers_]
+apresenta as ABCs do https://fpy.li/13-54[módulo `numbers`],
+mas a discussão no
+https://fpy.li/13-55[_Mypy issue #3186_] intitulado
+"int is not a Number?" (int não é um número?)
+inclui alguns argumentos sobre por que a torre numérica não serve
+para checagem estática de tipo.
+Alex Waygood escreveu uma
+https://fpy.li/13-56[«resposta abrangente»] no StackOverflow, discutindo formas de anotar tipos numéricos.
+
+Vou continuar monitorando o 
+https://fpy.li/13-55[_Mypy issue #3186_]
+para os próximos capítulos dessa saga,
+na esperança de um final feliz que torne a tipagem estática
+e a tipagem ganso compatíveis, como deveriam ser.
+
+
+<<<
+[[interfaces_soapbox]]
+.Ponto de vista
+****
+
+**A Jornada MVP da tipagem estática no Python**
+
+Trabalhei((("interfaces", "Soapbox discussion", id="Isoap13")))((("protocols",
+"Soapbox discussion", id="Psoap13")))((("ABCs (abstract base classes)", "Soapbox
+discussion", id="ABCsoap13")))((("Soapbox sidebars", "static
+typing")))((("static protocols", "Soapbox discussion"))) na Thoughtworks, 
+empresa pioneira em metodologias ágeis de engenharia de software.
+A Thoughtworks muitas vezes ajuda os clientes a criar e implantar um MVP:
+_Minimum Viable Product_ (Produto Mínimo Viável),
+"uma versão simples de um produto, oferecida para os usuários com o
+objetivo de validar hipóteses centrais do negócio,"
+conforme a definição de Paulo Caroli em
+https://fpy.li/13-58[_Lean Inception_],
+um artigo no
+https://fpy.li/13-59[«blog coletivo»] editado por Martin Fowler.
+
+Guido van Rossum e os outros mantenedores que projetaram e implementaram a
+tipagem estática têm seguido a estratégia do MVP desde 2006. Primeiro, a
+https://fpy.li/pep3107[_PEP 3107—Function Annotations_] foi implementada no Python
+3.0 com uma semântica bastante limitada: apenas uma sintaxe para anexar
+anotações a parâmetros e resultados de funções, armazenadas no objeto função.
+Isso foi feito para explicitamente permitir experimentação e
+receber feedback—os principais benefícios de um MVP.
+
+Oito anos depois, a https://fpy.li/pep484[_PEP 484—Type Hints_] foi proposta e
+aprovada. Sua implementação, no Python 3.5, não exigiu mudanças na linguagem ou
+na biblioteca padrão—exceto a adição do módulo `typing`, do qual nenhuma outra
+parte da biblioteca padrão dependia.
+
+A PEP 484 suportava apenas tipos nominais com genéricos—similar ao Java—mas com
+a checagem estática sendo executada por ferramentas externas.
+Recursos importantes não existiam, como anotações de variáveis, tipos embutidos
+genéricos, e protocolos.
+
+Apesar destas limitações, este MVP de tipagem foi bem sucedido o suficiente para
+atrair investimento e adoção por parte de empresas com enormes bases de código
+em Python, como a Dropbox, o Google e o Facebook, bem como apoio de IDEs
+profissionais como o https://fpy.li/13-60[PyCharm], o
+https://fpy.li/13-61[Wing], e o https://fpy.li/13-62[VS Code].
+
+<<<
+A https://fpy.li/pep526[_PEP 526—Syntax for Variable Annotations_] foi o primeiro
+passo evolutivo que exigiu mudanças no interpretador, no Python 3.6. Mais
+mudanças no interpretador foram feitas na versão 3.7 para suportar a
+https://fpy.li/pep563[_PEP 563—Postponed Evaluation of Annotations_] e a
+https://fpy.li/pep560[_PEP 560—Core support for typing module and generic types_],
+que permitiram que coleções embutidas e da biblioteca padrão aceitem dicas de
+tipo genéricas "de fábrica" no Python 3.9, graças à
+https://fpy.li/pep585[_PEP 585—Type Hinting Generics In Standard Collections_].
+
+Durante todos esses anos, alguns usuários de Python—incluindo eu—ficamos
+desapontados com os tipos estáticos. Após aprender Go, a falta de
+tipagem pato estática em Python era incompreensível, 
+pois a tipagem pato sempre foi uma característica marcante desta linguagem.
+
+Mas essa é a natureza dos MVPs: eles podem não satisfazer todos os usuários em
+potencial, mas exigem menos esforço de implementação, e guiam o desenvolvimento
+posterior com o feedback do uso em situações reais.
+
+Se há uma coisa que todos aprendemos com Python 3, é que progresso incremental é
+mais seguro que lançamentos estrondosos. Estou contente que não tivemos que
+esperar pelo Python 4—se é que existirá—para tornar Python mais atrativo para
+grandes empresas, onde os benefícios da tipagem estática superam a complexidade
+adicional.
+
+**Abordagens à tipagem em linguagens populares**
+
+A <> é((("Soapbox sidebars", "typing map")))((("typing
+map"))) uma variação do Mapa de Tipagem (<>) com 
+algumas linguagens conhecidas que suportam cada um dos modos de tipagem.
+
+TypeScript e Python ≥ 3.8 são as únicas linguagens em minha pequena amostra que
+suportam todas as quatro abordagens.
+
+Go é claramente uma linguagem de tipos estáticos na tradição do Pascal, mas ela
+foi a pioneira da tipagem pato estática—pelo menos entre as linguagens mais
+usadas hoje. Também coloquei Go no quadrante da tipagem ganso por causa
+de sua sintaxe especial para checagem de tipo (_type assertion_),
+que permite tratar tipos nominais dinamicamente durante a execução.
+
+<<<
+{nbsp}
+
+[[type_systems_languages]]
+.Quatro abordagens para checagem de tipos e algumas linguagens que as usam.
+image::../images/mapa-da-tipagem-linguagens.png[align="center",pdfwidth=12cm]
+
+No ano 2000, só existiam linguagens populares nos quadrantes diametralmente opostos 
+da tipagem pato e da tipagem estática.
+Não conheço nenhuma linguagem que suportava tipagem pato estática ou
+tipagem ganso 20 anos atrás, mas pode ser que existam.
+O fato de cada um dos quatro quadrantes ter pelo
+menos três linguagens populares sugere que muita gente vê benefícios em cada uma
+das quatro abordagens à tipagem.
+
+**Monkey patching**
+
+Monkey patching((("Soapbox sidebars",
+"monkey-patching")))((("monkey-patching"))) tem uma reputação ruim. Se usado com
+exagero, pode gerar sistemas difíceis de entender e manter. O remendo (_patch_)
+dinâmico está
+normalmente fortemente acoplado ao seu alvo, tornando-se quebradiço quando o código
+evolui. Outro problema é
+que duas bibliotecas que aplicam remendos deste tipo durante a execução podem
+pisar nos pés uma da outra, com a segunda biblioteca a rodar destruindo os
+remendos da primeira.
+
+Mas o monkey patching pode também ser útil, por exemplo, para fazer uma classe
+implementar um protocolo durante a execução. O design pattern _Adapter_ resolve
+o mesmo problema de modo mais verboso implementando toda uma nova classe.
+
+É fácil usar monkey patching em código Python, mas há limitações. Ao contrário
+de Ruby e Javascript, Python não permite mudar o comportamento dos tipos
+embutidos durante a execução. Na verdade, considero isto uma vantagem, pois dá a
+certeza de que um objeto `str` terá sempre os mesmos métodos. Esta limitação reduz
+a chance de bibliotecas aplicarem correções conflitantes quando importadas em
+seu projeto.
+
+**Metáforas e _idioms_ em interfaces**
+
+Uma((("Soapbox sidebars", "interfaces"))) metáfora promove o entendimento
+tornando restrições e acessos visíveis. Esse é o valor das palavras _stack_
+(pilha) e _queue_ (fila) para descrever estruturas de dados fundamentais:
+elas tornam claras as operações permitidas, isto é, como os itens podem ser
+adicionados ou removidos. Por outro lado, Alan Cooper et al. escrevem em _About
+Face, the Essentials of Interaction Design_, 4th ed. (Wiley):
+
+[quote]
+____
+
+Fidelidade estrita a metáforas liga interfaces de forma desnecessariamente firme
+aos mecanismos do mundo físico.
+
+____
+
+Os autores estão falando de interface de usuário, mas a advertência se aplica também a
+APIs. Eles admitem que quando "cai no nosso colo" uma metáfora "verdadeiramente
+apropriada", podemos usá-la (escreveram "cai no nosso colo" porque é tão
+difícil encontrar metáforas adequadas que ninguém deveria perder tempo tentando
+encontrá-las). Acredito que a imagem da máquina de bingo que usei
+nesse capítulo é apropriada.
+
+_About Face_ é, disparado, o melhor livro sobre design de UI que já li—e eu li
+uns tantos. Abandonar as metáforas como paradigmas de design, adotando em seu
+lugar "interfaces idiomáticas", foi a lição mais valiosa que aprendi com o
+trabalho de Cooper.
+
+Em _About Face_, Cooper não lida com APIs, mas quanto mais penso em suas ideias,
+mais vejo como se aplicam ao Python. Cada protocolo fundamental da linguagem é
+o que Cooper chama de _idiom_ (idiomatismo).footnote:[Neste contexto,
+a tradução correta de _idiom_ não é "idioma", mas sim "idiomatismo" que é uma
+"construção ou locução peculiar a uma língua".
+Cooper defende que uma GUI
+é uma linguagem com locuções peculiares, como menus e caixas de diálogo.]
+
+Uma vez que aprendemos o que é uma "sequência",
+podemos aplicar este conhecimento em diferentes contextos. Este é o tema
+principal de _Python Fluente_: ressaltar os idiomatismos fundamentais da linguagem,
+para que o seu código seja conciso, eficaz e legível para um pythonista
+fluente.((("", startref="ABCsoap13")))((("", startref="Psoap13")))((("",
+startref="Isoap13")))
+
+****
diff --git a/vol2/cap14.adoc b/vol2/cap14.adoc
new file mode 100644
index 0000000..6681465
--- /dev/null
+++ b/vol2/cap14.adoc
@@ -0,0 +1,1594 @@
+[[ch_inheritance]]
+== Herança: para o bem ou para o mal
+:example-number: 0
+:figure-number: 0
+
+[quote, Alan Kay, Os Primórdios de Smalltalk]
+____
+
+[...] precisávamos de toda uma teoria melhor sobre herança (e ainda precisamos).
+Por exemplo, herança e instanciação (que é um tipo de herança) confundem
+a pragmática (fatorar o código para economizar espaço) quanto a
+semântica (usada para tarefas demais, como: especialização, generalização,
+especiação, etc.).footnote:[Alan Kay, "The Early History of Smalltalk" (_Os
+Primórdios de Smalltalk_), na SIGPLAN Not. 28, 3 (março de 1993), 69–95.
+Também disponível https://fpy.li/14-1[online]. Agradeço a meu amigo
+Christiano Anderson, por compartilhar essa referência quando eu estava
+escrevendo este capítulo.]
+
+____
+
+Este((("inheritance and subclassing", "topics covered"))) capítulo é sobre
+herança e criação de subclasses. Vou presumir um entendimento básico destes
+conceitos, que você pode ter aprendido lendo
+https://fpy.li/6w[«O Tutorial do Python»],
+ou trabalhando com outra linguagem orientada a objetos, tal como
+Java, C# ou {cpp}. Vamos nos concentrar em quatro características de
+Python:
+
+* A função `super()`
+* Armadilhas na criação de subclasses de tipos embutidos
+* Herança múltipla e a ordem de resolução de métodos
+* Classes mixin
+
+Herança múltipla acontece quando uma classe tem duas ou mais superclasses.
+Ela existe em {cpp}, mas não em Java ou C#.
+Muitos consideram que a herança múltipla não vale
+os problemas que causa. Ela foi deliberadamente deixada de fora de
+Java, após supostamente ser usada em excesso nos primeiros anos de {cpp}.
+
+Este capítulo apresenta a herança múltipla para aqueles que nunca a usaram,
+e oferece dicas para lidar com herança simples ou múltipla,
+quando necessário.
+
+Em 2021, quando escrevo essas linhas, há uma forte reação contra o uso excessivo
+de herança em geral—não apenas herança múltipla—porque superclasses e subclasses
+são fortemente acopladas, ou seja, interdependentes. Esse acoplamento forte
+significa que modificações em uma classe podem ter efeitos inesperados e de longo
+alcance em suas subclasses, tornando os sistemas frágeis e difíceis de entender.
+
+Entretanto, ainda temos que dar manutenção a sistemas existentes, que podem ter
+hierarquias de classe complexas, ou trabalhar com frameworks que nos obrigam a
+usar herança—algumas vezes até herança múltipla.
+
+Vou ilustrar as aplicações práticas da herança múltipla com a biblioteca padrão,
+o framework Django e o toolkit para programação de
+interface gráfica Tkinter.
+
+=== Novidades neste capítulo
+
+Não((("inheritance and subclassing", "significant changes to"))) há nenhum
+recurso novo no Python relacionado ao tema deste capítulo, mas fiz
+inúmeras modificações baseadas nos comentários dos revisores técnicos da segunda
+edição, especialmente Leonardo Rochael e Caleb Hattingh.
+
+Escrevi uma nova seção de abertura, tratando especificamente da função embutida
+`super()`, e mudei os exemplos na <>, para explorar mais
+profundamente a forma como `super()` suporta a herança múltipla cooperativa.
+
+A <> também é nova. Reorganizei a <>,
+apresentando exemplos mais simples de _mixin_ na bilbioteca
+padrão, antes de apresentar o exemplos com o Django e a 
+hierarquia complicada do Tkinter.
+
+Como o próprio título sugere, as ressalvas à herança sempre foram um dos temas
+principais desse capítulo. Mas como cada vez mais desenvolvedores consideram
+essa técnica problemática, acrescentei alguns parágrafos sobre como evitar a
+herança no final da <> e da
+<>.
+
+Vamos começar com uma revisão da mal compreendida função `super()`.
+
+
+=== A função `super()`
+
+O((("inheritance and subclassing", "super() function",
+id="IACsuper14")))((("super() function", id="super14")))((("functions", "super() function")))
+uso consistente da função embutida `super()` é essencial na criação
+de programas orientados a objetos fáceis de manter em Python.
+
+Quando uma subclasse sobrescreve um método de uma superclasse, o novo método
+normalmente precisa invocar o método correspondente na superclasse. Aqui está o
+modo recomendado de fazer isso, tirado de um exemplo da documentação do módulo
+_collections_, na seção
+https://fpy.li/6x[OrderedDict: Exemplos e Receitas].:footnote:[A docstring
+original estava errada, reportei no https://fpy.li/7e[_issue #141721_],
+enviei PR, e traduzi aqui.]
+
+[source, python]
+----
+class LastUpdatedOrderedDict(OrderedDict):
+    """Armazena itens mantendo por ordem de atualização."""
+
+    def __setitem__(self, key, value):
+        super().__setitem__(key, value)
+        self.move_to_end(key)
+----
+
+Para executar sua tarefa, `LastUpdatedOrderedDict` sobrescreve `+__setitem__+` para:
+
+. Usar `+super().__setitem__+`, invocando aquele método na superclasse e
+permitindo que ele insira ou atualize o par chave/valor.
+
+. Invocar `self.move_to_end`, para garantir que a `key` atualizada esteja na
+última posição.
+
+Invocar um `+__init__+` herdado é particulamente importante, para permitir que a
+superclasse execute sua parte na inicialização da instância.
+
+[TIP]
+====
+
+Se você aprendeu programação orientada a objetos com Java, deve se lembrar de
+que, naquela linguagem, um método construtor invoca automaticamente o construtor
+sem argumentos da superclasse. Python não faz isso. Acostume-se a escrever o
+seguinte código padrão:
+
+[source, python]
+----
+    def __init__(self, a, b) :
+        super().__init__(a, b)
+        ... # mais código para inicializar a instância
+----
+====
+
+Você pode já ter visto código que não usa `super()`, e em vez disso invoca o
+método na superclasse diretamente, assim:
+
+[source, python]
+----
+class NotRecommended(OrderedDict):
+    """Isto é um contra-exemplo!"""
+
+    def __setitem__(self, key, value):
+        OrderedDict.__setitem__(self, key, value)
+        self.move_to_end(key)
+----
+
+Esta alternativa até funciona nesse caso em particular, mas não é recomendada por duas razões.
+Primeiro, codifica a superclasse explicitamente.
+O nome `OrderedDict` aparece na declaração `class` e também dentro de
+`+__setitem__+`. Se, no futuro, alguém modificar a declaração `class` para mudar
+a classe base ou adicionar outra, pode se esquecer de atualizar o corpo de
+`+__setitem__+`, introduzindo um bug.
+
+A segunda razão é que `super` implementa lógica para tratar hierarquias de
+classe com herança múltipla.
+Voltaremos a isso na <>.
+Para concluir essa recapitulação de `super`, é bom rever como essa função era
+invocada no Python 2. Sem os parâmetros default, a assinatura de `super` é mais
+reveladora:
+
+[source, python]
+----
+class LastUpdatedOrderedDict(OrderedDict):
+    """Funciona igual em Python 2 e Python 3"""
+
+    def __setitem__(self, key, value):
+        super(LastUpdatedOrderedDict, self).__setitem__(key, value)
+        self.move_to_end(key)
+----
+
+Os dois parâmetros de `super` agora são opcionais.
+O compilador de bytecode de Python 3 fornece os argumentos examinando o contexto
+quando `super()` é invocado dentro de um método.
+Os parâmetros são:
+
+`type`::
+    O início do caminho para a superclasse que implementa o método desejado.
+    Por default, é a classe onde está o método que invoca `super()`.
+
+`object_or_type`::
+    O objeto (ao invocar métodos de instância) ou classe (ao invocar
+    métodos de classe) que será o receptor da chamada ao
+    método.footnote:[Adotamos o termo "receptor" como tradução para _receiver_,
+    que é o objeto `x` vinculado um método `m` no momento da chamada `x.m()`.]
+    Por default, é `self` se a chamada `super()` acontece no corpo de um método
+    de instância.
+
+A chamada a `super()` devolve um objeto proxy dinâmico que encontra um método (tal
+como `+__setitem__+` no exemplo) em uma superclasse do argumento `type` e o
+vincula a `object_or_type`, de modo que não precisamos passar explicitamente o
+receptor (`self`) quando invocamos o método.
+
+<<<
+No Python 3, ainda é permitido passar explicitamente o primeiro e o segundo
+argumentos a `super()`.footnote:[Também é possível passar apenas o primeiro
+argumento, mas isso não é útil e pode logo ser descontinuado, com as bênçãos de
+Guido van Rossum, o próprio criador de `super()`. Veja a discussão em
+https://fpy.li/14-4[_Is it time to deprecate unbound super methods?_]
+(Está na hora de descontinuar métodos "super" não vinculados?).]
+Mas eles são necessários apenas em casos especiais, para testes, ou depuração,
+ou para contornar algum comportamento indesejado em uma superclasse.
+
+Vamos agora discutir armadilhas na criação de subclasses de tipos
+embutidos.((("", startref="super14")))((("", startref="IACsuper14")))
+
+
+[[subclass_builtin_woes_sec]]
+=== Problemas com subclasses de tipos embutidos
+
+Nas((("inheritance and subclassing", "subclassing built-in types",
+id="IASsubbuilt14"))) primeiras versões do Python não era possível criar
+subclasses de tipos embutidos como `list` ou `dict`. Desde o Python 2.2 isso é
+possível, mas há uma limitação importante: o código em C dos tipos
+embutidos normalmente não invoca os métodos sobrescritos por classes definidas
+pelo usuário. Há uma boa descrição curta do problema na documentação do PyPy, na
+seção _Differences between PyPy and CPython_ (Diferenças entre o PyPy e o
+CPython), em https://fpy.li/pypydif[_Subclasses of built-in types_]
+(Subclasses de tipos embutidos)]:
+
+[quote]
+____
+Oficialmente, o CPython não tem nenhuma regra sobre exatamente quando um método
+sobrescrito de subclasses de tipos embutidos é ou não invocado implicitamente.
+Como uma aproximação, esses métodos nunca são chamados por outros métodos
+embutidos do mesmo objeto. Por exemplo, um `+__getitem__+` sobrescrito em uma
+subclasse de `dict` nunca será invocado pelo método `get()` do tipo embutido.
+____
+
+Concretamente, isto significa que `meu_dict['x']` e `meu_dict.get('x')`
+podem produzir resultados diferentes, mesmo no caso mais simples quando
+a chave `'x'` existe, supondo que `meu_dict` é uma instância de uma subclasse
+de `dict` criada por você.
+
+O <> ilustra o problema.
+
+<<<
+[[ex_doppeldict]]
+.Nosso `+__setitem__+` sobrescrito é ignorado pelos métodos `+__init__+` e `+__update__+` to tipo embutido `dict`
+====
+[source, python]
+----
+>>> class DoppelDict(dict):
+...     def __setitem__(self, key, value):
+...         super().__setitem__(key, [value] * 2)  # <1>
+...
+>>> dd = DoppelDict(one=1)  # <2>
+>>> dd
+{'one': 1}
+>>> dd['two'] = 2  # <3>
+>>> dd
+{'one': 1, 'two': [2, 2]}
+>>> dd.update(three=3)  # <4>
+>>> dd
+{'three': 3, 'one': 1, 'two': [2, 2]}
+----
+====
+
+<1> `+DoppelDict.__setitem__+` duplica os valores ao armazená-los (por nenhuma
+razão, apenas para termos um efeito visível). Ele funciona delegando
+para a superclasse.
+
+<2> O método `+__init__+`, herdado de `dict`, claramente ignora que
+`+__setitem__+` foi sobrescrito: o valor de `'one'` não foi duplicado.
+
+<3> O operador `[]` invoca nosso `+__setitem__+` e funciona como esperado:
+`'two'` está mapeado para o valor duplicado `[2, 2]`.
+
+<4> O método `update` de `dict` também não usa nossa versão de `+__setitem__+`:
+o valor de `'three'` não foi duplicado.
+
+Este comportamento dos tipos embutidos viola uma regra básica da
+programação orientada a objetos: a busca por métodos deveria sempre começar pela
+classe do receptor (`self`), mesmo quando a invocação ocorre dentro de um método
+implementado na superclasse. Isso é o que se chama _late binding_ (vinculação tardia),
+que Alan Kay—um dos criadores de Smalltalk—considera ser uma
+característica essencial da programação orientada a objetos: em qualquer chamada na
+forma `x.method()`, o método exato a ser chamado deve ser determinado durante a
+execução, baseado na classe do receptor `x`.footnote:[É interessante observar
+que o {cpp} diferencia métodos virtuais e não-virtuais. Métodos virtuais têm
+vinculação tardia, enquanto os métodos não-virtuais são vinculados na
+compilação. Apesar de todos os métodos que podemos escrever em Python serem de
+vinculação tardia, como um método virtual, objetos embutidos escritos em C
+parecem ter métodos não-virtuais por default, pelo menos no CPython.] Este
+triste estado de coisas contribui para os problemas que vimos na
+https://fpy.li/88[«Seção 3.5.3»] (vol.1).
+
+O problema não está limitado a chamadas dentro de uma instância—saber se
+`self.get()` invoca `+self.__getitem__()+`. Também acontece com métodos
+sobrescritos de outras classes que deveriam ser chamados por métodos embutidos.
+O <> foi adaptado da https://fpy.li/14-5[documentação do
+PyPy].
+
+[[ex_other_subclass]]
+.O `+__getitem__+` de `AnswerDict` é ignorado por `dict.update`
+====
+[source, python]
+----
+>>> class AnswerDict(dict):
+...     def __getitem__(self, key):  # <1>
+...         return 42
+...
+>>> ad = AnswerDict(a='foo')  # <2>
+>>> ad['a']  # <3>
+42
+>>> d = {}
+>>> d.update(ad)  # <4>
+>>> d['a']  # <5>
+'foo'
+>>> d
+{'a': 'foo'}
+----
+====
+<1> `+AnswerDict.__getitem__+` sempre devolve `42`, independente da chave.
+<2> `ad` é um `AnswerDict` carregado com o par chave-valor `('a', 'foo')`.
+<3> `ad['a']` devolve `42`, como esperado.
+<4> `d` é uma instância direta de `dict`, que atualizamos com `ad`.
+<5> O método `dict.update` ignora nosso `+AnswerDict.__getitem__+`.
+
+[WARNING]
+====
+
+Criar subclasses diretamente de tipos embutidos, como `dict`, `list` ou `str`, é
+um processo propenso ao erro, pois os métodos embutidos quase sempre ignoram 
+métodos sobrescritos pelo usuário. Em vez de criar subclasses de tipos
+embutidos, derive suas classes do módulo
+https://fpy.li/2w[`collections`], usando
+as classes `UserDict`, `UserList`, e `UserString`, que foram projetadas para
+serem fáceis de estender.
+
+====
+
+Herdando de `collections.UserDict` em vez de `dict`, os problemas expostos no
+<> e no <> desaparecem. Veja o
+<>.
+
+[[ex_userdict_ok]]
+.`DoppelDict2` and `AnswerDict2` funcionam como esperado, porque estendem `UserDict` e não `dict`
+====
+[source, python]
+----
+>>> import collections
+>>>
+>>> class DoppelDict2(collections.UserDict):
+...     def __setitem__(self, key, value):
+...         super().__setitem__(key, [value] * 2)
+...
+>>> dd = DoppelDict2(one=1)
+>>> dd
+{'one': [1, 1]}
+>>> dd['two'] = 2
+>>> dd
+{'two': [2, 2], 'one': [1, 1]}
+>>> dd.update(three=3)
+>>> dd
+{'two': [2, 2], 'three': [3, 3], 'one': [1, 1]}
+>>>
+>>> class AnswerDict2(collections.UserDict):
+...     def __getitem__(self, key):
+...         return 42
+...
+>>> ad = AnswerDict2(a='foo')
+>>> ad['a']
+42
+>>> d = {}
+>>> d.update(ad)
+>>> d['a']
+42
+>>> d
+{'a': 42}
+----
+====
+
+Como um experimento, para medir o trabalho extra necessário para criar uma
+subclasse de um tipo embutido, reescrevi a classe `StrKeyDict` do
+https://fpy.li/93[«Exemplo 9 do Capítulo 3»] (vol.1),
+para torná-la uma subclasse de `dict` em vez de `UserDict`.
+Para fazê-la passar pelo mesmo banco de testes, tive que implementar
+`+__init__+`, `get`, e `update`, pois as versões herdadas de `dict` se recusaram
+a cooperar com os métodos sobrescritos `+__missing__+`, `+__contains__+` e
+`+__setitem__+`. A subclasse de `UserDict` no
+https://fpy.li/93[«Exemplo 9 do Capítulo 3»] (vol.1)
+tem 16 linhas, enquanto a subclasse experimental de `dict` acabou com 33 linhas.footnote:[Se
+você tiver curiosidade, o experimento está no arquivo
+https://fpy.li/14-7[_14-inheritance/strkeydict_dictsub.py_] do repositório
+https://fpy.li/code[_fluentpython/example-code-2e_].]
+
+
+Para deixar claro: esta seção tratou de um problema que se aplica apenas à
+delegação a métodos dentro do código em C dos tipos embutidos, e afeta apenas
+classes derivadas diretamente daqueles tipos. Se você criar uma subclasse de uma
+classe escrita em Python, tal como `UserDict` ou `MutableMapping`, não vai
+encontrar este problema.footnote:[Aliás, nesse mesmo tópico, o PyPy se comporta
+mais "corretamente" que o CPython, às custas de introduzir uma pequena
+incompatibilidade. Veja os detalhes em https://fpy.li/14-5["Differences between
+PyPy and CPython" (_Diferenças entre o PyPy e o CPython_)].]
+
+Vamos agora examinar uma questão que aparece na herança múltipla: se uma classe
+tem duas superclasses, como Python decide qual atributo usar quando invocamos
+`super().attr`, mas ambas as superclasses têm um atributo com este
+nome?((("",startref="IASsubbuilt14")))
+
+[[mult_inherit_mro_sec]]
+=== Herança múltipla e a Ordem de Resolução de Métodos
+
+Qualquer((("inheritance and subclassing", "multiple inheritance and method resolution order",
+id="IASmultiple14")))((("multiple inheritance", "method resolution order and",
+id="mulinh14")))((("method resolution order (MRO)", id="methres14")))
+linguagem que implemente herança múltipla precisa lidar com o
+potencial conflito de nomes, quando superclasses contêm métodos com nomes
+iguais. Este é o chamado "problema do losango" (_diamond problem_),
+ilustrado na <> e no <>, onde da hiearquia
+começa na classe base `Root` (raiz) e termina na classe `Leaf`
+(folha).footnote:[Adotamos a convenção dos computólogos
+e desenhamos árvores de cabeça para baixo: a raiz no topo, as folhas na base.]
+
+[[diamond_uml]]
+.Esquerda: Sequência de ativação para a chamada `leaf1.ping()`. Direita: Sequência de ativação para a chamada `leaf1.pong()`.
+image::../images/flpy_1401.png[align="center",pdfwidth=13cm]
+
+<<<
+
+[[ex_diamond]]
+.diamond.py: classes `Leaf`, `A`, `B`, `Root` formam o grafo na <>
+====
+[source, python]
+----
+include::../code/14-inheritance/diamond.py[tags=DIAMOND_CLASSES]
+----
+====
+<1> `Root` fornece `ping`, `pong`, e `+__repr__+` (para facilitar a leitura da saída).
+<2> Os métodos `ping` e `pong` na classe `A` chamam `super()`.
+<3> Apenas o método `ping` na classe `B` invoca `super()`.
+<4> A classe `Leaf` implementa apenas `ping`, e invoca `super()`.
+
+Vejamos agora o efeito da invocação dos métodos `ping` e `pong` em uma instância
+de `Leaf` (<>).
+
+[[ex_diamond_demo]]
+.Doctests para chamadas a `ping` e `pong` em um objeto `Leaf`
+====
+[source, python]
+----
+include::../code/14-inheritance/diamond.py[tags=DIAMOND_CALLS]
+----
+====
+<1> `leaf1` é uma instância de `Leaf`.
+
+<2> Chamar `leaf1.ping()` ativa os métodos `ping` em `Leaf`, `A`, `B`, e `Root`,
+porque os métodos `ping` nas três primeiras classes chamam `super().ping()`.
+
+<3> Chamar `leaf1.pong()` ativa `pong` em `A` através da herança, que por sua
+vez invoca `super.pong()`, ativando `B.pong`.
+
+As sequências de ativação que aparecem no <> e na
+<> são determinadas por dois fatores:
+
+* A ordem de resolução de métodos da classe `Leaf`.
+* O uso de `super()` em cada método.
+
+A ordem de resolução de métodos é conhecida pela sigla MRO (_Method Resolution
+Order_). Em Python, todas as classes têm um atributo chamado `+__mro__+`, que
+armazena uma tupla de referências a superclasses, na ordem de resolução dos
+métodos, indo desde a classe corrente até a classe `object`.footnote:[Classes
+também têm um método `.mro()`, mas este é um recurso avançado de programaçõa de
+metaclasses, mencionado na https://fpy.li/7s[«Seção 24.2»] (vol.3). Durante o uso normal de uma
+classe, apenas o conteúdo do atributo `+__mro__+` importa.]
+
+<<<
+Para a classe `Leaf`, o `+__mro__+` é o seguinte:
+
+[source, python]
+----
+include::../code/14-inheritance/diamond.py[tags=LEAF_MRO]
+----
+
+[NOTE]
+====
+
+Na <>, pode parecer que a MRO descreve uma
+https://fpy.li/6y[busca em largura], mas isso é apenas uma
+coincidência para esta hierarquia de classes simples. A MRO é computada
+por um algoritmo da literatura de computação, chamado C3.
+Seu uso no Python está detalhado no artigo
+https://fpy.li/14-10[_The Python 2.3 Method Resolution Order_] (A Ordem
+de Resolução de Métodos no Python 2.3), de Michele Simionato. É um texto
+difícil, mas Simionato escreve: "...a menos que você use herança
+múltipla intensivamente, e mantenha hierarquias não-triviais,
+não é necessário entender o algoritmo C3,
+e você pode facilmente ignorar este artigo."
+
+====
+
+A MRO determina apenas a ordem de ativação, mas se um método específico será ou
+não ativado em cada uma das classes vai depender de cada implementação chamar ou
+não `super()`.
+
+Considere o experimento com o método `pong`. A classe `Leaf` não sobrescreve
+aquele método, então a chamada `leaf1.pong()` ativa a implementação na próxima
+classe listada em `+Leaf.__mro__+`: a classe `A`. O método `A.pong` invoca
+`super().pong()`. A classe `B` class é e próxima na MRO, portanto `B.pong` é
+ativado. Mas aquele método não invoca `super().pong()`, então a sequência de
+ativação termina ali.
+
+Além do grafo de herança, a MRO também considera a ordem na qual as
+superclasses aparecem na declaração da uma subclasse. Considerando o programa
+_diamond.py_ (no <>), se a classe `Leaf` fosse declarada como `Leaf(B,
+A)`, daí a classe `B` apareceria antes de `A` em `+Leaf.__mro__+`. Isso afetaria
+a ordem de ativação dos métodos `ping`, e também faria `leaf1.pong()` ativar
+`B.pong` através da herança, mas `A.pong` e `Root.pong` não seriam invocados,
+porque `B.pong` não invoca `super()`.
+
+Quando um método invoca `super()`, ele é um método cooperativo. Métodos
+cooperativos permitem((("cooperative multiple inheritance"))) a herança
+múltipla cooperativa. Esses termos são intencionais: para funcionar, a herança
+múltipla no Python exige a cooperação ativa dos métodos envolvidos invocando
+`super()`. Na classe `B`, `ping` coopera, mas `pong` não.
+
+[WARNING]
+====
+
+Um método não-cooperativo pode ser a causa de bugs sutis. Muitos programadores,
+lendo o <>, poderiam esperar que, quando o método `A.pong` invoca
+`super.pong()`, isso acabaria por ativar `Root.pong`. Mas se `B.pong` for
+ativado antes, ele deixa a bola cair. Por isso, recomenda-se que um método
+subrescrito `m` de uma classe não-base invoque `super().m()`.
+
+====
+
+Métodos cooperativos devem ter assinaturas compatíveis, porque nunca se sabe se
+`A.ping` será chamado antes ou depois de `B.ping`. A sequência de ativação
+depende da ordem de `A` e `B` na declaração de cada subclasse que herda de
+ambos.
+
+Python é uma linguagem dinâmica, então a interação de `super()` com a MRO também
+é dinâmica. O <> mostra um resultado surpreendente desse
+comportamento dinâmico.
+
+[[ex_diamond2]]
+.diamond2.py: classes para demonstrar a natureza dinâmica de `super()`
+====
+[source, python]
+----
+include::../code/14-inheritance/diamond2.py[tags=DIAMOND_CLASSES]
+----
+====
+<1> A classe `A` vem de _diamond.py_ (no <>).
+<2> A classe `U` não tem relação com `A` ou `Root` do módulo `diamond`.
+<3> O que `super().ping()` faz? Resposta: depende. Continue lendo.
+<4> `LeafUA` é subclasse de `U` e `A`, nessa ordem.
+
+Se você criar uma instância de `U` e tentar chamar `ping`, ocorre um erro:
+
+[source, python]
+----
+include::../code/14-inheritance/diamond2.py[tags=UNRELATED_DEMO_1]
+----
+
+O objeto `'super'` devolvido por `super()` não tem um atributo `'ping'`, porque
+a MRO de `U` tem duas classes: `U` e `object`, e esta última não tem um atributo
+chamado `'ping'`.
+
+Entretanto, o método `U.ping` não é completamente inútil. Veja isso:
+
+[source, python]
+----
+include::../code/14-inheritance/diamond2.py[tags=UNRELATED_DEMO_2]
+----
+
+A chamada `super().ping()` em `LeafUA` ativa `U.ping`,
+que também coopera chamando `super().ping()`,
+ativando `A.ping` e, por fim, `Root.ping`.
+
+Observe que as clsses base de `LeafUA` são `(U, A)`, nesta ordem. Se em vez
+disso as bases fossem `(A, U)`, daí `leaf2.ping()` nunca chegaria a `U.ping`,
+porque o `super().ping()` em `A.ping` ativaria `Root.ping`, e esse último não
+invoca `super()`.
+
+Em um programa real, uma classe como `U` poderia ser uma classe _mixin_: uma
+classe projetada para ser usada ao lado outras classes em herança múltipla,
+fornecendo funcionalidade adicional. Vamos estudar _mixins_ na
+<>.
+
+Para((("UML class diagrams", "Tkinter Text widget class and superclasses")))
+concluir essa discussão sobre a MRO, a <> ilustra parte do
+complexo grafo de herança múltipla do toolkit de interface gráfica Tkinter, da
+biblioteca padrão de Python.
+
+[[tkwidgets_mro_uml]]
+.Esquerda: diagrama UML da classe e das superclasses do componente `Text` do Tkinter. Direita: O longo e sinuoso caminho de `+Text.__mro__+`, desenhado com as setas pontilhadas.
+image::../images/flpy_1402.png[UML do componente Text do Tkinter]
+
+Para estudar a figura, comece pela classe `Text`, na parte inferior. A classe
+`Text` implementa um componente de texto completo, editável e com múltiplas
+linhas. Ele sozinho fornece muita funcionalidade, mas também herda muitos
+métodos de outras classes. A imagem à esquerda mostra um diagrama de classe UML
+simples. À direita, a mesma imagem é decorada com setas mostrando a MRO, como
+listada no <> com a ajuda de uma função de conveniência
+`print_mro`.
+
+[[ex_tkinter_text_mro]]
+.MRO de `tkinter.Text`
+====
+[source, python]
+----
+>>> def print_mro(cls):
+...     print(', '.join(c.__name__ for c in cls.__mro__))
+>>> import tkinter
+>>> print_mro(tkinter.Text)
+Text, Widget, BaseWidget, Misc, Pack, Place, Grid, XView, YView, object
+----
+====
+
+Vamos agora falar sobre mixins.((("", startref="methres14")))((("", startref="mulinh14")))((("", startref="IASmultiple14")))
+
+
+[role="pagebreak-before less_space"]
+[[mixin_classes_sec]]
+=== Classes mixin
+
+Uma((("inheritance and subclassing", "mixin classes",
+id="IASmixin14")))((("mixin classes", id="mixin14"))) classe mixin é feita
+para ser herdada com pelo menos uma outra classe, em um arranjo de
+herança múltipla. Uma mixin não é feita para ser a única classe base de uma
+classe concreta, pois não fornece toda a funcionalidade para um objeto concreto,
+apenas adicionando ou customizando o comportamento de classes filhas ou irmãs.
+
+[NOTE]
+====
+
+Classes mixin são uma convenção sem qualquer suporte explícito no Python e no
+{cpp}. Ruby permite a definição explícita e o uso de módulos que funcionam como
+mixins—coleções de métodos que podem ser incluídas para adicionar funcionalidade
+a uma classe. C#, PHP, e Rust implementam traits (_traços_
+ou _aspectos_), que são também uma forma explícita de mixin.
+
+====
+
+Vamos ver um exemplo simples e útil de uma classe mixin.
+
+==== Mapeamentos maiúsculos
+
+O <> mostra a `UpperCaseMixin`, uma((("mappings",
+"case-insensitive", id="Mcase14"))) classe criada para fornecer acesso
+indiferente a maiúsculas/minúsculas para mapeamentos com chaves do tipo string,
+convertendo todas as chaves para maiúsculas quando elas são adicionadas ou
+consultadas.
+
+[[ex_uppermixin]]
+.uppermixin.py: `UpperCaseMixin` suporta mapeamentos indiferentes a maiúsculas/minúsculas
+====
+[source, python]
+----
+include::../code/14-inheritance/uppermixin.py[tags=UPPERCASE_MIXIN]
+----
+====
+
+<1> Esta função auxiliar recebe uma `key` de qualquer tipo e tenta devolver
+`key.upper()`; se isto falha, devolve a `key` inalterada.
+
+<2> A mixin implementa quatro métodos essenciais de mapeamentos, sempre chamando
+`super()` após tentar converter a chave em maiúsculas.
+
+Como todos os métodos de `UpperCaseMixin` chamam `super()`, esta mixin depende
+de uma classe irmã que implemente ou herde métodos com a mesma assinatura. Para
+dar sua contribuição, uma mixin normalmente precisa aparecer antes de outras
+classes na MRO de uma subclasse. Na prática, isto significa que mixins
+devem aparecer primeiro na tupla de classes base em uma declaração de classe.
+O <> apresenta dois exemplos.
+
+[[ex_upperdict]]
+.uppermixin.py: duas classes que usam `UpperCaseMixin`
+====
+[source, python]
+----
+include::../code/14-inheritance/uppermixin.py[tags=UPPERDICT]
+----
+====
+
+<1> `UpperDict` não precisa implementar nenhum método, mas
+`UpperCaseMixin` tem ser a primeira classe base,
+caso contrário os métodos chamados seriam os de `UserDict`.
+
+<2> `UpperCaseMixin` também funciona com `Counter`.
+
+<3> Em vez de `pass`, é melhor fornecer uma docstring para satisfazer
+a sintaxe da instrução `class`, que precisa ter um corpo.
+
+<<<
+Aqui estão alguns doctests de `UpperDict`, do módulo
+https://fpy.li/14-11[_uppermixin.py_]:
+
+[source, python]
+----
+include::../code/14-inheritance/uppermixin.py[tags=UPPERDICT_DEMO]
+----
+
+E uma rápida demonstração de `UpperCounter`:
+
+[source, python]
+----
+include::../code/14-inheritance/uppermixin.py[tags=UPPERCOUNTER_DEMO]
+----
+
+`UpperDict` e `UpperCounter` parecem quase mágicas, mas tive que estudar
+cuidadosamente o código de `UserDict` e `Counter` para fazer `UpperCaseMixin`
+trabalhar com eles.
+Por exemplo, minha primeira versão de `UpperCaseMixin` não incluía o método `get`.
+Aquela versão funcionava com `UserDict`, mas não com `Counter`.
+A classe `UserDict` herda `get` de `collections.abc.Mapping`, e aquele `get`
+invoca `+__getitem__+`, que implementei.
+Mas as chaves não eram transformadas em maiúsculas quando uma `UpperCounter` era
+carregada no `+__init__+`.
+Isso acontecia porque `+Counter.__init__+` usa `Counter.update`, que por sua vez
+recorre ao método `get` herdado de `dict`. Entretanto, o método `get` na classe
+`dict` não invoca `+__getitem__+`.
+
+Esta é a essência do problema discutido na https://fpy.li/88[«Seção 3.5.3»] (vol.1). É também
+uma clara demonstração da natureza frágil e quebradiça de programas que se
+apoiam no acoplamento forte da herança, mesmo nessa pequena escala.
+
+A próxima seção apresenta vários exemplos de herança múltipla, muitas vezes
+usando classes mixin.((("", startref="Mcase14")))((("",
+startref="mixin14")))((("", startref="IASmixin14")))
+
+[[multi_real_world_sec]]
+=== Herança múltipla no mundo real
+
+No((("inheritance and subclassing", "real-world examples of",
+id="IASreal14")))((("multiple inheritance", "real-world examples of",
+id="MIreal14"))) livro _Design Patterns_ (Padrões de Projetos),footnote:[Erich
+Gamma, Richard Helm, Ralph Johnson, e John Vlissides, _Padrões de Projetos:
+Soluções Reutilizáveis de Software Orientados a Objetos_ (Bookman).] quase todo
+o código está em {cpp}. O único exemplo de herança múltipla é o padrão
+_Adapter_ (Adaptador). Em Python a herança múltipla também não é regra, mas
+há exemplos importantes, que comentarei nessa seção.
+
+==== ABCs também são mixins
+
+Na((("collections.abc module", "multiple inheritance in")))((("mixin methods")))
+biblioteca padrão de Python, o uso mais visível de herança múltipla é o pacote
+`collections.abc`. Nenhuma controvérsia aqui:  afinal, até o Java suporta
+herança múltipla de interfaces, e ABCs são declarações de interface que podem,
+opcionalmente, fornecer implementações concretas de métodos.footnote:[Como já
+mencionado, o Java 8 permite que interfaces também forneçam implementações de
+métodos. Esse novo recurso é chamado https://fpy.li/14-12[_Default Methods_]
+(Métodos Default) no Tutorial oficial de Java.]
+
+A documentação oficial do pacote
+https://fpy.li/6z[`collections.abc`]
+chama de _mixin methods_ (métodos mixin) os métodos concretos
+implementados nas ABCs de coleções. As ABCs que oferecem métodos
+mixin cumprem dois papéis: elas são definições de interfaces e também classes
+mixin. Por exemplo, a
+https://fpy.li/14-14[«implementação»] de `collections.UserDict` aproveita
+vários métodos mixin fornecidos por `collections.abc.MutableMapping`.
+
+==== ThreadingMixIn e ForkingMixIn
+
+O pacote https://fpy.li/72[_http.server_]
+inclui((("ThreadingMixIn class")))((("ForkingMixIn")))((("HTTPServer class")))((("ThreadingHTTPServer class")))((("servers",
+"ThreadingHTTPServer class")))((("servers", "HTTPServer class")))
+as classes `HTTPServer` e `ThreadingHTTPServer`.
+Esta última foi adicionada ao Python 3.7.
+A documentação de `ThreadingHTTPServer` diz (nossa tradução):
+
+____
+Esta classe é idêntica a `HTTPServer`, mas trata requisições com threads, 
+usando a `ThreadingMixIn`. Isso é útil para lidar com navegadores web que abrem
+sockets prematuramente, situação na qual o `HTTPServer` esperaria indefinidamente.
+____
+
+As duas linhas abaixo são o
+https://fpy.li/14-16[«código-fonte completo»]
+da classe `ThreadingHTTPServer` no Python 3.10:
+
+[source, python]
+----
+class ThreadingHTTPServer(socketserver.ThreadingMixIn, HTTPServer):
+    daemon_threads = True
+----
+
+O https://fpy.li/14-17[«código-fonte»] de `socketserver.ThreadingMixIn` tem 38
+linhas, incluindo os comentários e as docstrings.
+O <> apresenta um resumo de sua implementação.
+
+[[ex_threadmixin]]
+.Parte de _Lib/socketserver.py_ no Python 3.10
+====
+[source, python]
+----
+class ThreadingMixIn:
+    """Mixin class to handle each request in a new thread."""
+
+    # 8 linhas omitidas aqui
+
+    def process_request_thread(self, request, client_address):  # <1>
+        ... # 6 linhas omitidas aqui
+
+    def process_request(self, request, client_address):  # <2>
+        ... # 8 linhas omitidas aqui
+
+    def server_close(self):  # <3>
+        super().server_close()
+        self._threads.join()
+----
+====
+
+<1> `process_request_thread` não invoca `super()` porque é um método novo,
+não sobrescreve um método herdado. Sua implementação invoca três métodos de
+instância que `HTTPServer` implementa ou herda.
+
+<2> Isto sobrescreve o método `process_request`, que `HTTPServer` herda de
+`socketserver.BaseServer`, iniciando uma thread e delegando o trabalho real
+para a `process_request_thread` que roda naquela thread. O método não invoca
+`super()`.
+
+<3> `server_close` invoca `super().server_close()` para parar de receber
+requisições, e então espera que as threads iniciadas por `process_request`
+terminem sua execução.
+
+A documentação do módulo https://fpy.li/73[`socketserver`]
+apresenta a `ThreadingMixIn` e a `ForkingMixIn`.
+Esta última classe foi projetada para
+suportar servidores concorrentes baseados em
+https://fpy.li/74[`os.fork()`],
+uma API para iniciar processos filhos, disponível em sistemas derivados do
+Unix, compatíveis com a norma https://fpy.li/7c[«POSIX»].
+
+
+
+[[django_cbv_sec]]
+==== Mixins de views genéricas no Django
+
+[NOTE]
+====
+
+Não é necessário conhecer Django para acompanhar essa seção. Uso uma pequena
+parte do framework como um exemplo prático de herança múltipla, e tentarei
+fornecer todo o pano de fundo necessário (supondo que você tenha alguma
+experiência com desenvolvimento Web no lado servidor, com qualquer linguagem ou
+framework).
+
+====
+
+No((("Django generic views mixins", id="Django14"))) Django, uma view é um
+objeto invocável que recebe um argumento `request`—um objeto representando uma
+requisição HTTP—e devolve um objeto representando uma resposta HTTP. Nosso
+interesse  aqui são as diferentes respostas. Elas podem ser tão simples quanto
+um redirecionamento, sem nenhum conteúdo em seu corpo, ou tão complexas quanto
+uma página de catálogo de uma loja online, renderizada a partir de um template
+HTML que exibe múltiplas mercadorias, com botões de compra e links para páginas
+com detalhes.
+
+Originalmente, o Django oferecia uma série de funções, chamadas _generic views_ (views genéricas),
+que implementavam alguns casos de uso comuns. Por exemplo, muitos sites precisam
+exibir resultados de busca que incluem dados de vários itens, com listagens
+ocupando múltiplas páginas, cada resultado contendo também  um link para uma
+página de informações detalhadas sobre aquele item. No Django, uma view de lista
+e uma view de detalhes são feitas para funcionarem juntas, resolvendo esse
+problema: uma view de lista renderiza resultados de busca, e uma view de
+detalhes produz uma página para cada item individual.
+
+Entretanto, as views genéricas originais eram funções, então não eram
+extensíveis. Se quiséssemos algo similar mas não exatamente igual a uma
+view de lista genérica, era  preciso começar do zero.
+
+O conceito de views baseadas em classes foi introduzido no Django 1.3,
+juntamente com um conjunto de classes de views genéricas divididas em classes
+base, mixins e classes concretas prontas para o uso. No Django 3.2, as classes
+base e as mixins estão no módulo `base` do pacote `django.views.generic`,
+ilustrado((("UML class diagrams", "django.views.generic.base module"))) na
+<>. No topo do diagrama vemos duas classes que se
+encarregam de responsabilidades muito diferentes: `View` e
+`TemplateResponseMixin`.
+
+[role="width-80"]
+[[django_view_base_uml]]
+.Diagrama de classes UML do módulo `django.views.generic.base`.
+image::../images/flpy_1403.png[align="center",pdfwidth=8cm]
+
+[TIP]
+====
+
+Um ótimo recurso para estudar essas classes é o site
+https://fpy.li/14-21[_Classy Class-Based Views_], onde você pode navegar
+facilmente pelo diagrama das classes, ver todos os métodos em cada classe
+(métodos herdados, sobrescritos e adicionados), consultar sua documentação e
+estudar seu
+https://fpy.li/14-22[«código-fonte no GitHub»].
+
+====
+
+`View` é a classe base de todas as views (ela poderia ser uma ABC), e oferece
+funcionalidade essencial como o método `dispatch`, que delega para métodos de
+tratamento de requisições (_request handling_) como `get`, `head`, `post`, etc.,
+implementados por subclasses concretas para tratar os diversos verbos
+HTTP.footnote:[Os programadores Django sabem que o método de classe `as_view` é
+a parte mais visível da interface `View`, mas isso não é relevante para nós
+aqui.] A classe `RedirectView` herda de `View` e 
+implementa `get`, `head`, `post`, etc.
+
+Espera-se que as subclasses concretas de `View` implementem os métodos de
+tratamento, então por que aqueles métodos não são parte da interface de `View`?
+A razão: subclasses são livres para implementar apenas os métodos de tratamento
+que querem suportar. Uma `TemplateView` é usada apenas para exibir conteúdo,
+então ela implementa apenas `get`. Se uma requisição HTTP `POST` é enviada para
+uma `TemplateView`, o método herdado `View.dispatch` verifica que não há um
+método de tratamento para `post`, e produz uma resposta HTTP `405 Method Not
+Allowed`.footnote:[Se você gosta de padrões de projetos, note que o mecanismo de
+despacho do Django é uma variação dinâmica do padrão
+https://fpy.li/75[_Template Method_] (Método Template).
+Ele é dinâmico porque a classe `View` não obriga
+subclasses a implementarem todos os métodos de tratamento, mas `dispatch`
+verifica, durante a execução, se um método de tratamento concreto está
+disponível para cada requisição específica.]
+
+A `TemplateResponseMixin` fornece funcionalidade que interessa apenas às views
+que precisam usar um template. Uma `RedirectView`, por exemplo, não tem
+conteúdo, então não precisa de um template e não herda dessa mixin.
+`TemplateResponseMixin` fornece comportamentos para `TemplateView`
+e outras views que renderizam templates, tal como `ListView`, `DetailView`,
+etc., definidas nos subpacotes de `django.views.generic`. A
+<> mostra o módulo `django.views.generic.list`((("UML
+class diagrams", "django.views.generic.list module"))) e parte do módulo `base`.
+
+[[django_view_list_uml]]
+.Diagrama de classe UML do o módulo `django.views.generic.list`. Aqui as três classes do módulo `base` aparecem recolhidas (veja a <>). A classe `ListView` não tem métodos ou atributos: é uma classe agregada.
+image::../images/flpy_1404.png[align="center",pdfwidth=12cm]
+
+Para usuários do Django, a classe mais importante na <> é
+`ListView`, uma classe agregada sem qualquer código (seu corpo é apenas uma
+docstring). Quando instanciada, uma `ListView` tem um atributo de instância
+`object_list`, através do qual o código do template pode iterar para montar o
+conteúdo da página, normalmente o resultado de uma consulta a um banco de dados,
+composto de múltiplos objetos. Toda a funcionalidade relacionada com a geração
+deste iterável de objetos vem da `MultipleObjectMixin`. Esta mixin também
+oferece uma lógica complexa de paginação—para exibir parte dos resultados em uma
+página e links para mais páginas.
+
+<<<
+Suponha que você queira criar uma view que não vai renderizar um template, mas
+sim produzir uma lista de objetos em formato JSON. Para isso existe
+`BaseListView`. Ela oferece um ponto inicial de extensão fácil de usar, unindo a
+funcionalidade de `View` e de `MultipleObjectMixin`, mas sem a complexidade do
+mecanismo de templates.
+
+A API de views baseadas em classes do Django é um exemplo melhor de herança
+múltipla que o Tkinter. É mais fácil entender suas classes mixin: cada
+uma tem um propósito bem definido, e seus nomes terminam com o sufixo
+`…Mixin`.
+
+Views baseadas em classes não são universalmente aceitas por usuários do Django.
+Muitos as usam de forma limitada, como caixas opacas. Mas quando é necessário
+criar algo novo, muitos programadores Django continuam criando funções
+monolíticas de views, para abarcar todas aquelas responsabilidades, ao invés de
+tentar reutilizar as views base e as mixins.
+
+Demora um certo tempo para aprender a usar as views baseadas em classes e a
+forma de estendê-las para suprir as necessidades específicas de uma aplicação,
+mas considero que vale a pena estudá-las. Elas eliminam muito código repetitivo,
+facilitam o reuso de soluções, e melhoram até a comunicação das
+equipes—por exemplo, pela definição de nomes padronizados para os templates e
+para as variáveis passadas para contextos de templates. Views baseadas em
+classes são views do Django "on rails"footnote:[NT: Literalmente "nos trilhos",
+mas claramente uma referência ao popular framework _Ruby on Rails_].((("", startref="Django14")))
+
+
+==== Herança múltipla no Tkinter
+
+Um((("Tkinter GUI toolkit", "multiple inheritance in", id="tinkter14"))) exemplo
+extremo de herança múltipla na biblioteca padrão de Python é o
+toolkit de interface gráfica 
+https://fpy.li/76[«Tkinter»]. 
+No momento em que escrevo essa seção, o Tkinter já tem 25 anos de idade. Não
+é um exemplo das melhores práticas atuais. Mas mostra como a herança múltipla
+era usada quando os programadores ainda não conheciam suas desvantagens. E vai
+nos servir de contra-exemplo, quando tratarmos de algumas boas práticas, na
+próxima seção.
+
+Usei parte da
+hierarquia de componentes do Tkinter para ilustrar a MRO na
+<>. A <> mostra todas as classes de componentes
+no pacote base `tkinter` (há mais componentes gráficos no subpacote
+https://fpy.li/77[`tkinter.ttk`]).
+
+[[tkinter_uml]]
+.Diagrama de classes resumido da hierarquia de classes de interface gráfica do Tkinter; classes marcadas com «mixin» existem para oferecer metodos concretos a outras classes, por herança múltipla.
+image::../images/flpy_1405.png[Diagrama de classes UML dos componentes do Tkinter]
+
+Considere as seguintes classes na <>:
+
+`① Toplevel`: A classe de uma janela principal em um aplicação Tkinter.
+
+`② Widget`: A superclasse de todos os objetos visíveis que podem ser colocados em uma janela.
+
+`③ Button`: Um componente de botão simples.
+
+`④ Entry`: Um campo de texto editável de uma única linha.
+
+`⑤ Text`: Um campo de texto editável de múltiplas linhas.
+
+<<<
+Aqui estão as MROs dessas classes, como exibidas pela função `print_mro` do <>:
+
+[source, python]
+----
+>>> import tkinter
+>>> print_mro(tkinter.Toplevel)
+Toplevel, BaseWidget, Misc, Wm, object
+>>> print_mro(tkinter.Widget)
+Widget, BaseWidget, Misc, Pack, Place, Grid, object
+>>> print_mro(tkinter.Button)
+Button, Widget, BaseWidget, Misc, Pack, Place, Grid, object
+>>> print_mro(tkinter.Entry)
+Entry, Widget, BaseWidget, Misc, Pack, Place, Grid, XView, object
+>>> print_mro(tkinter.Text)
+Text, Widget, BaseWidget, Misc, Pack, Place, Grid, XView, YView, object
+----
+
+[NOTE]
+====
+
+Pelos padrões atuais, a hierarquia de classes do Tkinter é profunda demais.
+Poucas partes da bilbioteca padrão de Python tem mais que três ou quatro níveis
+de classes concretas, e o mesmo pode ser dito da biblioteca de classes de Java.
+Entretanto, é interessante observar que algumas das hierarquias mais profundas
+da biblioteca de classes de Java são precisamente os pacotes relacionados à
+programação de interfaces gráficas:
+https://fpy.li/14-26[`java.awt`] e
+https://fpy.li/14-27[`javax.swing`].
+O https://fpy.li/14-28[«Squeak»], 
+uma versão moderna e aberta de Smalltalk, inclui o poderoso e inovador toolkit
+de interface gráfica Morphic, também com uma hierarquia de classes profunda. Na
+minha experiência, é nos toolkits de interface gráfica que a herança é mais
+útil.
+
+====
+
+Observe como essas classes se relacionam com outras:
+
+* `Toplevel` é a única classe gráfica que não herda de `Widget`, porque ela é a
+janela primária e não se comporta como um componente; por exemplo, ela não pode
+ser fixada a uma janela ou moldura (_frame_). `Toplevel` herda de `Wm`, que
+fornece funções de acesso direto ao gerenciador de janelas do ambiente gráfico
+do sistema operacional, para tarefas como definir o título da janela e
+configurar suas bordas.
+
+<<<
+* `Widget` herda diretamente de `BaseWidget` e de `Pack`, `Place`, e `Grid`. As
+últimas três classes são gerenciadores de geometria: são responsáveis por
+organizar componentes dentro de uma janela ou moldura. Cada uma delas encapsula
+uma estratégia de layout e uma API de colocação de componentes diferente.
+
+* `Button`, como a maioria dos componentes, descende diretamente apenas de
+`Widget`, mas indiretamente de `Misc`, que fornece dezenas de métodos para todos
+os componentes.
+
+* `Entry` é subclasse de `Widget` e `XView`, que suporta rolagem horizontal.
+
+* `Text` é subclasse de `Widget`, `XView` e `YView` (para rolagem vertical).
+
+Vamos agora discutir algumas boas práticas de herança múltipla e examinar
+como o Tkinter se comporta.((("", startref="tinkter14")))((("",
+startref="IASreal14")))((("", startref="MIreal14")))
+
+
+[role="pagebreak-before less_space"]
+=== Lidando com a herança
+
+Aquilo((("inheritance and subclassing", "best practices", id="IAScop14"))) que
+Alan Kay escreveu na epígrafe continua sendo verdade: ainda não existe um teoria
+geral sobre herança que guie os programadores. O que temos são regras
+gerais, padrões de projetos, "melhores práticas", acrônimos perspicazes, tabus,
+etc. Alguns desses nos dão orientações úteis, mas nenhum deles é universalmente
+aceito ou sempre aplicável.
+
+É fácil criar projetos frágeis e incompreensíveis usando herança, mesmo sem
+herança múltipla. Como não temos uma teoria abrangente, aqui estão algumas dicas
+para evitar diagramas de classes parecidos com um prato de espaguete.
+
+[[favor_composition_sec]]
+==== Prefira a composição de objetos à herança de classes
+
+O título desta seção é o segundo princípio do design orientado a objetos, do
+livro _Padrões de Projetos_,footnote:[Esse princípio aparece na página 20 da
+introdução, na edição em inglês.] e é o melhor conselho que posso oferecer aqui.
+Uma vez que você se sinta confortável com a herança, é fácil usá-la em excesso.
+Colocar objetos em uma hierarquia elegante apela para nosso senso de ordem;
+programadores fazem isso por pura diversão.
+
+Preferir a composição leva a designs mais flexíveis. Por exemplo, no caso da
+classe `tkinter.Widget`, em vez de herdar os métodos de todos os gerenciadores
+de geometria, instâncias do componente poderiam manter uma referência para um
+gerenciador de geometria, e invocar seus métodos. Afinal, um `Widget` não
+deveria "ser" um gerenciador de geometria, mas poderia usar os serviços de um
+deles por delegação. E daí você poderia adicionar um novo gerenciador de
+geometria sem afetar a hierarquia de classes do componente e sem se preocupar
+com colisões de nomes.
+
+Mesmo com herança simples, este princípio aumenta a
+flexibilidade, porque a subclasses são uma forma de acoplamento forte, e árvores
+de herança muito altas tendem a ser quebradiças.
+
+A composição e a delegação podem substituir o uso de mixins para tornar
+comportamentos disponíveis para diferentes classes, mas não podem substituir o
+uso de herança de interfaces para definir uma hierarquia de tipos.
+
+==== Entenda o motivo de usar herança em cada caso
+
+Ao lidarmos com herança múltipla, é útil ter claras as razões pelas quais
+subclasses são criadas em cada caso específico. As principais razões são:
+
+* Herança de interface cria um subtipo, implicando em uma relação _é-um_.
+A melhor forma de fazer isso é usando ABCs.
+
+* Herança de implementação evita duplicação de código pela reutilização.
+Mixins podem ajudar nisso.
+
+Na prática, frequentemente as duas razões coexistem, mas quando você puder
+tornar a intenção clara, faça isso. Herança para reutilização de código é um
+detalhe de implementação, e muitas vezes pode ser substituída por composição e
+delegação. Por outro lado, herança de interfaces é o fundamento de qualquer
+framework. Idealmente, a herança de interfaces deveria usar apenas ABCs como
+classes base.
+
+==== Torne a interface explícita com ABCs
+
+No Python moderno, se uma classe tem por objetivo definir uma interface, ela
+deveria ser explicitamente uma ABC ou uma subclasse de `typing.Protocol`. Uma
+ABC deveria ser subclasse apenas de `abc.ABC` ou de outras ABCs. A herança
+múltipla de ABCs não é problemática.
+
+==== Use mixins explícitas para reutilizar código
+
+Se uma classe é projetada para fornecer implementações de métodos para
+reutilização por múltiplas subclasses não relacionadas, sem implicar em uma
+relação do tipo _é-uma_, ele deveria ser uma classe mixin explícita. No Python,
+não há uma maneira formal de declarar uma classe como mixin. Por isso, recomendo
+que seus nomes incluam o sufixo `Mixin`. Conceitualmente, uma mixin não define
+um novo tipo; ela simplesmente empacota métodos para reutilização. Uma mixin não
+deveria nunca ser instanciada, e classes concretas não devem herdar apenas de
+uma mixin. Cada mixin deveria fornecer um único comportamento específico,
+implementando poucos métodos intimamente relacionados. Mixins devem evitar
+manter qualquer estado interno; isto é, uma classe mixin não deve ter atributos
+de instância.
+
+
+[[aggregate_class_sec]]
+==== Ofereça classes agregadas aos usuários
+
+[quote, Grady Booch et al., Object-Oriented Analysis and Design with Applications]
+____
+
+Uma classe construída principalmente herdando de mixins, sem adicionar estrutura
+ou comportamento próprios, é chamada de _classe agregada_.footnote:[Grady Booch
+et al., "Object-Oriented Analysis and Design with Applications" (_Análise e
+Projeto Orientados a Objetos, com Aplicações_), 3ª ed. (Addison-Wesley),  p.
+109.]
+
+____
+
+Se alguma combinação de ABCs ou mixins for especialmente útil para o código cliente, ofereça uma classe que una essas funcionalidades de uma forma sensata.
+
+Por exemplo, aqui está o https://fpy.li/14-29[«código-fonte»] completo
+da classe `ListView` do Django, do canto inferior direito da <>:
+
+[source, python]
+----
+class ListView(MultipleObjectTemplateResponseMixin, BaseListView):
+    """
+    Render some list of objects, set by `self.model` or `self.queryset`.
+    `self.queryset` can actually be any iterable of items, not just a
+    queryset.
+    """
+----
+
+O corpo de `ListView` é vaziofootnote:[NT: a doctring diz "Renderiza alguma
+lista de objetos, definida por `self.model` ou `self.queryset`. `self.queryset`
+na pode ser qualquer iterável de itens, não apenas um queryset."], mas a
+classe fornece um serviço útil: ela une uma mixin e uma classe base que devem
+ser usadas em conjunto.
+
+<<<
+Outro exemplo é https://fpy.li/14-30[`tkinter.Widget`], que tem quatro classes
+base e nenhum método ou atributo próprios—apenas uma docstring. Graças à classe
+agregada `Widget`, podemos criar um novo componente com as mixins necessárias,
+sem precisar descobrir em que ordem elas devem ser declaradas para funcionarem
+como desejado.
+
+Note que classes agregadas não precisam ser inteiramente vazias (mas
+frequentemente são).
+
+
+==== Só crie subclasses de classes feitas para serem herdadas
+
+Em um comentário sobre esse capítulo, o revisor técnico Leonardo Rochael sugeriu
+o alerta abaixo.
+
+[WARNING]
+====
+
+Criar subclasses e sobrescrever métodos de qualquer classe complexa é um
+processo muito suscetível a erros, porque os métodos da superclasse podem
+ignorar inesperadamante métodos sobrescritos na subclasse. Sempre que
+possível, evite sobrescrever métodos, ou pelo menos limite-se a criar
+subclasses de classes projetadas para serem facilmente estendidas, e apenas
+daquelas formas pelas quais a classe foi desenhada para ser estendida.
+
+====
+
+É um ótimo conselho, mas como descobrimos se uma classe foi projetada para ser
+estendida?
+
+A primeira resposta é a documentação (algumas vezes na forma de docstrings ou
+até de comentários no código). Por exemplo, o pacote
+https://fpy.li/78[`socketserver`] de Python é descrito como "um framework
+para servidores de rede". Sua classe
+https://fpy.li/79[`BaseServer`] foi
+projetada para a criação de subclasses, como o próprio nome sugere. E mais
+importante, a documentação e a
+https://fpy.li/14-33[«docstring no código-fonte da classe»]
+informa explicitamente quais de seus métodos foram
+criados para serem sobrescritos por subclasses.
+
+No Python ≥ 3.8 uma nova forma de tornar tais restrições de projeto explícitas
+foi oferecida pela
+https://fpy.li/pep591[_PEP 591—Adding a final qualifier to
+typing_] (Acrescentando um qualificador "final" à tipagem).
+A PEP introduz um decorador
+https://fpy.li/7a[`@final`], que pode ser aplicado a classes ou a
+métodos individuais, para que IDEs ou checadores de tipos possam detectar
+tentativas de criar subclasses de classes ou de sobrescrever métodos
+que não foram projetados para serem herdadas ou sobrescritos.footnote:[A
+PEP 591 também introduz uma anotação https://fpy.li/7d[`Final`] para
+variáveis e atributos que não devem ser reatribuídos ou sobrescritos.]
+
+
+==== Evite criar subclasses de classes concretas
+
+Criar subclasses de classes concretas é mais perigoso que criar subclasses de
+ABCs e mixins, pois instâncias de classes concretas normalmente têm um estado
+interno, que pode ser corrompido quando sobrescrevemos métodos que
+interferem naquele estado. Mesmo se nossos métodos cooperarem chamando `super()`,
+e o estado interno seja protegido através da sintaxe `__x`, restarão ainda
+inúmeras formas pelas quais sobrescrever um método pode introduzir bugs.
+
+No texto <> (<>), Alex Martelli cita _More Effective {cpp}_, de Scott
+Meyer, que diz: "toda classe não-final (não-folha) deveria ser abstrata". Em
+outras palavras, Meyer recomenda que subclasses deveriam ser criadas apenas a
+partir de classes abstratas.
+
+Se você precisar usar subclasses para reutilização de código, então o código a
+ser reutilizado deve estar em métodos mixin de ABCs, ou em classes mixin
+explicitamente nomeadas.
+
+Vamos agora analisar o Tkinter do ponto de vista destas recomendações.
+
+==== Tkinter: o bom, o mau e o feio
+
+A maioria dos conselhos da seção anterior não são seguidos pelo Tkinter, com a
+notável exceção de oferecer classes agregadas (<>). E
+mesmo assim, este não é um grande exemplo, pois a composição provavelmente
+funcionaria melhor para integrar os gerenciadores de geometria a `Widget`, como
+discutido na <>.
+
+Mas((("Tkinter GUI toolkit", "benefits and drawbacks of"))) lembre-se que o
+Tkinter é parte da biblioteca padrão desde o Python 1.1, lançado em 1994. O
+Tkinter é uma fachada em Python para o toolkit de GUI Tk, escrito na linguagem
+Tcl. O combo Tcl/Tk não é, na origem, orientado a objetos, então a API Tk é
+basicamente um imenso catálogo de funções. Entretanto, o toolkit é
+conceitualmente orientado a objetos, apesar de não usar classes na implementação
+original em Tcl.
+
+A docstring de `tkinter.Widget` começa com as palavras "Internal class" (Classe
+interna). Isto sugere que `Widget` deveria provavelmente ser uma ABC. Apesar da
+classe `Widget` não ter métodos próprios, ela define uma interface. Sua mensagem
+é: "Você pode contar que todos os componentes do Tkinter vão oferecer os métodos
+básicos de componente (`+__init__+`, `destroy`, e dezenas de funções da API Tk),
+além dos métodos de todos os três gerenciadores de geometria". Vamos combinar
+que essa não é uma boa definição de interface (é abrangente demais), mas ainda
+assim é uma interface, e `Widget` a "define" como a união das interfaces de suas
+superclasses.
+
+A classe `Tk`, que encapsula a lógica da aplicação gráfica, herda de `Wm` e
+`Misc`, nenhuma das quais é abstrata ou mixin (`Wm` não é uma mixin típica,
+porque `TopLevel` é subclasse apenas dela). O nome da classe `Misc` é, por sí
+só, um mau sinal. `Misc` tem mais de 100 métodos, e todos os componentes herdam
+dela. Por que é necessário que cada um dos componentes tenham métodos para
+tratamento do clipboard, seleção de texto, gerenciamento de timer e coisas
+assim? Não é possível colar algo em um botão ou selecionar texto de uma barra de
+rolagem. `Misc` deveria ser dividida em várias classes mixin especializadas, e
+nem todos os componentes deveriam herdar de todas aquelas mixins.
+
+Para ser justo, como usuário do Tkinter você não precisa, de forma alguma,
+entender ou usar herança múltipla. Ela é um detalhe de implementação, oculto
+atrás das classes de componentes que serão instanciadas ou usadas como base para
+subclasses em seu código. Mas você sofrerá as consequências da herança múltipla
+excessiva quando digitar `dir(tkinter.Button)` e tentar encontrar um método
+específico em meio aos 214 atributos listados. E terá que enfrentar a
+complexidade, caso decida implementar um novo componente Tk.
+
+[TIP]
+====
+
+Apesar de ter problemas, o Tkinter é estável, flexível, e fornece um visual
+moderno se você usar o pacote `tkinter.ttk` e seus componentes tematizados. Além
+disso, alguns dos componentes originais, como `Canvas` e `Text`, são
+incrivelmente poderosos. Em poucas horas é possível transformar um objeto
+`Canvas` em uma aplicação de desenho razoavelmente completa. Se você se
+interessa pela programação de interfaces gráficas, com certeza vale a pena
+estudar o Tkinter e o Tcl/Tk.
+
+====
+
+Aqui termina nossa viagem através do labirinto da herança.((("", startref="IAScop14")))
+
+[[inheritance_summary]]
+=== Resumo do capítulo
+
+Este((("inheritance and subclassing", "overview of"))) capítulo começou com uma
+revisão da função `super()` no contexto de herança simples. Daí discutimos o
+problema da criação de subclasses de tipos embutidos: seus métodos nativos,
+implementados em C, não invocam os métodos sobrescritos em subclasses, exceto em
+uns poucos casos especiais. É por isso que, quando precisamos de tipos `list`,
+`dict`, ou `str` customizados, é mais fácil criar subclasses de `UserList`,
+`UserDict`, ou `UserString (todos definidos no módulo
+https://fpy.li/2w[`collections`]), que encapsulam os tipos embutidos
+correspondentes e delegam operações para aqueles—três exemplos a favor da
+composição sobre a herança na biblioteca padrão. Se o comportamento desejado for
+muito diferente daquilo que os tipos embutidos oferecem, pode ser mais fácil
+criar uma subclasse da ABC apropriada em
+https://fpy.li/6z[`collections.abc`],
+e escrever sua própria implementação.
+
+O restante do capítulo foi dedicado à faca de dois gumes da herança múltipla.
+Primeiro vimos como a ordem de resolução de métodos, definida no atributo de
+classe `+__mro__+`, trata o problema de conflitos potenciais de nomes em métodos
+herdados. Também examinamos como a função embutida `super()` se comporta em
+hierarquias com herança múltipla, e como ela às vezes tem um comportamento
+surpreendente. O comportamento de `super()` foi projetado para suportar classes
+mixin, que estudamos usando o exemplo simples de `UpperCaseMixin` (para
+mapeamentos indiferentes a maiúsculas/minúsculas).
+
+Exploramos como a herança múltipla e os métodos mixin são usados nas ABCs de
+Python, bem como na construção de servidores HTTP com mixins baseados em threads
+e forks de `socketserver`. Usos mais complexos de herança múltipla foram
+exemplificados com as views baseadas em classes do Django e com o toolkit de
+interface gráfica Tkinter. Apesar do Tkinter não ser um exemplo das melhores
+práticas modernas, é um exemplo de hierarquias de classe complexas que podemos
+encontrar em sistemas legados.
+
+Encerrando o capítulo, apresentamos sete recomendações para lidar com herança,
+e aplicamos alguns daqueles conselhos em um comentário sobre a hierarquia de
+classes do Tkinter.
+
+Rejeitar a herança—mesmo a herança simples—é uma tendência moderna. Go é uma das
+mais bem sucedidas linguagens criadas no século 21. Ela não inclui um elemento
+chamado "classe", mas você pode construir tipos que são estruturas (_structs_)
+de campos encapsulados, e associar métodos a essas estruturas. Em Go é possível
+definir interfaces, que são checadas pelo compilador usando tipagem estrutural,
+também conhecida como((("static duck typing"))) tipagem pato estática—algo
+muito similar ao que temos com os tipos protocolo desde o Python 3.8. Essa
+linguagem também tem uma sintaxe especial para a criação de tipos e interfaces
+por composição, mas não há suporte a herança—nem entre interfaces.
+
+Então talvez o melhor conselho sobre herança seja: evite-a se puder. Mas,
+frequentemente, não temos essa opção: os frameworks que usamos nos impõe suas
+escolhas de design.
+
+[[inheritance_further_reading]]
+=== Para saber mais
+
+[quote, Hynek Schlawack, "Subclassing in Python Redux"]
+____
+
+No que diz respeito à legibilidade, composição feita adequadamente é
+superior a herança. Como é mais frequente ler o código que escrevê-lo, como
+regra geral evite subclasses, mas em especial não misture os vários tipos de
+herança e não crie subclasses para compartilhar código.
+
+____
+
+Durante((("inheritance and subclassing", "further reading on"))) a revisão final
+desse livro, o revisor técnico Jürgen Gmach recomendou o post
+https://fpy.li/14-37[_Subclassing in Python Redux_]
+(O ressurgimento das subclasses em Python), de Hynek Schlawack—a fonte da citação acima.
+Schlawack
+é o autor do popular pacote _attrs_, e foi um dos principais contribuidores do
+framework de programação assíncrona Twisted, um projeto criado por Glyph
+Lefkowitz em 2002. De acordo com Schlawack, após algum tempo os desenvolvedores
+perceberam que haviam criado subclasses em excesso no projeto. O post é longo, e
+cita outros posts e palestras importantes. Muito recomendado.
+
+Naquela mesma conclusão, Hynek Schlawack escreve: "Não esqueça que, na maioria
+dos casos, tudo o que você precisa é de uma função." Concordo, e é precisamente
+por essa razão que _Python Fluente_ trata em detalhes das funções, antes de
+falar de classes e herança. Meu objetivo foi mostrar o quanto você pode alcançar
+com funções se valendo das classes na biblioteca padrão, antes de criar suas
+próprias classes.
+
+<<<
+A criação de subclasses de tipos embutidos, a função `super`, e recursos
+avançados como descritores e metaclasses, foram todos introduzidos no artigo
+https://fpy.li/descr101[_Unifying types and classes in Python 2.2_]
+(Unificando tipos e classes em Python 2.2), de Guido van Rossum. Desde então, nada
+realmente importante mudou nesses recursos. Python 2.2 foi uma proeza fantástica
+de evolução da linguagem, adicionando vários novos recursos poderosos em um todo
+coerente, sem quebrar a compatibilidade com versões anteriores. Os novos recursos
+eram 100% opcionais. Para usá-los, bastava programar explicitamente uma
+subclasse de direta ou indirta de `object`, para criar uma assim chamada
+_new-style class_ (classe no novo estilo) . No Python 3,
+todas as classes são subclasses de `object`.
+
+O _Python Cookbook_, 3ª ed., de David Beazley e Brian K. Jones (O'Reilly) inclui
+várias receitas mostrando o uso de `super()` e de classes mixin. Você pode
+começar pela esclarecedora seção
+https://fpy.li/14-38[_8.7. Calling a Method on a Parent Class_]
+(Invocando um método em uma superclasse), e seguir as
+referências internas a partir dali.
+
+O post https://fpy.li/14-39[_Python's super() considered super!_]
+(O _super() de Python é mesmo super!)], de Raymond Hettinger, explica o funcionamento de
+`super` e a herança múltipla de uma perspectiva positiva. Ele foi escrito em
+resposta a 
+https://fpy.li/14-40[_Python's Super is nifty, but you can't use it (Previously: Python's Super Considered Harmful)_]
+(O super de Python é bacana, mas você não deve usá-lo (Antes: super de Python considerado nocivo)), de James
+Knight. A resposta de Martijn Pieters a
+https://fpy.li/14-41[_How to use super() with one argument?_]
+(Como usar super() com um só argumento?)
+inclui uma explicação concisa e aprofundada de `super`, incluindo sua relação
+com descritores, um conceito que estudaremos apenas no https://fpy.li/23[«Capítulo 23»] (vol.3). 
+Assim é `super`: simples de usar nos casos básicos, mas também
+uma ferramenta poderosa e complexa, que alcança alguns dos recursos dinâmicos
+mais avançados de Python, raramente encontrados em outras linguagens.
+
+Apesar dos títulos daqueles posts, o problema não é exatamente a função
+embutida `super`—que no Python 3 ficou mais fácil de usar do que era no Python 2.
+A questão real é a herança múltipla, algo inerentemente complicado e traiçoeiro.
+Michele Simionato vai além da crítica, e de fato oferece uma solução em seu
+https://fpy.li/14-42[_Setting Multiple Inheritance Straight_]
+(Colocando a herança múltipla em seu devido lugar):
+ele implementa _traits_ ("traços"), uma forma explícita de mixin originada na linguagem Self.
+Simionato escreveu, em seu blog, uma longa série de posts sobre herança múltipla em Python, incluindo
+https://fpy.li/14-43[_The wonders of cooperative inheritance, or using super in Python 3_]
+(As maravilhas da herança cooperativa, ou usando super em Python 3);
+https://fpy.li/14-44[_Mixins considered harmful, part 1_]
+(Mixins consideradas nocivas, parte 1) e
+https://fpy.li/14-45[_part 2_];
+e https://fpy.li/14-46[_Things to Know About Python Super, part 1_]
+(O que você precisa saber sobre o super de Python),
+https://fpy.li/14-47[_part 2_], e
+https://fpy.li/14-48[_part 3_].
+Os posts mais antigos usam a sintaxe de `super` de Python 2, mas ainda são relevantes.
+
+Li a primeira edição do _Object-Oriented Analysis and Design_, 3ª ed., de
+Grady Booch et al., e o recomendo fortemente como uma introdução geral ao
+pensamento orientado a objetos, independente da linguagem de programação. É um
+dos raros livros que trata da herança múltipla sem preconceitos.
+
+Hoje, mais que nunca, aconselha-se evitar a herança. Então cá estão duas
+referências sobre como fazer isso. Brandon Rhodes escreveu
+https://fpy.li/14-49[_The Composition Over Inheritance Principle_]
+(O princípio da composição antes da herança), parte de seu excelente guia
+https://fpy.li/14-50[_Python Design Patterns_]
+(Padrões de Projetos no Python). Augie Fackler e Nathaniel Manista apresentaram
+https://fpy.li/14-51[_The End Of Object Inheritance & The Beginning Of A New Modularity_]
+(O Fim da Herança de Objetos & O Início de Uma Nova Modularidade)
+na PyCon 2013. Fackler e Manista falam sobre organizar sistemas em torno de
+interfaces e das funções que lidam com os objetos que implementam aquelas
+interfaces, evitando o acoplamento forte e os pontos de falha de classes e da
+herança. É o modo de pensar da comunidade Go, aplicado ao Python.
+
+.Soapbox
+****
+
+**Pense nas classes realmente necessárias**
+
+[quote, Alan Kay, The Early History of Smalltalk ("Os Primórdios de Smalltalk")]
+____
+
+[...] começamos a defender a ideia de herança como uma maneira de permitir que
+iniciantes pudessem construir [algo] a partir de frameworks que só poderiam ser
+projetadas por especialistasfootnote:[Alan Kay, _The Early History of Smalltalk_
+(Os Promórdios de Smalltalk), na SIGPLAN Not. 28, 3 (março de 1993),
+69–95. Também disponível https://fpy.li/14-1[online]. Agradeço a meu
+amigo Cristiano Anderson, que compartilhou esta referência quando eu estava
+escrevendo esse capítulo)].
+
+____
+
+A((("inheritance and subclassing", "Soapbox discussion",
+id="IASsoap14")))((("Soapbox sidebars", "multilevel class hierarchies"))) imensa
+maioria dos programadores escreve aplicações, não frameworks. Mesmo aqueles que
+escrevem frameworks provavelmente passam boa parte de seu tempo
+escrevendo aplicações. Quando escrevemos aplicações, normalmente não precisamos
+criar hierarquias de classes. No máximo escrevemos classes que são subclasses de
+ABCs ou de outras classes oferecidas pelo framework. Como desenvolvedores de
+aplicações, é muito raro precisarmos escrever uma classe que funcionará como
+superclasse de outra. Quase sempre as classes que escrevemos são classes
+folha: classes concretas sem subclasses.
+
+Se, trabalhando como desenvolvedor de aplicações, você se pegar criando
+hierarquias de classe de múltiplos níveis, aposto que está vivendo uma destas
+situações:
+
+* Você está reinventando a roda. Procure um framework ou biblioteca que forneça
+componentes você possa reutilizar em sua aplicação.
+
+* Você está usando um framework mal projetado. Procure uma alternativa.
+
+* Você está complicando demais. Lembre-se((("KISS principle"))) do
+_Princípio KISS_.
+
+* Você ficou entediado programando aplicações e decidiu criar um novo framework.
+Parabéns e boa sorte!
+
+Também é possível que todas as alternativas acima descrevam situação:
+você ficou entediado e decidiu reinventar a roda, escrevendo seu próprio
+framework mal projetado e excessivamente complexo, e está sendo forçado a
+programar classe após classe para resolver problemas triviais. Espero que você
+esteja se divertindo, ou pelo menos que esteja sendo pago para fazer isso.
+
+**Tipos embutidos mal-comportados: bug ou _feature_?**
+
+Os((("Soapbox sidebars", "trade-offs of built-ins"))) tipos embutidos `dict`,
+`list`, e `str` são blocos básicos essenciais do próprio Python, então precisam
+ser rápidos—qualquer problema de desempenho ali teria severos impactos em
+praticamente todo o resto. É por isso que o CPython adotou atalhos que fazem com
+que muitos métodos embutidos escritos em C se comportem mal, ao não cooperarem
+com os métodos sobrescritos por subclasses em Python. 
+
+Uma solução para este dilema seria oferecer duas implementações para cada um
+desses tipos: uma "interno", otimizada para uso pelo interpretador, e uma externa,
+facilmente extensível. 
+
+Mas veja só, isso nós já temos: `UserDict`, `UserList`, e `UserString` não são
+tão rápidos quanto seus equivalentes embutidos, mas são fáceis de estender. A
+abordagem pragmática tomada pelo CPython significa que também podemos usar, em
+nossas próprias aplicações, as implementações altamente otimizadas mas difíceis
+estender. E isso faz sentido, considerando que não é tão frequente precisarmos
+de um mapeamento, uma lista ou uma string customizados, mas usamos `dict`,
+`list`, e `str` diariamente. Só precisamos estar cientes dos compromissos
+envolvidos.
+
+
+**Herança através das linguagens**
+
+Alan Kay((("Soapbox sidebars", "inheritance across languages"))) criou o termo
+"orientado a objetos", e Smalltalk tinha apenas herança simples, apesar de
+existirem versões com diferentes formas de suporte a herança múltipla, incluindo
+os dialetos modernos de Smalltalk, Squeak e Pharo, que suportam _traits_
+("traços")—um dispositivo de linguagem que pode substituir classes mixin, mas
+evita alguns dos problemas da herança múltipla.
+
+A primeira linguagem popular a implementar herança múltipla foi o {cpp}, e esse
+recurso foi abusado o suficiente para que o Java—criado para ser um substituto
+do {cpp}—fosse projetado sem suporte a herança múltipla de implementação (isto
+é, sem classes mixin). Quer dizer, isso até o Java 8 (e Kotlin) permitir métodos
+default, que que aproximam suas interfaces do conceito de classes abstratas
+que temps em Python e {cpp}.
+
+Outras linguagens que suportam _traits_ são versões recentes de PHP e Groovy,
+bem como Rus, Scala, t e Raku—a linguagem antes conhecida como Perl 6.footnote:[Meu amigo
+e revisor técnico Leonardo Rochael explica isso melhor do que eu poderia: "A
+existência continuada junto com o persistente adiamento da chegada do Perl 6
+estava drenando a força de vontade da evolução do próprio Perl. Agora o Perl
+continua a ser desenvolvido como uma linguagem separada (está na versão 5.34),
+sem a ameaça de ser descontinuada pela linguagem antes conhecida como Perl 6."]
+Então podemos dizer que _traits_ estão na moda em 2021.
+
+Ruby traz uma perspectiva original para a herança múltipla: não a suporta, mas
+introduz mixins como um recurso explícito da linguagem. Uma classe Ruby pode
+incluir um módulo em seu corpo, e aí os métodos definidos no módulo se tornam
+parte da implementação da classe. Essa é uma forma "pura" de mixin, sem herança
+envolvida, e está claro que uma mixin em Ruby não influencia o tipo da classe
+onde que a utiliza. Isto oferece os benefícios das mixins, evitando muitos de
+seus problemas mais comuns.
+
+<<<
+Duas novas linguagens orientadas a objetos que estão recebendo muita atenção
+limitam severamente a herança: Go e Julia. Ambas giram em torno de programar
+"objetos" implementando "métodos", e suportam https://fpy.li/7b[«polimorfismo»],
+mas evitam o termo "classe".
+
+Go não tem nenhum tipo de herança, mas oferece sintaxe para facilitar a
+composição em suas interfaces e structs. Julia tem uma hierarquia de tipos, mas
+subtipos não podem herdar estrutura, só comportamentos, e só é permitido
+criar subtipos de tipos abstratos. Além disso, os métodos de Julia são
+implementados com despacho múltiplo—uma forma mais avançada do mecanismo de
+despacho único que vimos na <>.((("",
+startref="IASsoap14")))
+
+****
diff --git a/vol2/cap15.adoc b/vol2/cap15.adoc
new file mode 100644
index 0000000..417178a
--- /dev/null
+++ b/vol2/cap15.adoc
@@ -0,0 +1,2027 @@
+[[ch_more_types]]
+== Mais dicas de tipo
+:example-number: 0
+:figure-number: 0
+
+[quote, Guido van Rossum, um fã do Monty Python]
+____
+
+Aprendi uma dura lição: para programas pequenos, a tipagem dinâmica é ótima.
+Para programas grandes precisamos de uma abordagem mais disciplinada. E ajuda se
+a linguagem der a você aquela disciplina, ao invés de dizer "Bem, faça o que
+quiser".footnote:[De um vídeo no YouTube da _A Language Creators' Conversation:
+Guido van Rossum, James Gosling, Larry Wall & Anders Hejlsberg_ (Uma Conversa
+entre Criadores de Linguagens: Guido van Rossum, James Gosling, Larry Wall &
+Anders Hejlsberg), transmitido em 2 de abril de 2019. A citação (editada por
+brevidade) começa em https://fpy.li/15-1[1:32:05]. Produzi e publiquei
+a transcrição completa em https://github.com/fluentpython/language-creators.]
+
+____
+
+
+Este((("gradual type system", "topics covered"))) capítulo é uma continuação do
+https://fpy.li/8[«Capítulo 8»] (vol.1), e fala mais sobre o sistema de tipagem gradual de Python.
+Os tópicos principais são:
+
+* Assinaturas de funções sobrecarregadas
+* `typing.TypedDict`: dando dicas de tipos para `dicts` usados como registros
+* Coerção de tipo
+* Acesso a dicas de tipo durante a execução
+* Tipos genéricos
+** Declarando uma classe genérica
+** Variância: tipos invariantes, covariantes e contravariantes
+** Protocolos estáticos genéricos
+
+=== Novidades neste capítulo
+
+Este((("gradual type system", "significant changes to"))) capítulo é
+inteiramente novo, escrito para essa segunda edição de _Python Fluente_.
+Vamos começar com sobrecargas.
+
+[[overload_sec]]
+=== Assinaturas sobrecarregadas
+
+No Python, as funções((("gradual type system", "overloaded signatures",
+id="GTSoverload15")))((("overloaded signatures",
+id="overlaodsig15")))((("@typing.overload decorator", id="attyping15")))
+podem aceitar diferentes combinações de argumentos.
+
+O decorador `@typing.overload` permite anotar tais combinações. Isto é
+particularmente importante quando o tipo devolvido pela função depende do tipo
+de dois ou mais parâmetros.
+
+Considere a função embutida `sum`. Esse é o texto de `help(sum)`, traduzido:
+
+[source, python]
+----
+>>> help(sum)
+sum(iterable, /, start=0)
+    Devolve a soma de um valor 'start' (default: 0) mais a soma dos
+    números de um iterável
+
+    Quando o iterável é vazio, devolve o valor inicial ('start').
+    Esta função é direcionada especificamente para uso com valores
+    numéricos e pode rejeitar tipos não-numéricos.
+----
+
+A função embutida `sum` é escrita em C, mas o _typeshed_ tem dicas de tipos
+sobrecarregadas para ela, em https://fpy.li/15-2[_builtins.pyi_]:
+
+[source, python]
+----
+@overload
+def sum(__iterable: Iterable[_T]) -> Union[_T, int]: ...
+@overload
+def sum(__iterable: Iterable[_T], start: _S) -> Union[_T, _S]: ...
+----
+
+Primeiro, vamos olhar a sintaxe geral das sobrecargas.
+Esse acima é todo o código sobre `sum` que encontrei no arquivo stub (_.pyi_).
+A implementação fica em um arquivo diferente.
+As reticências (`\...`) não tem qualquer função além de cumprir a exigência
+sintática para um corpo de função, em vez usar de `pass`.
+Assim os arquivos _.pyi_ são arquivos Python válidos.
+Como mencionado na https://fpy.li/7t[«Seção 8.6»] (vol.1), os dois sublinhados prefixando
+`+__iterable+` são a convenção da PEP 484 para argumentos apenas posicionais,
+que é checada pelo Mypy. Isso significa que você pode invocar `sum(my_list)`,
+mas não `sum(__iterable = my_list)`.
+
+O checador de tipos tenta fazer a correspondência entre os argumentos dados com
+cada assinatura sobrecarregada, em ordem. A chamada `sum(range(100), 1000)` não
+casa com a primeira sobrecarga, pois aquela assinatura tem apenas um parâmetro.
+Mas casa com a segunda.
+
+Você pode também usar `@overload` em um modulo Python (_.py_) normal, colocando
+as assinaturas sobrecarregadas logo antes da assinatura real da função e de sua
+implementação. O <> mostra como a função `sum` apareceria anotada e
+implementada em um módulo Python.
+
+[[sum_overload_ex]]
+._mysum.py_: definição da função `sum` com assinaturaas sobrecarregadas
+====
+[source, python]
+----
+include::../code/15-more-types/mysum.py[]
+----
+====
+<1> Precisamos deste segundo `TypeVar` na segunda assinatura.
+
+<2> Essa assinatura é para o caso simples: `sum(my_iterable)`. O tipo do
+resultado pode ser `T`—o tipo dos elementos que `my_iterable` produz—ou pode ser
+`int`, se o iterável for vazio, pois o valor default do parâmetro `start` é `0`.
+
+<3> Quando `start` é dado, ele pode ser de qualquer tipo `S`, então o tipo do
+resultado é `Union[T, S]`. É por isso que precisamos de `S`. Se `T` fosse
+reutilizado aqui, então o tipo de `start` teria que ser do mesmo tipo dos
+elementos de `Iterable[T]`.
+
+<4> A assinatura da implementação real da função não tem dicas de tipo.
+
+São muitas linhas para anotar uma função de uma única linha.
+Sinto muito, mas pelo menos a função do exemplo não é `foo`.
+
+Se quiser aprender sobre `@overload` lendo código, o _typeshed_ tem centenas de
+exemplos. Quando escrevo esse capítulo, o https://fpy.li/15-3[arquivo stub] do
+_typeshed_ para as funções embutidas de Python tem 186 sobrecargas—mais que
+qualquer outro na biblioteca padrão.
+
+.Aproveite a tipagem gradual
+[TIP]
+====
+
+Tentar produzir código 100% anotado pode levar a dicas de tipo que acrescentam
+muito ruído e pouco valor agregado. Refatoração para simplificar as dicas de
+tipo pode levar a APIs inconvenientes para quem vai usar.
+Algumas vezes é melhor ser pragmático, e deixar
+parte do código sem dicas de tipo.
+
+====
+
+As APIs convenientes e práticas que consideramos pythônicas são muitas vezes
+difíceis de anotar. Na próxima seção veremos um exemplo: são necessárias seis
+sobrecargas para anotar adequadamente a função embutida `max`,
+que é muito flexível nos parâmetros que aceita.
+
+
+[[max_overload_sec]]
+==== Sobrecarga máxima
+
+É((("max() function", id="maxfunc15")))((("functions", "max() function")))
+difícil acrescentar dicas de tipo a funções que usam os poderosos recursos
+dinâmicos de Python.
+
+Quando estudava o typeshed, encontrei o relatório de bug
+https://fpy.li/shed4051[#4051]: Mypy não avisou que é proibido passar `None` como
+um dos argumentos para a função embutida `max()`, ou passar um iterável que em
+algum momento produz `None`. Nos dois casos, você recebe uma exceção como a
+seguinte durante a execução:
+
+----
+TypeError: '>' not supported between instances of 'int' and 'NoneType'
+----
+
+Tradução: '>' não é suportado entre instâncias de 'int' e 'NoneType'.
+
+A documentação de `max` começa com a seguinte sentença:
+
+[quote]
+____
+Devolve o maior item em um iterável ou o maior de dois ou mais argumentos.
+____
+
+Para mim, essa é uma descrição bastante intuitiva.
+
+Mas se eu for anotar uma função descrita nesses termos, tenho que decidir
+como declarar "um iterável" ou "dois ou mais argumentos".
+A realidade é ainda mais complicada, porque `max` também pode receber dois argumentos
+opcionais: `key` e `default`.
+
+Escrevi `max` em Python para evidenciar a relação entre o
+funcionamento da função e as anotações sobrecarregadas (a função embutida
+original é escrita em C); veja o <>.
+
+[[mymax_ex]]
+._mymax.py_: Versão da função `max` em Python
+====
+[source, python]
+----
+# imports and definitions omitted, see next listing
+
+MISSING = object()
+EMPTY_MSG = 'max() arg is an empty sequence'
+
+# overloaded type hints omitted, see next listing
+
+include::../code/15-more-types/protocol/mymax/mymax.py[tags=MYMAX]
+----
+====
+
+O foco deste exemplo não é a lógica de `max`, então não vou explicar a
+implementação, exceto para falar sobre `MISSING`. A constante `MISSING` é uma
+instância única de `object`, usada como sentinela. É o valor default para o
+argumento nomeado `default=`. A escolha de `MISSING` em vez de `None` como
+valor default para o argumento nomeado `default` permite que a função `max` 
+detecte estas duas situações diferentes:
+
+. O usuário passou `None` como argumento `default`.
+. O usuário não passou o argumento `default`
+(neste caso seu valor fica sendo `MISSING`).
+
+Quando `first` é um iterável vazio...
+
+. Se o usuário não forneceu um argumento para `default=`, então ele é `MISSING`, e `max` gera um `ValueError`.
+. Se usuário forneceu um valor para `default=`, incluindo `None`, e então `max` devolve o valor de `default`.
+
+Para consertar o https://fpy.li/shed4051[issue #4051], escrevi o código no
+<>.footnote:[Agradeço a Jelle Zijlstra—um mantenedor do
+_typeshed_—que me ensinou várias coisas, incluindo como reduzir minhas nove
+sobrecargas originais para "apenas" seis.]
+
+[[mymax_types_ex]]
+._mymax.py_: início do módulo, com importações, definições e sobrecargas
+====
+[source, python]
+----
+include::../code/15-more-types/protocol/mymax/mymax.py[tags=MYMAX_TYPES]
+----
+====
+
+Minha implementação de `max` em Python tem mais ou menos o mesmo tamanho
+daquelas importações e declarações de tipo. Graças à tipagem pato, meu código
+não tem nenhuma checagem usando `isinstance`, e fornece a mesma checagem de erro
+daquelas dicas de tipo—mas apenas durante a execução, claro.
+
+Uma vantagem importante de `@overload` é declarar o tipo devolvido da forma
+mais precisa possível, de acordo com os tipos dos argumentos recebidos. Veremos
+este vantagem a seguir, estudando as sobrecargas de `max`, em grupos de duas ou
+três por vez.
+
+===== Argumentos implementando `SupportsLessThan`, sem `key` ou `default`
+
+[source, python]
+----
+@overload
+def max(__arg1: LT, __arg2: LT, *_args: LT, key: None = ...) -> LT:
+    ...
+# ... lines omitted ...
+@overload
+def max(__iterable: Iterable[LT], *, key: None = ...) -> LT:
+    ...
+----
+
+Nestes casos, as entradas são ou argumentos separados do tipo `LT` que
+implementam `SupportsLessThan`, ou um `Iterable` de itens desse tipo. O tipo
+devolvido por `max` é do mesmo tipo dos argumentos ou itens reais, como vimos na
+https://fpy.li/7w[«Seção 8.5.9.2»] (vol.1).
+
+Amostras de chamadas que casam com essas sobrecargas:
+
+[source, python]
+----
+max(1, 2, -3)  # returns 2
+max(['Go', 'Python', 'Rust'])  # returns 'Rust'
+----
+
+===== Argumento `key` fornecido, mas `default` não
+
+[source, python]
+----
+@overload
+def max(__arg1: T, __arg2: T, *_args: T, key: Callable[[T], LT]) -> T:
+    ...
+# ... lines omitted ...
+@overload
+def max(__iterable: Iterable[T], *, key: Callable[[T], LT]) -> T:
+    ...
+----
+
+As entradas podem ser item separados de qualquer tipo `T` ou um único
+`Iterable[T]`, e `key=` deve ser um invocável que recebe um argumento do mesmo
+tipo `T`, e devolve um valor que implementa `SupportsLessThan`. O tipo devolvido
+por `max` é o mesmo dos argumentos reais.
+
+Amostras de chamadas que casam com essas sobrecargas:
+
+[source, python]
+----
+max(1, 2, -3, key=abs)  # returns -3
+max(['Go', 'Python', 'Rust'], key=len)  # returns 'Python'
+----
+
+===== Argumento `default` fornecido, `key` não
+
+[source, python]
+----
+@overload
+def max(__iterable: Iterable[LT], *, key: None = ...,
+        default: DT) -> Union[LT, DT]:
+    ...
+----
+
+A entrada é um iterável de itens do tipo `LT` que implemente `SupportsLessThan`.
+O argumento `default=` é o valor devolvido quando `Iterable` é vazio.
+Assim, o tipo devolvido por `max` deve ser uma `Union` do tipo `LT` e
+do tipo do argumento `default`.
+
+Amostras de chamadas que casam com essas sobrecargas:
+
+[source, python]
+----
+max([1, 2, -3], default=0)  # returns 2
+max([], default=None)  # returns None
+----
+
+
+===== Argumentos `key` e `default` fornecidos
+
+[source, python]
+----
+@overload
+def max(__iterable: Iterable[T], *, key: Callable[[T], LT],
+        default: DT) -> Union[T, DT]:
+    ...
+----
+
+As entradas são:
+
+* Um `Iterable` de itens de qualquer tipo `T`
+* Invocável que recebe um argumento do tipo `T`
+e devolve um valor do tipo `LT`, que implementa `SupportsLessThan`
+* Um valor default de qualquer tipo `DT`
+
+O tipo devolvido por `max` deve ser uma `Union` do tipo `T` e do tipo do
+argumento `default`:
+
+
+[source, python]
+----
+max([1, 2, -3], key=abs, default=None)  # returns -3
+max([], key=abs, default=None)  # returns None
+----
+
+
+
+==== Lições da sobrecarga de max
+
+
+Dicas de tipo permitem ao Mypy marcar uma chamada como `max([None, None])` com
+essa mensagem de erro:
+
+----
+mymax_demo.py:109: error: Value of type variable "_LT" of "max"
+  cannot be "None"
+----
+
+Por outro lado, escrever tantas linhas para suportar o checador de tipos
+pode desencorajar a criação de funções convenientes e flexíveis como `max`. Se
+eu precisasse reinventar também a função `min`, poderia refatorar e reutilizar a
+maior parte da implementação de `max`. Mas teria que copiar e colar todas as
+declarações de sobrecarga—apesar delas serem idênticas para `min`, exceto pelo
+nome da função.
+
+<<<
+Meu amigo João S. O. Bueno—um dos desenvolvedores Python mais talentosos que
+conheço—escreveu o seguinte https://fpy.li/15-4[tweet]:
+
+____
+
+Apesar de ser difícil expressar a assinatura de `max`—ela se encaixa muito
+facilmente em nossa estrutura mental. Considero a expressividade das marcas de
+anotação muito limitadas, se comparadas à de Python.
+
+____
+
+Vamos agora examinar o elemento de tipagem `TypedDict`.
+Ele não é tão útil quanto imaginei inicialmente, mas tem seus usos.
+Experimentar com `TypedDict` demonstra as limitações da tipagem estática para lidar com estruturas dinâmicas, como dados em formato JSON.((("", startref="maxfunc15")))((("", startref="overlaodsig15")))((("", startref="attyping15")))((("", startref="GTSoverload15")))
+
+
+[[typeddict_sec]]
+=== TypedDict
+
+[WARNING]
+====
+
+É((("gradual type system", "TypedDict", id="GTStypeddict15")))((("TypedDict",
+id="typeddict15"))) tentador usar `TypedDict` para se proteger contra erros ao
+tratar estruturas de dados dinâmicas como as respostas da API JSON. Mas os
+exemplos aqui deixam claro que o tratamento correto de JSON precisa acontecer
+durante a execução, e não com checagem estática de tipo. Para checar estruturas
+similares a JSON usando dicas de tipo durante a execução, dê uma olhada no
+pacote https://fpy.li/15-5[_pydantic_] no PyPI.
+
+====
+
+Algumas vezes os dicionários de Python são usados como registros, as chaves
+interpretadas como nomes de campos e os valores como valores dos campos de
+diferentes tipos. Considere, por exemplo, um registro descrevendo um livro, em
+JSON ou Python:
+
+
+
+[source, javascript]
+----
+{"isbn": "0134757599",
+ "title": "Refactoring, 2e",
+ "authors": ["Martin Fowler", "Kent Beck"],
+ "pagecount": 478}
+----
+
+Antes de Python 3.8, não havia uma boa maneira de anotar um registro como esse,
+pois os tipos de mapeamento que vimos na https://fpy.li/8c[«Seção 8.5.6»] (vol.1) limitam os valores
+a um mesmo tipo.
+
+<<<
+Aqui estão duas tentativas ruins de anotar um registro como o objeto JSON acima:
+
+`dict[str, Any]`::
+As chaves são `str` mas os valores podem ser de qualquer tipo.
+
+`dict[str, str|int|list[str]]`::
+Difícil de ler, e não preserva a relação entre os nomes dos campos e seus
+respectivos tipos: `title` deve ser uma `str`, ele não pode ser um `int` ou uma
+`List[str]`.
+
+A https://fpy.li/pep589[_PEP 589—TypedDict: Type Hints for Dictionaries with a
+Fixed Set of Keys_] (TypedDict: dicas de tipo para dicionários com um conjunto
+fixo de chaves) resolve este problema. O <> mostra um
+`TypedDict` simples.
+
+[[bookdict_ex]]
+._books.py_: a definição de `BookDict`
+====
+[source, python]
+----
+include::../code/15-more-types/typeddict/books.py[tags=BOOKDICT]
+----
+====
+
+À primeira vista, `typing.TypedDict` pode parecer uma fábrica de classes de
+dados, similar a `typing.NamedTuple`—tratada no https://fpy.li/5[«Capítulo 5»] (vol.1).
+
+A similaridade sintática é enganosa. `TypedDict` é muito diferente. Ele existe
+apenas para orientar um checador de tipos, e não tem qualquer efeito
+durante a execução.
+
+`TypedDict` fornece duas coisas:
+
+* Uma sintaxe similar à de classe para anotar um `dict` com dicas de tipo para
+os valores de cada campo identificado por um chaves.
+* Um construtor que informa que o checador de tipos
+deve esperar um `dict` com chaves e valores como especificados.
+
+Durante a execução, um construtor de `TypedDict` como `BookDict` é um placebo:
+ele tem o mesmo efeito de uma chamada ao construtor de `dict` com os mesmos
+argumentos.
+
+O fato de `BookDict` criar um `dict` simples também significa que:
+
+* Os "campos" na definição da pseudoclasse não criam atributos de instância.
+* Não é possível escrever inicializadores com valores default para os "campos".
+* Não é permitido definir métodos.
+
+Vamos explorar o comportamento de um `BookDict` durante a execução (no
+<>).
+
+[[bookdict_first_use_ex]]
+.Usando um `BookDict`, mas não exatamente como planejado
+====
+[source, python]
+----
+>>> from books import BookDict
+>>> pp = BookDict(title='Programming Pearls',  # <1>
+...               authors='Jon Bentley',  # <2>
+...               isbn='0201657880',
+...               pagecount=256)
+>>> pp  # <3>
+{'title': 'Programming Pearls', 'authors': 'Jon Bentley', 'isbn': '0201657880',
+ 'pagecount': 256}
+>>> type(pp)
+
+>>> pp.title  # <4>
+Traceback (most recent call last):
+  File "", line 1, in 
+AttributeError: 'dict' object has no attribute 'title'
+>>> pp['title']
+'Programming Pearls'
+>>> BookDict.__annotations__  # <5>
+{'isbn': , 'title': , 'authors': typing.List[str],
+ 'pagecount': }
+----
+====
+<1> É possível invocar `BookDict` como um construtor de `dict`, com argumentos nomeados, ou passando um argumento `dict`—incluindo um literal `dict`.
+<2> Ops... esqueci que `authors` deve ser uma lista. Mas não há checagem de tipos estáticos durante a execução.
+<3> O resultado da chamada a `BookDict` é um `dict` simples...
+<4> ...assim não é possível ler os campos usando a notação `objeto.campo`.
+<5> As dicas de tipo estão em `+BookDict.__annotations__+`, e não em `pp`.
+
+Sem um checador de tipos, `TypedDict` é tão útil quanto comentários em um programa:
+pode ajudar a documentar o código, mas só isso.
+As fábricas de classes do https://fpy.li/5[«Capítulo 5»] (vol.1), por outro lado,
+são úteis mesmo se você não usar um checador de tipos,
+porque durante a execução elas geram uma classe customizada que pode ser instanciada.
+Elas também fornecem vários métodos ou funções úteis,
+listadas na https://fpy.li/8v[«Seção 5.2.1»] (vol.1).
+
+O <> cria um `BookDict` válido e tenta executar algumas operações com ele.
+A seguir, o <> mostra  como `TypedDict` permite que o Mypy encontre erros.
+
+[[bookdict_demo_ex]]
+._demo_books.py_: operações legais e ilegais em um `BookDict`
+====
+[source, python]
+----
+include::../code/15-more-types/typeddict/demo_books.py[]
+----
+====
+<1> Lembre-se de adicionar o tipo devolvido, assim o Mypy não ignora a função.
+
+<2> Este é um `BookDict` válido: todas as chaves estão presentes, com valores do
+tipo correto.
+
+<3> O Mypy vai inferir o tipo de `authors` a partir da anotação na chave
+`'authors'` em `BookDict`.
+
+
+<4> `typing.TYPE_CHECKING` só é `True` quando os tipos no programa estão sendo
+checados. Durante a execução ele é sempre falso.
+
+<5> O `if` anterior evita que `reveal_type(authors)` seja chamado durante a
+execução. `reveal_type` não é uma função de Python disponível durante a
+execução, mas sim um instrumento de depuração fornecido pelo Mypy. Por isso não
+há um `import` para ela. Veja sua saída no <>.
+
+<6> As últimas três linhas da função `demo` são ilegais. Elas vão disparar
+mensagens de erro no <>.
+
+Verificando a tipagem em _demo_books.py_, do <>, obtemos o
+<>.
+
+[[bookdict_demo_check]]
+.Verificando os tipos em _demo_books.py_
+====
+[source]
+----
+…/typeddict/ $ mypy demo_books.py
+demo_books.py:13: note: Revealed type is
+                  'built-ins.list[built-ins.str]'  <1>
+demo_books.py:14: error: Incompatible types in assignment
+                  (expression has type "str",
+                  variable has type "List[str]")  <2>
+demo_books.py:15: error: TypedDict "BookDict" has no key 'weight'  <3>
+demo_books.py:16: error: Key 'title' of TypedDict "BookDict"
+                  cannot be deleted  <4>
+Found 3 errors in 1 file (checked 1 source file)
+----
+====
+<1> Esta observação é o resultado de `reveal_type(authors)`.
+
+<2> O tipo da variável `authors` foi inferido a partir do tipo da expressão que
+a inicializou, `book['authors']`. Você não pode atribuir uma `str` para uma
+variável do tipo `List[str]`. Checadores de tipo em geral não permitem que o
+tipo de uma variável mude.footnote:[Em maio de 2020, o pytype ainda permite
+isso. Mas seu https://fpy.li/15-6[FAQ] diz que tal operação será proibida no
+futuro. Veja a pergunta _Why didn't pytype catch that I changed the type of an
+annotated variable?_ (Por que o pytype não avisou quando eu mudei o tipo de uma
+variável anotada?) no https://fpy.li/15-6[FAQ] do pytype.]
+
+<3> Não é permitido atribuir a uma chave que não é parte da definição de
+`BookDict`.
+
+<4> Não se pode apagar uma chave que é parte da definição de `BookDict`.
+
+Vejamos agora `BookDict` sendo usado em assinaturas de função, para checar o
+tipo em chamadas de função.
+
+Imagine que você precisa gerar XML a partir de registros de livros como esse:
+
+[source, xml]
+----
+
+  0134757599
+  Refactoring, 2e
+  Martin Fowler
+  Kent Beck
+  478
+
+----
+
+Se você estivesse escrevendo o código em MicroPython, para ser integrado a um
+pequeno microcontrolador, poderia escrever uma função parecida com o
+<>.footnote:[Prefiro usar o pacote https://fpy.li/15-8[lxml] para
+gerar e interpretar XML: ele é fácil de começar a usar, completo e rápido.
+Infelizmente, nem o lxml nem o
+https://fpy.li/7f[_ElementTree_]
+do próprio Python cabem na RAM limitada de meu microcontrolador hipotético.]
+
+[[to_xml_ex]]
+._books.py_: a função `to_xml`
+====
+[source, python]
+----
+include::../code/15-more-types/typeddict/books.py[tags=TOXML]
+----
+====
+<1> O principal objetivo do exemplo: usar `BookDict` em uma assinatura de função.
+<2> Se a coleção começa vazia, o Mypy não tem como inferir o tipo dos elementos.
+Por isso a anotação de tipo é necessária aqui.footnote:[A documentação do Mypy discute isso na seção
+https://fpy.li/15-11[_Types of empty collections_] (Tipos de coleções vazias) da página
+https://fpy.li/15-10[_Common issues and solutions_] (Problemas comuns e soluções).]
+
+<3> O Mypy entende testes com `isinstance`, e trata `value` como uma `list`
+neste bloco.
+
+<4> Quando usei `key == 'authors'` como condição do `if` que guarda este bloco,
+o Mypy encontrou um erro nessa linha: `"object" has no attribute "++__iter__++"`
+(_"object" não tem um atributo "+__iter__+"_ ), porque inferiu o tipo de `value`
+devolvido por `book.items()` como `object`, que não suporta o método
+`+__iter__+` exigido pela expressão geradora. O teste com `isinstance` funciona
+porque garante que `value` é uma `list` neste bloco.
+
+O <> mostra uma função que interpreta uma `str` JSON e devolve
+um `BookDict`.
+
+[[from_json_any_ex]]
+.books_any.py: a função `from_json`
+====
+[source, python]
+----
+include::../code/15-more-types/typeddict/books_any.py[tags=FROMJSON]
+----
+====
+
+<1> O tipo devolvido por `json.loads()` é `Any`.footnote:[Brett Cannon, Guido
+van Rossum e outros vem discutindo como escrever dicas de tipo para
+`json.loads()` desde 2016, em https://fpy.li/15-12[Mypy issue #182: Define a
+JSON type (_Definir um tipo JSON_)].]
+
+<2> Posso devolver `whatever`—de tipo `Any`—porque `Any` é _consistente-com_
+todos os tipos, incluindo o tipo declarado do valor devolvido, `BookDict`.
+
+É muito importante de ter em mente segundo ponto do <>:
+o Mypy não vai apontar qualquer problema neste código, mas durante a execução
+o valor em `whatever` pode não se adequar à estrutura de `BookDict`—pode até
+não ser um `dict`!
+
+Se você rodar o Mypy com `--disallow-any-expr`, ele vai reclamar sobre as duas
+linhas no corpo de `from_json`:
+
+[source]
+----
+…/typeddict/ $ mypy books_any.py --disallow-any-expr
+books_any.py:30: error: Expression has type "Any"
+books_any.py:31: error: Expression has type "Any"
+Found 2 errors in 1 file (checked 1 source file)
+----
+
+As linhas 30 e 31 mencionadas no trecho acima são o corpo da função `from_json`.
+Podemos silenciar o erro de tipo acrescentando uma dica de tipo à inicialização
+da variável `whatever`, como no <>.
+
+[[from_json_ex]]
+.books.py: a função `from_json` com uma anotação de variável
+====
+[source, python]
+----
+include::../code/15-more-types/typeddict/books.py[tags=FROMJSON]
+----
+====
+
+<1> `--disallow-any-expr` não gera erros quando uma expressão de tipo `Any` é
+imediatamente atribuída a uma variável com uma dica de tipo.
+
+<2> Agora `whatever` é do tipo `BookDict`, o tipo declarado do valor devolvido.
+
+[WARNING]
+====
+
+Não se deixe enganar por uma falsa sensação de tipagem segura com o
+<>! Olhando o código estático, o checador de tipos não tem como
+prever se `json.loads()` irá devolver qualquer coisa parecida com um `BookDict`.
+Apenas a validação durante a execução pode garantir isso.
+
+====
+
+A checagem de tipos estática é incapaz de prevenir erros cm código inerentemente
+dinâmico, como `json.loads()`, que cria objetos Python de tipos diferentes
+durante a execução. O <>, o
+<> e o <> demonstram
+isso.
+
+[[bookdict_demo_not_book_ex]]
+.demo_not_book.py: `from_json` devolve um `BookDict` inválido, e `to_xml` o aceita
+====
+[source, python]
+----
+include::../code/15-more-types/typeddict/demo_not_book.py[]
+----
+====
+<1> Essa linha não produz um `BookDict` válido—veja o conteúdo de `NOT_BOOK_JSON`.
+<2> Vamos deixar o Mypy revelar alguns tipos.
+<3> Isso não deve causar problemas: `print` consegue lidar com `object` e com qualquer outro tipo.
+<4> `BookDict` não tem uma chave `'flavor'`, mas o fonte JSON tem...o que acontecerá?
+<5> Lembre-se da assinatura: `to_xml(book: BookDict) {rt-arrow} str:`
+<6> Como será a saída em XML?
+
+Agora checamos _demo_not_book.py_ com o Mypy:
+
+[[bookdict_demo_not_book_check]]
+.Relatório do Mypy para _demo_not_book.py_, reformatado por legibilidade
+====
+[source]
+----
+…/typeddict/ $ mypy demo_not_book.py
+demo_not_book.py:12: note: Revealed type is
+   'TypedDict('books.BookDict', {'isbn': built-ins.str,
+                                 'title': built-ins.str,
+                                 'authors': built-ins.list[built-ins.str],
+                                 'pagecount': built-ins.int})'  <1>
+demo_not_book.py:13: note: Revealed type is 'built-ins.list[built-ins.str]'  <2>
+demo_not_book.py:16: error: TypedDict "BookDict" has no key 'flavor'  <3>
+Found 1 error in 1 file (checked 1 source file)
+----
+====
+<1> O tipo revelado é o tipo estático, não o conteúdo de `not_book` durante a execução.
+
+<2> De novo, este é o tipo estático de `not_book['authors']`, como definido em
+`BookDict`. Não o tipo durante a execução.
+
+<3> Este erro é para a linha `print(not_book['flavor'])`: esta chave não existe
+no tipo estático.
+
+Agora vamos executar _demo_not_book.py_, mostrando o resultado no
+<>.
+
+[[bookdict_demo_not_book_run]]
+.Resultado da execução de `demo_not_book.py`
+====
+[source]
+----
+…/typeddict/ $ python3 demo_not_book.py
+{'title': 'Andromeda Strain', 'flavor': 'pistachio', 'authors': True}  <1>
+pistachio  <2>
+  <3>
+        Andromeda Strain
+        pistachio
+        True
+
+----
+====
+<1> Isso não é um `BookDict` de verdade.
+<2> O valor de `not_book['flavor']`.
+<3> `to_xml` recebe um argumento `BookDict`, mas não há qualquer checagem durante a execução: entra lixo, sai lixo.
+
+O <> mostra que _demo_not_book.py_ devolve bobagens,
+mas não há qualquer erro durante a execução. Usar um `TypedDict` ao tratar dados
+em formato JSON não resultou em uma tipagem segura.
+
+Olhando o código de `to_xml` no <> do ponto de vista da tipagem pato,
+o argumento `book` deve fornecer um método `.items()` que devolve um iterável de
+tuplas na forma `(chave, valor)`, onde:
+
+* `chave` deve ter um método `.upper()`
+* `valor` pode ser qualquer coisa.
+
+A conclusão desta demonstração: quando estamos lidando com dados de estrutura
+dinâmica, tal como JSON ou XML, `TypedDict` não é, de forma alguma, um
+substituto para a validaçào de dados durante a execução. Para isso, use o
+https://fpy.li/15-5[_pydantic_].
+
+`TypedDict` tem mais recursos, incluindo suporte a chaves opcionais, uma forma
+limitada de herança e uma sintaxe de declaração alternativa. Para saber mais
+sobre ele, estude a
+https://fpy.li/pep589[_PEP 589—TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys_]
+(TypedDict: dicas de tipo para dicionários com um conjunto fixo de chaves).
+
+Vamos agora voltar nossa atenção para uma função que é melhor evitar, mas que
+algumas vezes é inevitável: `typing.cast`.((("", startref="typeddict15")))((("",
+startref="typeddict15")))
+
+
+[[type_casting_sec]]
+=== Coerção de tipo (_type casting_)
+
+Nenhum((("gradual type system", "type casting", id="GTStypecast15")))((("type casting",
+id="typecast15"))) sistema de tipos é perfeito, nem tampouco os
+checadores estáticos de tipo, as dicas de tipo no projeto _typeshed_ ou as dicas
+de tipo em pacotes de terceiros, quando existem.
+A função especial `typing.cast()` é uma forma de lidar com defeitos ou
+incorreções nas dicas de tipo em código que não podemos consertar. A
+https://fpy.li/15-14[documentação do Mypy 0.930] explica (tradução nossa):
+
+[quote]
+____
+Coerções são usadas para silenciar avisos espúrios do checador de tipos,
+e ajudam o checador quando ele não consegue entender o que está acontecendo.
+____
+
+Durante a execução, `typing.cast` não faz absolutamente nada.
+Esta é sua https://fpy.li/15-15[implementação]:
+
+[source, python]
+----
+def cast(typ, val):
+    """Cast a value to a type.
+    This returns the value unchanged.  To the type checker this
+    signals that the return value has the designated type, but at
+    runtime we intentionally don't check anything (we want this
+    to be as fast as possible).
+    """
+    return val
+----
+
+A docstring diz: "Coage um valor para um tipo.
+Isto devolve o valor inalterado.
+Para o checador de tipos, isto sinaliza que o valor
+devolvido tem o tipo designado, mas na execução
+não fazemos nenhuma checagem (queremos que isto seja
+tão rápido quanto possível)".
+
+A PEP 484 exige que os checadores de tipos "acreditem cegamente" em `cast`. A
+https://fpy.li/15-16[seção "Casts" (_Coerções_) da PEP 484] mostra um exemplo
+onde o checador precisa da orientação de `cast`:
+
+[source, python]
+----
+include::../code/15-more-types/cast/find.py[tags=CAST]
+----
+
+A chamada `next()` na expressão geradora vai devolver o índice de um item
+`str` ou levantar `StopIteration`. Assim, `find_first_str` vai sempre devolver uma
+`str` se não for gerada uma exceção, e `str` é o tipo declarado do valor
+devolvido.
+
+Mas se a última linha for apenas `return a[index]`, o Mypy inferiria o tipo
+devolvido como `object`, porque o argumento `a` é declarado como `list[object]`.
+Então `cast()` é necessário para orientar o Mypy.footnote:[O uso de `enumerate` no
+exemplo serve para confundir intencionalmente o checador de tipos. Uma
+implementação mais simples, produzindo strings diretamente, sem passar pelo
+índice de `enumerate`, seria corretamente analisada pelo Mypy, e o `cast()` não
+seria necessário.]
+
+Aqui está outro exemplo com `cast`, desta vez para corrigir uma dica de tipo
+desatualizada na biblioteca padrão de Python. No https://fpy.li/94[«Exemplo 12 do Capítulo 21»] (vol.3), criei
+um objeto `asyncio.Server`, e queria obter o endereço onde o servidor está
+ouvindo (aceitando conexões). Escrevi esta linha de código:
+
+[source, python]
+----
+addr = server.sockets[0].getsockname()
+----
+
+Mas o Mypy informou o seguinte erro:
+
+----
+Value of type "Optional[List[socket]]" is not indexable
+----
+
+A dica de tipo para `Server.sockets` no _typeshed_, em maio de 2021, é válida
+para Python 3.6, onde o atributo `sockets` podia ser `None`. Mas no Python 3.7,
+`sockets` se tornou uma propriedade, com um _getter_ que sempre devolve uma
+`list`—que pode ser vazia, se o servidor não tiver um _socket_. E desde o Python
+3.8, esse _getter_ devolve uma `tuple` (usada como uma sequência imutável).
+
+<<<
+Já que não posso consertar o _typeshed_ nesse instante,footnote:[Relatei o
+problema em https://fpy.li/15-17[issue #5535] no _typeshed_, "Dica de tipo
+errada para o atributo `sockets` em asyncio.base_events.Server sockets
+attribute.", e ele foi rapidamente resolvido por Sebastian Rittau. Mas decidi
+manter o exemplo, pois ele ilustra um caso de uso comum para `cast`, e o `cast`
+que escrevi é inofensivo.] acrescentei um `cast`, assim:
+
+[source, python]
+----
+include::../code/15-more-types/cast/tcp_echo.py[tags=CAST_IMPORTS]
+
+# ... muitas linhas omitidas ...
+
+include::../code/15-more-types/cast/tcp_echo.py[tags=CAST_USE]
+----
+
+Usar `cast` nesse caso exigiu algumas horas para entender o problema e ler o
+código-fonte de  _asyncio_, para encontrar o tipo correto para _sockets_: a
+classe `TransportSocket` do módulo não-documentado `asyncio.trsock`. Também
+precisei adicionar duas instruções `import` e mais uma linha de código para
+melhorar a legibilidade.footnote:[Na realidade, inicialmente coloquei um
+comentário `# type: ignore` às linhas com `+server.sockets[0]+` porque, após
+pesquisar um pouco, encontrei linhas similares na
+https://fpy.li/7g[documentação]
+do _asyncio_ e em um https://fpy.li/15-19[caso de teste], e aí comecei a
+suspeitar que o problema não estava no meu código.] Mas agora o código está mais
+seguro.
+
+A leitora atenta pode ter notado que `sockets[0]` poderia gerar um `IndexError`
+se `sockets` estiver vazio.
+Entretanto, até onde entendo o _asyncio_, isso não pode acontecer no
+https://fpy.li/94[«Exemplo 12 do Capítulo 21»] (vol.3), pois no momento em que leio o atributo `sockets`, o
+`server` já está pronto para aceitar conexões , portanto o atributo não estará
+vazio. E, de qualquer forma, `IndexError` ocorre durante a execução. O Mypy não
+consegue localizar esse problema nem mesmo em um caso trivial como
+`print([][0])`.
+
+[WARNING]
+====
+
+Não se acostume a usar `cast` para silenciar o Mypy toda hora, porque
+normalmente o Mypy está certo quando aponta um erro. Se você estiver aplicando
+`cast` com frequência, isso é um https://fpy.li/15-20[_code smell_]
+(cheiro no código). Sua equipe pode estar fazendo um mau uso das dicas de tipo, ou sua
+base de código pode ter dependências de baixa qualidade.
+
+====
+
+Apesar de suas desvantagens, há usos válidos para `cast`.
+Eis algo que Guido van Rossum escreveu sobre isso:
+
+[quote]
+____
+
+O que há de errado com uma eventual chamada a `cast()` ou um comentário +
+`# type: ignore` ?footnote:[https://fpy.li/15-21[Mensagem de 18 de maio de 2020]
+para a lista de e-mail typing-sig.]
+____
+
+É insensato banir inteiramente o uso de `cast`, principalmente porque as
+alternativas para contornar esses problemas são piores:
+
+* `# type: ignore` é menos informativo.footnote:[A sintaxe `+# type:
+ignore[code]+` permite especificar qual erro do Mypy está sendo silenciado,
+mas os códigos nem sempre são fáceis de interpretar. Veja a página
+https://fpy.li/15-22[_Error codes_] na documentação do Mypy.]
+
+* Usar `Any` é contagioso: já que `Any` é _consistente-com_ todos os tipos, seu
+abuso pode produzir efeitos em cascata através da inferência de tipo, minando a
+capacidade do checador de tipos para detectar erros em outras partes do código.
+
+Claro, nem todos os contratempos de tipagem podem ser resolvidos com `cast`.
+Algumas vezes precisamos de `# type: ignore`, do `Any` aqui ou ali, ou mesmo
+deixar uma função sem dicas de tipo.
+
+A seguir, vamos falar sobre o uso de anotações durante a execução.((("",
+startref="typecast15")))((("", startref="GTStypecast15")))
+
+
+[[runtime_annot_sec]]
+=== Lendo dicas de tipo durante a execução
+
+Durante((("gradual type system", "reading hints at runtime",
+id="GTSruntime15"))) a importação, Python lê as dicas de tipo em funções,
+classes e módulos, e as armazena em atributos chamados `+__annotations__+`.
+Considere, por exemplo, a função `clip` no
+<>.footnote:[Não vou entrar nos detalhes da implementação de
+`clip`, mas se você tiver curiosidade, pode ler o módulo completo em
+https://fpy.li/15-23[_clip_annot.py_].]
+
+
+[[ex_clip_annot]]
+.clipannot.py: a assinatura anotada da função `clip`
+====
+[source, python]
+----
+def clip(text: str, max_len: int = 80) -> str:
+----
+====
+
+As dicas de tipo são armazenadas em um `dict` no atributo `+__annotations__+` da função:
+
+[source, python]
+----
+>>> from clip_annot import clip
+>>> clip.__annotations__
+{'text': , 'max_len': , 'return': }
+----
+
+A chave `'return'` está mapeada para a dica do tipo devolvido após o símbolo {rt-arrow} no <>.
+
+<<<
+Note que as anotações são avaliadas pelo interpretador no momento da importação, ao mesmo tempo em que os valores default dos parâmetros são avaliados.
+Por isso os valores nas anotações são as classes Python `str` e `int`,
+e não as strings `'str'` and `'int'`.
+A avaliação das anotações no momento da importação é o padrão desde o Python 3.10,
+mas isso pode mudar se a https://fpy.li/pep563[PEP 563] ou a https://fpy.li/pep649[PEP 649] se tornarem o comportamento padrão.
+
+[role="pagebreak-before less_space"]
+[[problems_annot_runtime_sec]]
+==== Problemas com anotações durante a execução
+
+O aumento do uso de dicas de tipo gerou dois problemas:
+
+* Importar módulos usa mais CPU e memória quando são há dicas de tipo.
+* Referências a tipos ainda não definidos exigem o uso de strings em vez dos tipos reais.
+
+As duas questões são relevantes. A primeira pelo que acabamos de ver: anotações
+são avaliadas pelo interpretador durante a importação e armazenadas no atributo
+`+__annotations__+`. Quando uma empresa tem milhares de servidores importanto
+arquivos Python, o custo pode ser significativo, mesmo considerando que a
+importação de cada módulo só acontece no início do processo.
+
+Vamos nos concentrar agora no segundo problema.
+
+Armazenar anotações((("forward reference problem"))) como string é necessário
+algumas vezes, por causa do problema da referência futura (_forward
+reference_): quando uma dica de tipo precisa se referir a uma classe definida
+mais adiante no mesmo módulo. Entretanto uma manifestação comum desse problema
+no código-fonte não se parece de forma alguma com uma referência futura:
+quando um método devolve um novo objeto da mesma classe. Já que o objeto classe
+não está definido até Python terminar a avaliação do corpo da classe, as dicas
+de tipo precisam usar o nome da classe como string. Eis um exemplo:
+
+[source, python]
+----
+class Rectangle:
+    # ... lines omitted ...
+    def stretch(self, factor: float) -> 'Rectangle':
+        return Rectangle(width=self.width * factor)
+----
+
+<<<
+Escrever dicas de tipo com referências futuras como strings é a prática
+padrão e exigida no Python 3.10. Os checadores de tipos estáticos foram
+projetados desde o início para lidar com esse problema.
+
+Mas durante a execução, se você escrever código para ler a anotação `return` de
+`stretch`, vai receber a string `'Rectangle'` em vez de uma referência ao tipo
+real, a classe `Rectangle`. E aí seu código precisa descobrir o que aquela
+string significa.
+
+O módulo `typing` inclui três funções e uma classe categorizadas como
+https://fpy.li/7h[Auxiliares de introspecção],
+sendo `typing.get_type_hints` a mais importante delas. Parte de sua documentação afirma:
+
+`get_type_hints(obj, globals=None, locals=None, include_extras=False)`::
+  [...] Isso é muitas vezes igual a `+obj.__annotations__+`.
+  Além disso, referências futuras codificadas como strings
+  literais são tratadas por sua avaliação nos espaços de nomes
+  `globals` e `locals`. [...]
+
+[WARNING]
+====
+
+Desde o Python 3.10, a nova função
+https://fpy.li/15-25[`inspect.get_annotations(…)`] deve ser usada, em vez de
+`get_type_hints`. Entretanto, alguns leitores podem ainda não estar trabalhando
+com Python 3.10, então usarei `get_type_hints` nos exemplos, pois essa função
+está disponível desde a inclusão do módulo `typing`, no Python 3.5.
+
+====
+
+A https://fpy.li/pep563[_PEP 563—Postponed Evaluation of Annotations_]
+(Avaliação Adiada de Anotações) foi aprovada para tornar desnecessário escrever
+anotações como strings, e para reduzir o custo das dicas de tipo durante a
+execução. A ideia principal está descrita nessas duas sentenças do
+https://fpy.li/15-26[_Abstract_]:
+
+[quote]
+____
+
+Esta PEP propõe modificar as anotações de funções e de variáveis, de forma que
+elas não mais sejam avaliadas no momento da definição da função. Em vez disso,
+elas são preservadas em +__annotations__+ na forma de strings.
+
+____
+
+A partir de Python 3.7, é assim que anotações são tratadas em qualquer módulo
+que comece com a seguinte instrução `import`:
+
+[source, python]
+----
+from __future__ import annotations
+----
+
+Para demonstrar seu efeito, coloquei a mesma função `clip` do <>
+em um módulo  _clip_annot_post.py_ com aquela linha de importação `+__future__+`
+no início.
+
+No console, esse é o resultado de importar aquele módulo e ler as anotações de `clip`:
+
+[source, python]
+----
+>>> from clip_annot_post import clip
+>>> clip.__annotations__
+{'text': 'str', 'max_len': 'int', 'return': 'str'}
+----
+
+Como se vê, todas as dicas de tipo são agora strings simples, apesar de não
+terem sido escritas como strings na definição de `clip` (no <>).
+
+A função `typing.get_type_hints` consegue resolver muitas dicas de tipo,
+incluindo essas de `clip`:
+
+[source, python]
+----
+>>> from clip_annot_post import clip
+>>> from typing import get_type_hints
+>>> get_type_hints(clip)
+{'text': , 'max_len': , 'return': }
+----
+
+A chamada a `get_type_hints` nos dá os tipos reais—mesmo em alguns casos onde
+a dica de tipo original foi escrita como uma string. Esta é a maneira
+recomendada de ler dicas de tipo durante a execução.
+
+O comportamento prescrito na PEP 563 estava previsto para se tornar o default no
+Python 3.10, tornando a importação com `+__future__+` desnecessária. Entretanto,
+os mantenedores da _FastAPI_ e do _pydantic_ avisaram de que tal mudança
+quebraria seu código, que se baseia em dicas de tipo durante a execução e não
+podem usar `get_type_hints` de forma confiável.
+
+Na discussão que se seguiu na lista de e-mail python-dev, Łukasz Langa—autor da
+PEP 563—descreveu algumas limitações daquela função:
+
+[quote]
+____
+
+[...] a verdade é que `typing.get_type_hints()` tem limites que tornam seu uso
+geral custoso durante a execução e, mais importante, insuficiente para resolver
+todos os tipos. O exemplo mais comum se refere a contextos não-globais nos quais
+tipos são gerados (isto é, classes aninhadas, classes dentro de funções, etc.).
+Mas um dos principais exemplos de referências futuras, classes com métodos
+aceitando ou devolvendo objetos de seu próprio tipo, também não é tratado de
+forma apropriada por `typing.get_type_hints()` se um gerador de classes for
+usado. Há alguns truques que podemos usar para ligar os pontos mas, de uma forma
+geral, isso não é bom.footnote:[Mensagem https://fpy.li/15-27[_PEP 563 in light
+of PEP 649_] (PEP 563 à luz da PEP 649), publicado em 16 de abril de 2021.]
+
+____
+
+O Steering Council de Python decidiu adiar a elevação da PEP 563 a comportamento
+padrão até Python 3.11 ou posterior, dando mais tempo aos desenvolvedores para
+criar uma solução para os problemas que a PEP 563 tentou resolver, sem quebrar o
+uso dissseminado das dicas de tipo durante a execução. A
+https://fpy.li/pep649[_PEP 649—Deferred Evaluation Of Annotations Using Descriptors_]
+(Avaliação adiada de anotações usando descritores) está sendo
+considerada como uma possível solução, mas algum outro acordo ainda pode ser
+alcançado.
+
+Resumindo: ler dicas de tipo durante a execução não é 100% confiável no Python
+3.10 e provavelmente mudará em alguma futura versão.
+
+[NOTE]
+====
+
+Empresas usando Python em escala muito grande desejam os benefícios da tipagem
+estática, mas não querem pagar o preço da avaliação de dicas de tipo no momento
+da importação. A checagem estática acontece nas estações de trabalho dos
+desenvolvedores e em servidores de integração contínua dedicados, mas o
+carregamento de módulos acontece com uma frequência e um volume muito maiores,
+em servidores de produção, e este custo não é desprezível em grande escala.
+
+Isto cria uma tensão na comunidade Python, entre aqueles que querem as dicas de
+tipo armazenadas apenas como strings—para reduzir os custos de
+carregamento—versus aqueles que também querem usar as dicas de tipo durante a
+execução, como os criadores e os usuários do _pydantic_ e da _FastAPI_, para
+quem seria mais fácil acessar diretamente os tipos, ao invés de precisarem
+analisar strings nas anotações, uma tarefa complicada.
+
+====
+
+==== Lidando com o problema
+
+Dada a instabilidade da situação atual, se você precisar ler anotações durante a execução, recomendo o seguinte:
+
+* Evite ler `+__annotations__+` diretamente; em vez disso, use `inspect.get_annotations` (desde o Python 3.10) ou `typing.get_type_hints` (desde o Python 3.5).
+* Escreva uma função customizada própria, como um invólucro para
+pass:[in​spect​.get_annotations] ou `typing.get_type_hints`, e faça o restante de sua base de código chamar aquela função, de forma que mudanças futuras fiquem restritas a um único local.
+
+Para demonstrar esse segundo ponto, aqui estão as primeiras linhas da classe `Checked`,
+que estudaremos no https://fpy.li/95[«Exemplo 5 do Capítulo 24»] (vol.3):
+
+[source, python]
+----
+class Checked:
+    @classmethod
+    def _fields(cls) -> dict[str, type]:
+        return get_type_hints(cls)
+----
+
+O método de `Checked._fields` evita que outras partes do módulo dependam diretamente de
+`typing.get_type_hints`. Se `get_type_hints` mudar no futuro, exigindo lógica adicional, ou se eu quiser substituí-la por `inspect.get_annotations`, a mudança estará limitada a `Checked._fields` e não afetará o restante do programa.
+
+[WARNING]
+====
+Dadas as discussões atuais e as mudanças propostas para a inspeção de dicas de tipo durante a execução, a página da documentação oficial https://fpy.li/7j[«Boas práticas para anotações»] é leitura obrigatória, e deverá ser atualizada até o lançamento do Python 3.11.
+Aquele _how-to_ foi escrito por Larry Hastings, autor da
+https://fpy.li/pep649[_PEP 649—Deferred Evaluation Of Annotations Using Descriptors_]
+(Avaliação Adiada de Anotações Usando Descritores), uma alternativa para
+tratar os problemas da
+https://fpy.li/pep563[_PEP 563—Postponed Evaluation of Annotations_]
+(Avaliação Adiada de Anotações).
+====
+
+As demais seções deste capítulo cobrem tipos genéricos, começando pela forma de definir uma classe genérica, que pode ser parametrizada por seus usuários.((("", startref="GTSruntime15")))
+
+
+[[impl_generic_class_sec]]
+=== Implementando uma classe genérica
+
+No((("gradual type system", "implementing generic classes",
+id="GTSgeneric15")))((("classes", "implementing generic classes",
+id="CAPgeneric15")))((("generic classes, implementing", id="genclasimp15")))
+<> do <>,
+definimos a ABC `Tombola`: uma interface para classes que
+funcionam como um recipiente para sorteio de bingo. A classe `LottoBlower`
+(<> do <>) é uma implementação concreta.
+Vamos agora estudar uma versão
+genérica de `LottoBlower`, usada da forma que aparece no
+<>.
+
+
+[[ex_generic_lotto_demo]]
+.generic_lotto_demo.py: usando uma classe genérica de sorteio de bingo
+====
+[source, python]
+----
+include::../code/15-more-types/lotto/generic_lotto_demo.py[tags=LOTTO_USE]
+----
+====
+<1> Para instanciar uma classe genérica,
+passamos a ela um parâmetro de tipo concreto, como `int` aqui.
+<2> O Mypy irá inferir corretamente que `first` é um `int`...
+<3> ... e que `remain` é uma `tuple` de inteiros.
+
+Além disso, o Mypy aponta violações do tipo parametrizado com mensagens úteis,
+como ilustrado no <>.
+
+[[ex_generic_lotto_errors]]
+.generic_lotto_errors.py: erros apontados pelo Mypy
+====
+[source, python]
+----
+include::../code/15-more-types/lotto/generic_lotto_errors.py[]
+----
+====
+<1> Na instanciação de `LottoBlower[int]`, o Mypy marca o `float`.
+
+<2> Na chamada `.load('ABC')`, o Mypy explica porque uma `str` não serve:
+`+str.__iter__+` devolve um `Iterator[str]`, mas `LottoBlower[int]` exige um
+`Iterator[int]`.
+
+
+O <> é a implementação.
+
+
+[[ex_generic_lotto]]
+.generic_lotto.py: uma classe genérica de sorteador de bingo
+====
+[source, python]
+----
+include::../code/15-more-types/lotto/generic_lotto.py[]
+----
+====
+
+<1> Declarações de classes genéricas muitas vezes usam herança múltipla, porque
+precisamos incluir a superclasse `Generic` para declarar os parâmetros de tipo
+formais—neste caso, `T`.
+
+<2> O argumento `items` em `+__init__+` é do tipo `Iterable[T]`, que se torna
+`Iterable[int]` quando uma instância é declarada como `LottoBlower[int]`.
+
+<3> O método `load` é igualmente anotado.
+
+<4> O tipo do valor devolvido `T` agora se torna `int` em um `LottoBlower[int]`.
+
+<5> Nenhuma variável de tipo aqui.
+
+<6> Por fim, `T` define o tipo dos itens na `tuple` devolvida.
+
+
+[TIP]
+====
+
+A seção
+https://fpy.li/7k[_User-defined generic types_]
+(Tipos genéricos definidos pelo usuário), na documentação do
+módulo `typing`, é curta, inclui bons exemplos e fornece alguns detalhes que não
+menciono aqui.
+
+====
+
+Agora que vimos como implementar um classe genérica, vamos definir a
+terminologia para falar sobre tipos genéricos.
+
+==== Jargão básico para tipos genéricos
+
+Aqui estão algumas definições que encontrei estudando genéricos:footnote:[Os
+termos são do livro clássico de Joshua Bloch, _Effective Java_, 3rd ed. As
+traduções, definições e exemplos são meus.]
+
+Tipo genérico::
+Um tipo declarado com uma ou mais variáveis de tipo. +
+Exemplos: `LottoBlower[T]`, `abc.Mapping[KT, VT]`
+
+Parâmetro de tipo formal::
+As((("formal type parameters"))) variáveis de tipo que aparecem em um declaração de tipo genérica. +
+Exemplo: `KT` e `VT` no último exemplo: `abc.Mapping[KT, VT]`
+
+Tipo parametrizado::
+Um((("parameterized types"))) tipo declarado com os parâmetros de tipo reais. +
+Exemplos: `LottoBlower[int]`, `abc.Mapping[str, float]`
+
+Parâmetro de tipo real::
+Os((("actual type parameters"))) tipos reais passados como parâmetros
+quando um tipo parametrizado   é declarado. +
+Exemplo: o `int` em `LottoBlower[int]`
+
+O próximo tópico é sobre como tornar os tipos genéricos mais flexíveis,
+introduzindo os conceitos de covariância, contravariância e invariância.((("",
+startref="genclasimp15")))((("", startref="CAPgeneric15")))((("",
+startref="GTSgeneric15")))
+
+[[variance_sec]]
+=== Variância
+
+[NOTE]
+====
+
+Dependendo((("gradual type system", "variance and",
+id="GTSvar15")))((("variance", "relevance of"))) de sua experiência com
+genéricos em outras linguagens, esta pode ser a seção mais difícil do livro. O
+conceito de variância é abstrato, e uma apresentação rigorosa faria essa seção
+se parecer com páginas de um livro de matemática.
+
+Na prática, a variância é mais relevante para autores de bibliotecas que querem
+suportar novos tipos de coleções genéricas ou fornecer uma API baseada em
+_callbacks_. Mesmo nestes casos, é possível evitar muita complexidade suportando
+apenas coleções invariantes—que é o que temos hoje na biblioteca
+padrão. Então, em uma primeira leitura você pode pular toda esta seção, ou ler
+apenas as partes sobre tipos invariantes.
+
+====
+
+Já vimos o conceito de _variância_ na https://fpy.li/7y[«Seção 8.5.11.1»] (vol.1), aplicado a
+tipos genéricos `Callable` parametrizados. Aqui vamos expandir o conceito para
+abarcar tipos genéricos de coleções, usando uma analogia do "mundo real" para
+tornar mais concreto esse conceito abstrato.
+
+Imagine uma cantina escolar que tenha como regra que apenas máquinas servindo
+sucos podem ser instaladas.footnote:[A primeira vez que vi a analogia da
+cantina para variância foi no prefácio de Erik Meijer para o livro _The Dart
+Programming Language_ ("A Linguagem de Programação Dart"), de Gilad Bracha
+(Addison-Wesley).] Máquinas de bebida genéricas não são permitidas, pois podem
+servir refrigerantes, que foram banidos pela direção da escola.footnote:[Muito
+melhor que banir livros!]
+
+
+==== Uma máquina de bebida invariante
+
+Vamos((("variance", "invariant types"))) tentar modelar o cenário da cantina com uma classe genérica `BeverageDispenser`, que pode ser parametrizada com o tipo de bebida..
+Veja o <>.
+
+[[invariant_dispenser_types_ex]]
+.invariant.py: definições de tipo e função `install`
+====
+[source, python]
+----
+include::../code/15-more-types/cafeteria/invariant.py[tags=BEVERAGE_TYPES]
+----
+====
+<1> `Beverage`, `Juice`, e `OrangeJuice` formam uma hierarquia de tipos.
+<2> Uma declaração `TypeVar` simples.
+<3> `BeverageDispenser` é parametrizada pelo tipo de bebida.
+<4> `install` é uma função global do módulo. Sua dica de tipo faz valer a regra de que apenas máquinas de suco são aceitáveis.
+
+Dadas as definições no <>, o seguinte código é válido:
+
+
+[source, python]
+----
+include::../code/15-more-types/cafeteria/invariant.py[tags=INSTALL_JUICE_DISPENSER]
+----
+
+Entretanto, isto não é válido:
+
+[source, python]
+----
+include::../code/15-more-types/cafeteria/invariant.py[tags=INSTALL_BEVERAGE_DISPENSER]
+----
+
+Uma máquina que serve qualquer `Beverage` não é aceitável, pois a cantina exige uma máquina especializada em `Juice`.
+
+De forma um tanto surpreendente, este código também é inválido:
+
+[source, python]
+----
+include::../code/15-more-types/cafeteria/invariant.py[tags=INSTALL_ORANGE_JUICE_DISPENSER]
+----
+
+Uma máquina especializada em `OrangeJuice` também não é permitida.
+Apenas `BeverageDispenser[Juice]` serve.
+No jargão da tipagem, dizemos que `BeverageDispenser(Generic[T])` é invariante quando `BeverageDispenser[OrangeJuice]` não é compatível com `BeverageDispenser[Juice]`—apesar do fato de `OrangeJuice` ser um _subtipo-de_ `Juice`.
+
+Os tipos de coleções mutáveis de Python—tal como `list` e `set`—são invariantes.
+A classe `LottoBlower` do <> também é invariante.
+
+
+==== Uma máquina de bebida covariante
+
+Se((("variance", "covariant types"))) quisermos ser mais flexíveis, e modelar as máquinas de bebida como uma classe genérica que aceite alguma bebida e também seus subtipos, precisamos tornar a classe covariante.
+O <> mostra como declararíamos `BeverageDispenser`.
+
+[[covariant_dispenser_types_ex]]
+._covariant.py_: definições de tipo e função `install`
+====
+[source, python]
+----
+include::../code/15-more-types/cafeteria/covariant.py[tags=BEVERAGE_TYPES]
+----
+====
+<1> Define `covariant=True` ao declarar a variável de tipo; `+_co+` é o sufixo convencional para parâmetros de tipo covariantes no _typeshed_.
+<2> Usa `T_co` para parametrizar a classe especial `Generic`.
+<3> As dicas de tipo para `install` são as mesmas do <>.
+
+O código abaixo funciona porque tanto a máquina de `Juice` quanto a de `OrangeJuice` são válidas em uma `BeverageDispenser` covariante:
+
+[source, python]
+----
+include::../code/15-more-types/cafeteria/covariant.py[tags=INSTALL_JUICE_DISPENSERS]
+----
+
+mas uma máquina de uma `Beverage` arbitrária não é aceitável:
+
+[source, python]
+----
+include::../code/15-more-types/cafeteria/covariant.py[tags=INSTALL_BEVERAGE_DISPENSER]
+----
+
+Isso é uma covariância:
+a relação de subtipo das máquinas parametrizadas varia na mesma direção da relação de subtipo dos parâmetros de tipo.
+
+
+==== Uma lata de lixo contravariante
+
+Vamos((("variance", "contravariant types"))) agora modelar a regra da cantina para a instalação de uma lata de lixo.
+Vamos supor que a comida e a bebida são servidas em recipientes biodegradáveis, e as sobras e utensílios descartáveis também são biodegradáveis.
+As latas de lixo devem ser adequadas para resíduos biodegradáveis.
+
+[NOTE]
+====
+Neste exemplo didático, vamos fazer algumas suposições e classificar o lixo em uma hierarquia simplificada:
+
+* `Refuse` (_Resíduo_) é o tipo mais geral de lixo. Todo lixo é resíduo.
+
+* `Biodegradable` (_Biodegradável_) é um tipo de lixo decomposto por microrganismos ao longo do tempo.
+Parte do `Refuse` não é `Biodegradable`.
+
+* `Compostable` (_Compostável_) é um tipo específico de lixo `Biodegradable` que pode ser transformado de em fertilizante orgânico,
+em um processo de compostagem. Na nossa definição, nem todo lixo `Biodegradable` é `Compostable`.
+====
+
+Para modelar a regra descrevendo uma lata de lixo aceitável na cantina,
+precisamos introduzir o conceito de "contravariância" através de um exemplo, apresentado no <>.
+
+[[contravariant_trash_ex]]
+._contravariant.py_: definições de tipo e a função `install`
+====
+[source, python]
+----
+include::../code/15-more-types/cafeteria/contravariant.py[tags=TRASH_TYPES]
+----
+====
+<1> Uma hierarquia de tipos para resíduos: `Refuse` é o tipo mais geral, `Compostable` o mais específico.
+<2> `T_contra` é o nome convencional para uma variável de tipo contravariante.
+<3> `TrashCan` é contravariante ao tipo de resíduo.
+<4> A função `deploy` exige uma lata de lixo compatível com `TrashCan[Biodegradable]`.
+
+Dadas essas definições, os seguintes tipos de lata de lixo são aceitáveis:
+
+[source, python]
+----
+include::../code/15-more-types/cafeteria/contravariant.py[tags=DEPLOY_TRASH_CANS]
+----
+
+A função `deploy` aceita uma `TrashCan[Refuse]`, pois ela pode receber qualquer tipo de resíduo, incluindo `Biodegradable`.
+Entretanto, uma `TrashCan[Compostable]` não serve, pois ela não pode receber `Biodegradable`:
+
+[source, python]
+----
+include::../code/15-more-types/cafeteria/contravariant.py[tags=DEPLOY_NOT_VALID]
+----
+
+Vamos resumir os conceitos vistos até aqui.
+
+
+==== Revisão da variância
+
+A variância((("variance", "overview of"))) é uma propriedade sutil. As próximas seções recapitulam o conceito de tipos invariantes, covariantes e contravariantes, e fornecem algumas regras gerais para pensar sobre eles.
+
+===== Tipos invariantes
+
+Um tipo genérico `L` é invariante quando não há nenhuma relação de supertipo ou subtipo entre dois tipos parametrizados, independente da relação que possa existir entre os parâmetros concretos.
+Em outras palavras, se `L` é invariante, então `L[A]` não é supertipo ou subtipo de `L[B]`.
+Eles são inconsistentes em ambos os sentidos.
+
+As coleções mutáveis da biblioteca padrão de Python são invariantes.
+O tipo `list` é um bom exemplo:
+`list[int]` não é _consistente-com_ `list[float]`, e vice-versa.
+
+Em geral, se um parâmetro de tipo formal aparece em dicas de tipo de argumentos a métodos, e o mesmo parâmetro aparece nos tipos devolvidos pelo método, aquele parâmetro deve ser invariante, para garantir a segurança de tipo na atualização e leitura da coleção.
+
+Por exemplo, aqui está parte das dicas de tipo para o tipo embutido `list` no
+https://fpy.li/15-30[_typeshed_]:
+
+[source, python]
+----
+class list(MutableSequence[_T], Generic[_T]):
+    @overload
+    def __init__(self) -> None: ...
+    @overload
+    def __init__(self, iterable: Iterable[_T]) -> None: ...
+    # ... lines omitted ...
+    def append(self, __object: _T) -> None: ...
+    def extend(self, __iterable: Iterable[_T]) -> None: ...
+    def pop(self, __index: int = ...) -> _T: ...
+    # etc...
+----
+
+Veja que `_T` aparece entre os parâmetros de `+__init__+`, `append` e `extend`,
+e como tipo devolvido por `pop`.
+Não há como tornar segura a tipagem dessa classe se ela for covariante ou contravariante em `_T`.
+
+
+[[covariant_types_sec]]
+===== Tipos covariantes
+
+Considere dois tipos `A` e `B`, onde `B` é _consistente-com_ `A`, e nenhum deles é `Any`.
+Alguns autores usam os símbolos `<:` e `:>` para indicar relações de tipos como essas:
+
+`A :> B`:: `A` é um _supertipo-de_ ou igual a `B`.
+
+`B <: A`:: `B` é um _subtipo-de_ ou igual a `A`.
+
+Dado `A :> B`, um tipo genérico `C` é covariante quando `C[A] :> C[B]`.
+
+Observe que a direção da seta no símbolo `:>` é a mesma nos dois casos em que `A` está à esquerda de `B`.
+Tipos genéricos covariantes seguem a relação de subtipo do tipo real dos parâmetros.
+
+Contêineres imutáveis podem ser covariantes.
+Por exemplo, é assim que a classe `typing.FrozenSet` está
+https://fpy.li/7m[documentada] como covariante com uma variável de tipo usando o nome convencional `T_co`:
+
+[source, python]
+----
+class FrozenSet(frozenset, AbstractSet[T_co]):
+----
+
+Aplicando a notação `:>` a tipos parametrizados, temos:
+
+[source]
+----
+           float :> int
+frozenset[float] :> frozenset[int]
+----
+
+Iteradores são outro exemplo de genéricos covariantes: eles não são coleções
+apenas para leitura como um `frozenset`, mas apenas produzem itens sob demanda.
+Qualquer código que espere um `abc.Iterator[float]` que produz números de ponto
+flutuante pode usar com segurança um `abc.Iterator[int]` que produz inteiros.
+Tipos `Callable` são covariantes no tipo devolvido pela mesma razão.
+
+[[contravariant_types_sec]]
+===== Tipos contravariantes
+
+Dado `A :> B`, um tipo genérico `K` é contravariante se `K[A] <: K[B]`.
+
+Tipos genéricos contravariantes revertem a relação de subtipo dos tipos reais
+dos parâmetros.
+
+A classe `TrashCan` exemplifica isso:
+
+[source]
+----
+          Refuse :> Biodegradable
+TrashCan[Refuse] <: TrashCan[Biodegradable]
+----
+
+Um contêiner contravariante normalmente é uma estrutura de dados só para
+escrita, também conhecida como "coletor" (_sink_). Não há exemplos de coleções
+deste tipo na biblioteca padrão, mas existem alguns tipos com parâmetros de tipo
+contravariantes.
+
+`Callable[[ParamType, …], ReturnType]` é contravariante nos tipos dos
+parâmetros, mas covariante no  `ReturnType`, como vimos na
+https://fpy.li/7y[«Seção 8.5.11.1»] (vol.1). Além disso, https://fpy.li/15-32[`Generator`],
+https://fpy.li/typecoro[`Coroutine`], e https://fpy.li/15-33[`AsyncGenerator`]
+têm um parâmetro de tipo contravariante. O tipo `Generator` está descrito na
+https://fpy.li/87[«Seção 17.13.3»] (vol.3); `Coroutine` e `AsyncGenerator` são
+descritos no https://fpy.li/21[«Capítulo 21»] (vol.3).
+
+Para efeito da presente discussão sobre variância, o ponto principal é que
+parâmetros formais contravariantes definem o tipo dos argumentos usados para
+invocar ou enviar dados para o objeto, enquanto parâmetros formais covariantes
+definem os tipos de saídas produzidos pelo objeto—o tipo devolvido por uma
+função ou produzido por um gerador. Os significados precisos de "enviar" e
+"produzir" são definidos na https://fpy.li/7z[«Seção 17.13»] (vol.3).
+
+A partir destas  observações sobre saídas covariantes e entradas contravariantes
+podemos derivar algumas orientações úteis.
+
+[[variance_rules_sec]]
+===== Regras gerais de variância
+
+Por fim, aqui((("variance", "rules of thumb"))) estão algumas regras gerais a
+considerar quando estamos pensando sobre variância:
+
+. Se um parâmetro de tipo formal define um tipo para dados que saem de um
+objeto, ele pode ser covariante.
+
+. Se um parâmetro de tipo formal define um tipo para dados que entram em um
+objeto, ele pode ser contravariante.
+
+. Se um parâmetro de tipo formal define um tipo para dados que saem de um objeto
+e o mesmo parâmetro define um tipo para dados que entram em um objeto, ele deve
+ser invariante.
+
+. Na dúvida, use parâmetros de tipo formais invariantes. Não haverá prejuízo se
+no futuro precisar usar parâmetros de tipo covariantes ou contravariantes, pois
+nestes casos a tipagem ficará mais tolerante e não quebrará códigos existentes.
+
+`Callable[[ParamType, …], ReturnType]` demonstra as regras 1 e 2: O
+`ReturnType` é covariante, e cada `ParamType` é contravariante.
+
+Por default, `TypeVar` cria parâmetros formais invariantes, e é assim que as
+coleções mutáveis na biblioteca padrão são anotadas.
+Veremos mais exemplos de variância na 
+https://fpy.li/87[«Seção 17.13.3»] (vol.3).
+
+A seguir, veremos como usar um protocolo estático genérico.((("", startref="GTSvar15")))
+
+
+[[implementing_generic_static_proto_sec]]
+=== Implementando um protocolo estático genérico
+
+A((("gradual type system", "implementing generic static protocols",
+id="GTSgenstatpro15")))((("generic static protocols",
+id="genstatpro15")))((("protocols", "implementing generic static protocols",
+id="Pgenstatpro15")))((("static protocols", "implementing generic static protocols",
+id="SPgenstatpro15"))) biblioteca padrão de Python 3.10 fornece
+alguns protocolos estáticos genéricos. Um deles é `SupportsAbs`,
+implementado assim no https://fpy.li/15-34[módulo _typing_]:
+
+[source, python]
+----
+@runtime_checkable
+class SupportsAbs(Protocol[T_co]):
+    """An ABC with one abstract method __abs__ that is covariant in its
+        return type."""
+    __slots__ = ()
+
+    @abstractmethod
+    def __abs__(self) -> T_co:
+        pass
+----
+
+`T_co` é declarado de acordo com a convenção de nomenclatura:
+
+[source, python]
+----
+T_co = TypeVar('T_co', covariant=True)
+----
+
+Graças a `SupportsAbs`, o Mypy considera válido o seguinte código, como visto no <>.
+
+[[ex_abs_demo]]
+._abs_demo.py_: uso do protocolo genérico `SupportsAbs`
+====
+[source, python]
+----
+include::../code/15-more-types/protocol/abs_demo.py[]
+----
+====
+<1> Definir `+__abs__+` torna `Vector2d` _consistente-com_ `SupportsAbs`.
+<2> Parametrizar `SupportsAbs` com `float` assegura...
+<3> ...que o Mypy aceite `abs(v)` como primeiro argumento para `math.isclose`.
+<4> Graças a `@runtime_checkable` na definição de `SupportsAbs`, essa é uma asserção válida durante a execução.
+<5> Todo o restante do código passa pelas checagens do Mypy e pelas asserções durante a execução.
+<6> O tipo `int` também é _consistente-com_ `SupportsAbs`.
+De acordo com o https://fpy.li/15-35[_typeshed_],
+`+int.__abs__+` devolve um `int`, o que é _consistente-com_ o parametro de tipo `float` declarado na dica de tipo `is_unit` para o argumento `v`.
+
+De forma similar, podemos escrever uma versão genérica do protocolo
+`RandomPicker`, apresentado no <> do <>, que foi definido com
+um único método `pick` devolvendo `Any`.
+
+O <> mostra como criar um `RandomPicker`
+genérico, covariante no tipo devolvido por `pick`.
+
+[[ex_generic_randompick_protocol]]
+._generic_randompick.py_: definição do `RandomPicker` genérico
+====
+[source, python]
+----
+include::../code/15-more-types/protocol/random/generic_randompick.py[]
+----
+====
+<1> Declara `T_co` como `covariante`.
+<2> Isso torna `RandomPicker` genérico, com um parâmetro de tipo formal covariante.
+<3> Usa `T_co` como tipo do valor devolvido.
+
+
+O protocolo genérico `RandomPicker` pode ser covariante porque seu único
+parâmetro formal é usado em um tipo de saída.
+
+Com isso, podemos dizer que temos mais um capítulo.((("",
+startref="GTSgenstatpro15")))((("", startref="genstatpro15")))((("",
+startref="Pgenstatpro15")))((("", startref="SPgenstatpro15")))
+
+
+=== Resumo do capítulo
+
+Começamos((("gradual type system", "overview of"))) com um exemplo simples de
+uso de `@overload`, seguido por um exemplo mais complexo, que estudamos em
+detalhes: as assinaturas sobrecarregadas exigidas para anotar corretamente a
+função embutida `max`.
+
+A seguir veio o tipo especial `typing.TypedDict`. Escolhi tratar dele aqui e não
+no https://fpy.li/5[«Capítulo 5»] (vol.1), onde vimos `typing.NamedTuple`, porque `TypedDict` parece
+mas não é uma fábrica de classes; ele é meramente uma forma de acrescentar dicas
+de tipo a uma variável ou a um argumento que exige um `dict` com um conjunto
+específico de chaves do tipo string, e tipos específicos para cada chave—algo
+que acontece quando usamos um `dict` como registro, muitas vezes no contexto do
+tratamento de dados JSON. Aquela seção foi um pouco mais longa porque usar
+`TypedDict` pode levar a um falso sentimento de segurança, e eu queria mostrar
+como as checagens durante a execução e o tratamento de erros são inevitáveis
+quando tentamos criar registros estruturados estaticamente a partir de
+mapeamentos, que são dinâmicos por natureza.
+
+Então falamos sobre `typing.cast`, uma função criada para nos permitir orientar
+o checador de tipos. É importante considerar cuidadosamente quando usar `cast`,
+porque seu uso excessivo atrapalha o checador de tipos.
+
+O acesso a dicas de tipo durante a execução veio em seguida. O ponto principal
+era usar `typing.get_type_hints` em vez de ler o atributo `+__annotations__+`
+diretamente. Entretanto, aquela função pode não ser confiável para algumas
+anotações, e vimos que os mantenedores de Python ainda estão discutindo uma
+forma de tornar as dicas de tipo usáveis durante a execução, e ao mesmo tempo
+reduzir seu impacto sobre o uso de CPU e memória.
+
+A última seção foi sobre genéricos, começando com a classe genérica
+`LottoBlower`—que mais tarde aprendemos ser uma classe genérica invariante.
+Aquele exemplo foi seguido pelas definições de quatro termos básicos: tipo
+genérico, parâmetro de tipo formal, tipo parametrizado e parâmetro de tipo real.
+
+Continuamos pelo grande tópico da variância, usando máquinas bebidas e latas de
+lixo para uma cantina como exemplos da "vida real" para tipos genéricos
+invariantes, covariantes e contravariantes. Então revisamos, formalizamos e
+aplicamos aqueles conceitos a exemplos na biblioteca padrão de Python.
+
+Por fim, vimos como é definido um protocolo estático genérico, primeiro
+considerando o protocolo `typing.SupportsAbs`, e então aplicando a mesma ideia
+ao exemplo do `RandomPicker`, tornando-o mais rigoroso que o protocolo original
+do <>.
+
+[NOTE]
+====
+
+O sistema de tipos de Python é um campo imenso e em rápida expansão. Este
+capítulo não é abrangente. Escolhi me concentrar em tópicos que são
+amplamente aplicáveis, ou particularmente complexos, ou conceitualmente
+importantes, e que assim provavelmente se manterão relevantes por mais
+tempo.
+
+====
+
+
+[[more_type_hints_further_sec]]
+=== Para saber mais
+
+O((("gradual type system", "further reading on"))) sistema de tipagem estática de Python
+já era complexo quando foi originalmente projetado, e tem se tornado mais complexo a cada ano.
+A <> lista todas as PEPs que encontrei até maio de 2021.
+Seria necessário um livro inteiro para cobrir tudo.
+
+[[typing_peps_tbl]]
+.PEPs sobre dicas de tipo, com links nos títulos. PEPs com números marcados com * são importantes o suficiente para serem mencionadas no parágrafo de abertura da https://fpy.li/4a[documentação de `typing`]. Pontos de interrogação na coluna Python indica PEPs em discussão ou ainda não implementadas; "n/a" aparece em PEPs informacionais sem relação com uma versão específica de Python. Dados coletados em maio de 2021.
+[options="header"]
+[cols="3,24,4,3"]
+|=================================================================================================================================
+|PEP |título                                                                                                           |Python|ano
+|3107|https://fpy.li/pep3107[_Function Annotations_] (Anotações de Função)                                                 |3.0   |2006
+|483*|https://fpy.li/pep483[_The Theory of Type Hints_] (A Teoria das Dicas de Tipo)                                             |n/a   |2014
+|484*|https://fpy.li/pep484[_Type Hints_] (Dicas de Tipo)                                                           |3.5   |2014
+|482 |https://fpy.li/pep482[_Literature Overview for Type Hints_] (Revisão da Literatura sobre Dicas de Tipo)                                   |n/a   |2015
+|526*|https://fpy.li/pep526[_Syntax for Variable Annotations_] (Sintaxe para Anotações de Variáveis)                                      |3.6   |2016
+|544*|https://fpy.li/pep544[_Protocols: Structural subtyping (static duck typing)_] (Protocolos: subtipagem estrutural (duck typing estático))                 |3.8   |2017
+|557 |https://fpy.li/pep557[_Data Classes_] (Classes de Dados)                                                         |3.7   |2017
+|560 |https://fpy.li/pep560[_Core support for typing module and generic types_] (Suporte nativo para tipagem de módulos e tipos genéricos)                     |3.7   |2017
+|561 |https://fpy.li/pep561[_Distributing and Packaging Type Information_] (Distribuindo e Empacotando Informação de Tipo)                         |3.7   |2017
+|563 |https://fpy.li/pep563[_Postponed Evaluation of Annotations_] (Avaliação Adiada de Anotações)                                  |3.7   |2017
+|586*|https://fpy.li/pep586[_Literal Types_] (Tipos Literais)                                                        |3.8   |2018
+|585 |https://fpy.li/pep585[_Type Hinting Generics In Standard Collections_] (Dicas de Tipo para Genéricos nas Coleções Padrão)                        |3.9   |2019
+|589*|https://fpy.li/pep589[_TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys_] (TypedDict: Dicas de Tipo para Dicionários com um Conjunto Fixo de Chaves)      |3.8   |2019
+|591*|https://fpy.li/pep591[_Adding a final qualifier to typing_] (Acrescentando um qualificador final à tipagem)                                   |3.8   |2019
+|593 |https://fpy.li/pep593[_Flexible function and variable annotations_] (Anotações flexíveis para funções e variáveis)                           |?     |2019
+|604 |https://fpy.li/pep604[_Allow writing union types as X | Y_] (Permitir a definição de tipos de união como X | Y)                              |3.10  |2019
+|612 |https://fpy.li/pep612[_Parameter Specification Variables_] (Variáveis de Especificação de Parâmetros)                                    |3.10  |2019
+|613 |https://fpy.li/pep613[_Explicit Type Aliases_] (Aliases de Tipo Explícitos)                                                |3.10  |2020
+|645 |https://fpy.li/pep645[_Allow writing optional types as x?_] (Permitir a definição de tipos opcionais como x?)                                   |?     |2020
+|646 |https://fpy.li/pep646[_Variadic Generics_] (Genéricos Variádicos)                                                    |?     |2020
+|647 |https://fpy.li/pep647[_User-Defined Type Guards_] (Guardas de Tipos Definidos pelo Usuário)                                             |3.10  |2021
+|649 |https://fpy.li/pep649[_Deferred Evaluation Of Annotations Using Descriptors_] (Avaliação Adiada de Anotações Usando Descritores)                 |?     |2021
+|655 |https://fpy.li/pep655[_Marking individual TypedDict items as required or potentially-missing_] (Marcando itens individuais de TypedDict como obrigatórios ou potencialmente ausentes)|?     |2021
+|=================================================================================================================================
+
+A documentação oficial de Python mal consegue acompanhar tudo aquilo, então
+https://fpy.li/mypy[a documentação do Mypy] é uma referência essencial. 
+_Robust Python_, de Patrick Viafore (O'Reilly), é o primeiro livro com um tratamento abrangente do
+sistema de tipagem estática de Python que conheço, publicado em agosto de 2021.
+Você pode estar lendo o segundo livro sobre o assunto nesse exato instante.
+
+O tópico sutil da variância tem sua própria
+https://fpy.li/15-37[seção na PEP 484], e também é abordado na página
+https://fpy.li/15-38[_Generics_] do Mypy, bem como em sua inestimável página
+https://fpy.li/15-39[_Common Issues_] (Problemas Comuns).
+
+A https://fpy.li/pep362[_PEP 362—Function Signature Object_] (O objeto assinatura de função)
+vale a pena ler se você pretende usar o módulo `inspect`, que complementa a função `typing.get_type_hints`.
+
+Se tiver interesse na história de Python, saiba que Guido van Rossum publicou
+https://fpy.li/15-40[_Adding Optional Static Typing to Python_]
+(Acrescentando tipagem estática opcional ao Python).
+
+https://fpy.li/15-41[_Python 3 Types in the Wild: A Tale of Two Type Systems_]
+(Os tipos de Python 3 na natureza: um conto de dois sistemas de tipo) é um
+artigo científico de Ingkarat Rak-amnouykit e outros, do Rensselaer Polytechnic
+Institute e do IBM TJ Watson Research Center. O artigo avalia o uso de dicas de
+tipo em projetos de código aberto no GitHub, mostrando que a maioria dos
+projetos não as usa, e também que a maioria dos projetos que têm dicas de
+tipo aparentemente não usa um checador de tipos. Achei particularmente
+interessante a discussão das semânticas diferentes do Mypy e do _pytype_ do
+Google, onde os autores concluem que eles são "essencialmente dois sistemas de
+tipos diferentes."
+
+Dois artigos fundamentais sobre tipagem gradual são
+https://fpy.li/15-42[_Pluggable Type Systems_] (Sistemas de tipo conectáveis),
+de Gilad Bracha, e
+https://fpy.li/15-43[_Static Typing Where Possible, Dynamic Typing When Needed: The End of the Cold War Between Programming Languages_]
+(Tipagem Estática Quando Possível, Tipagem Dinâmica Quando Necessário: O Fim da
+Guerra Fria Entre Linguagens de Programação), de Eric Meijer e Peter
+Drayton.footnote:[O leitor de notas de rodapé se lembrará que dei o crédito a
+Erik Meijer pela analogia da cantina para explicar variância.]
+
+Aprendi muito lendo as partes relevantes de alguns livros sobre outras
+linguagens que implementam algumas das mesmas ideias:
+
+* https://fpy.li/15-44[_Atomic Kotlin_], de Bruce Eckel e Svetlana Isakova
+(Mindview)
+
+* https://fpy.li/15-45[_Effective Java_, 3rd ed.,], de Joshua Bloch
+(Addison-Wesley)
+
+* https://fpy.li/15-46[_Programming with Types: TypeScript Examples_], de Vlad
+Riscutia (Manning)
+
+* https://fpy.li/15-47[_Programming TypeScript_], de Boris Cherny (O'Reilly)
+
+* https://fpy.li/15-48[_The Dart Programming Language_] de Gilad Bracha
+(Addison-Wesley).footnote:[Esse livro foi escrito para o Dart 1. Há mudanças
+significativas no Dart 2, inclusive no sistema de tipos. Mesmo assim, Bracha é
+um pesquisador importante na área de design de linguagens de programação, e
+achei o livro valioso por sua perspectiva sobre o design do Dart.]
+
+Para algumas visões críticas sobre os sistemas de tipagem, recomendo os posts de Victor Youdaiken
+https://fpy.li/15-49[_Bad ideas in type theory_] (Ideias ruins em teoria dos tipos)
+e https://fpy.li/15-50[_Types considered harmful II_] (Tipos considerados nocivos II).
+
+Por fim, me surpreendi ao encontrar
+https://fpy.li/15-51[_Generics Considered Harmful_] (Genéricos Considerados Nocivos),
+de Ken Arnold, um desenvolvedor
+principal de Java desde o início, bem como co-autor das primeiras quatro edições
+do livro oficial _The Java Programming Language_ (Addison-Wesley)—com
+James Gosling, o principal criador de Java.
+
+Infelizmente, as críticas de Arnold também se aplicam ao sistema de tipagem
+estática de Python. Quando leio as muitas regras e casos especiais das PEPs de
+tipagem, sou constantemente lembrado dessa passagem do post de Arnold:
+
+<<<
+[quote]
+____
+
+O que nos traz ao problema que sempre cito para o {cpp}:
+a "exceção de enésima ordem à regra de exceção".
+
+É mais ou menos assim: "Você pode fazer x, exceto no caso y, a menos que y faça z, caso
+em que você pode se..."
+
+____
+
+Felizmente, Python tem uma vantagem crítica sobre o Java e o {cpp}: um sistema
+de tipagem opcional. Podemos silenciar os checadores de tipos e omitir as dicas
+de tipo quando se tornam muito inconvenientes.
+
+[[type_hints_in_classes_soapbox]]
+.Ponto de Vista
+****
+
+**As tocas de coelho da tipagem**
+
+Quando((("gradual type system", "Soapbox discussion")))((("Soapbox sidebars",
+"undocumented classes")))((("classes", "undocumented classes")))((("undocumented classes")))
+usamos um checador de tipos, algumas vezes somos obrigados a
+descobrir e importar classes que não precisávamos conhecer, e que nosso código
+não precisa usar—exceto para escrever dicas de tipo. Tais classes não são
+documentadas, provavelmente porque são consideradas detalhes de implementação
+pelos autores dos pacotes. Aqui estão dois exemplos da biblioteca padrão.
+
+Tive que vasculhar a imensa documentação do _asyncio_, e depois navegar o
+código-fonte de vários módulos daquele pacote para descobrir a classe
+não-documentada `TransportSocket` no módulo igualmente não documentado
+`asyncio.trsock` só para usar `cast()` no exemplo do `server.sockets`, na
+<>. Usar `socket.socket` em vez de `TransportSocket` seria
+incorreto, pois esse último não é subtipo do primeiro, como explicitado em uma
+https://fpy.li/15-52[docstring] no código-fonte.
+
+
+Caí em uma toca de coelho similar quando acrescentei dicas de tipo ao
+https://fpy.li/96[«Exemplo 13 do Capítulo 19»] (vol.3), uma demonstração simples de `multiprocessing`.
+Aquele exemplo usa objetos `SimpleQueue`,
+obtidos invocando `multiprocessing.SimpleQueue()`.
+Entretanto, não pude usar aquele nome em uma dica de tipo,
+porque `multiprocessing.SimpleQueue` não é uma classe!
+É um método vinculado da classe não documentada `multiprocessing.BaseContext`,
+que cria e devolve uma instância da classe `SimpleQueue`,
+definida no módulo não-documentado `multiprocessing.queues`.
+
+Em cada um desses casos, tive que gastar algumas horas até encontrar a
+classe não-documentada correta para importar, só para escrever uma única dica de tipo.
+Esse tipo de pesquisa é parte do trabalho quando você está escrevendo um livro.
+Mas se eu estivesse criando o código para uma aplicação,
+provavelmente evitaria tais caças ao tesouro por causa de uma única linha,
+e simplesmente colocaria `# type: ignore`.
+Algumas vezes essa é a única solução com custo-benefício positivo.
+
+**Notação de variância em outras linguagens**
+
+A variância((("Soapbox sidebars", "variance notation in other languages")))((("variance",
+"variance notation in other languages"))) é um tópico complicado,
+e a sintaxe das dicas de tipo de Python deixa a desejar.
+Esta citação direta da PEP 484 evidencia isso:
+
+[quote]
+____
+
+Covariância ou contravariância não são propriedades de uma variável de tipo,
+mas sim uma propriedade da classe genérica definida usando essa
+variável.footnote:[Veja o último parágrafo da seção
+https://fpy.li/15-37[_Covariance and Contravariance_] (Covariância e
+Contravariância) na PEP 484.]
+
+____
+
+Se esse é o caso, por que a covariância e a contravarância são declaradas com
+`TypeVar` e não na classe genérica?
+
+Os autores da PEP 484 optaram por introduzir dicas de tipo sem fazer
+qualquer modificação no interpretador.
+Em Python, todo identificador aparece pela primeira vez no código-fonte
+de um módulo através de uma
+atribuição, ou uma instrução especial como `import`, `class`, ou  `def`.
+Por isso tiveram que criar `TypeVar` para declarar uma variável de tipo
+através de uma atribuição:
+
+[source, python]
+----
+T = TypeVar('T')
+----
+
+Para não mexer no _parser_, reutilizaram o operador `[]` na sintaxe
+`Klass[T]` para genéricos—em vez da
+notação `Klass` usada em outras linguagens populares, incluindo C#, Java,
+Kotlin e TypeScript. Estas linguagens não exigem que variáveis de tipo sejam
+declaradas antes de serem usadas.
+
+Além disso, a sintaxe do Kotlin e do C# torna claro se um parâmetro de tipo é
+covariante, contravariante ou invariante exatamente onde isso faz sentido: na
+declaração de classe ou interface.
+
+Em Kotlin, poderíamos declarar a `BeverageDispenser` assim:
+
+[source, kotlin]
+----
+class BeverageDispenser {
+    // etc...
+}
+----
+
+O modificador `out` no parâmetro de tipo formal significa que `T` é um tipo de
+_output_ (saída), e portanto `BeverageDispenser` é covariante.
+Você provavelmente consegue adivinhar como `TrashCan` seria declarada:
+
+[source, kotlin]
+----
+class TrashCan {
+    // etc...
+}
+----
+
+Dado `T` como um parâmetro de tipo formal de _input_ (entrada),
+então `TrashCan` é contravariante.
+Se nem `in` nem `out` aparecem, então a classe é invariante naquele parâmetro.
+
+É fácil lembrar das regras gerais de variância (<>)
+quando `out` e `in` são usados nos parâmetros de tipo formais.
+Isso sugere uma convenção melhor para nomear de variáveis de tipo
+covariantes e contravariantes:
+
+[source, python]
+----
+T_out = TypeVar('T_out', covariant=True)
+T_in = TypeVar('T_in', contravariant=True)
+----
+
+Aí poderíamos definir as classes assim:
+
+[source, python]
+----
+class BeverageDispenser(Generic[T_out]):
+    ...
+
+class TrashCan(Generic[T_in]):
+    ...
+----
+
+Será tarde demais para adotar `T_out` e `T_in` em vez de
+`T_co` e `T_contra` que foram sugeridos na PEP 484?
+
+****
+
diff --git a/vol2/cap16.adoc b/vol2/cap16.adoc
new file mode 100644
index 0000000..f359eae
--- /dev/null
+++ b/vol2/cap16.adoc
@@ -0,0 +1,1559 @@
+[[ch_op_overload]]
+== Sobrecarga de operadores
+:example-number: 0
+:figure-number: 0
+
+[quote, James Gosling, Criador de Java]
+____
+
+Certas coisas me deixam meio dividido, como a sobrecarga de operadores. Deixei
+a sobrecarga de operadores de fora em uma decisão bastante pessoal, pois tinha
+visto gente demais abusar [deste recurso] no {cpp}.footnote:[Fonte:
+https://fpy.li/16-1[_The C Family of Languages: Interview with Dennis Ritchie,
+Bjarne Stroustrup, and James Gosling_] (A Família de Linguagens C: entrevista
+com Dennis Ritchie, Bjarne Stroustrup, e James Gosling).]
+
+____
+
+Em((("operator overloading", "infix operators")))((("infix operators"))) Python,
+podemos calcular juros compostos usando uma fórmula escrita assim:
+
+[source, python]
+----
+interest = principal * ((1 + rate) ** periods - 1)
+----
+
+Operadores que aparecem entre operandos, como `{plus}` em `1 + rate`, são
+_operadores infixos_. No Python, operadores infixos podem lidar com qualquer
+tipo arbitrário. Assim, se você está trabalhando com dinheiro de verdade, pode
+armazenar `principal`, `rate`, e `periods` como números exatos—instâncias da
+classe `decimal.Decimal` de Python. A mesma fórmula vai funcionar como escrita,
+calculando um resultado exato.
+
+Mas em Java, se você mudar de `float` para `BigDecimal`, para obter resultados
+exatos, não é mais possível usar operadores infixos, porque naquela linguagem
+eles só funcionam com tipos primitivos como `float` ou `long`.
+Veja a mesma fórmula escrita em Java para funcionar com números `BigDecimal`:
+
+[source, java]
+----
+BigDecimal interest = principal.multiply(BigDecimal.ONE.add(rate)
+                        .pow(periods).subtract(BigDecimal.ONE));
+----
+
+Está claro que operadores infixos tornam as fórmulas mais legíveis. A sobrecarga
+de operadores é necessária para suportar a notação infixa de operadores com
+tipos definidos pelo usuário ou em extensões compiladas, como os arrays da NumPy.
+
+Oferecer a sobrecarga de operadores em uma linguagem de alto nível e fácil de
+usar foi talvez uma das principais razões do grande sucesso de Python na
+ciência de dados, incluindo as aplicações científicas e financeiras.
+
+Na https://fpy.li/84[Seção 1.3.1»] (vol.1), vimos algumas implementações
+triviais de operadores em uma classe básica `Vector`.
+Escrevi os métodos `+__add__+` e `+__mul__+` no https://fpy.li/8w[«Exemplo 2 do Capítulo 1»] (vol.1)
+para demonstrar como os métodos especiais suportam a sobrecarga de operadores,
+mas deixei passar alguns problemas sutis naquelas implementações.
+Além disso, no <> do <> notamos que o
+método `+Vector2d.__eq__+` considera `True` a seguinte expressão:
+`Vector(3, 4) == [3, 4]`. Tal resultado pode fazer sentido ou não. Neste capítulo vamos
+cuidar destes problemas, e((("operator overloading", "topics covered")))
+falaremos também de:
+
+
+* Como um método de operador infixo deve indicar que não consegue tratar um operando
+* Tipagem pato e tipagem ganso para lidar com operandos de tipos diferentes
+* O comportamento especial dos operadores de comparação rica (`==`, `>`, `{lte}`, etc.)
+* O tratamento padrão de operadores de atribuição aumentada, como `{iadd}`, e como sobrecarregá-los
+
+
+=== Novidades neste capítulo
+
+A tipagem ganso((("operator overloading", "significant changes to"))) é uma
+parte fundamental de Python, mas as ABCs `numbers` não são suportadas na tipagem
+estática. Então, mudei o <> para usar tipagem pato, em vez de uma
+checagem explícita usando `isinstance` contra `numbers.Real`.footnote:[As demais
+ABCs na biblioteca padrão de Python funcionam bem para tipagem ganso e tipagem
+estática. O problema com as ABCs `numbers` é explicado na
+<>.]
+
+Na primeira edição do _Python Fluente_, tratei do operador de multiplicação de
+matrizes `@` como uma mudança futura, pois o Python 3.5 ainda estava em desenvolvimento.
+Agora o `@` está integrado ao fluxo do capítulo na <>.
+Aproveitei a tipagem ganso para tornar a implementação de `+__matmul__+`
+mais segura na primeira edição, sem comprometer sua flexibilidade.
+
+A <> agora inclui algumas novas referências—incluindo um
+post do blog de Guido van Rossum. Também inclui menções a duas bibliotecas
+que demonstram usos interessantes da sobrecarga de operadores em contextos
+não numéricos: `pathlib` e `Scapy`.
+
+
+[[op_overloading_101_sec]]
+=== Introdução à sobrecarga de operadores
+
+A sobrecarga de operadores((("operator overloading", "basics of"))) permite que
+objetos definidos pelo usuário suportem operadores infixos como `{plus}` e
+`|`, ou com operadores unários como `-` e `~`.
+De forma geral, em Python a notação de invocação de função (`f()`),
+o acesso a atributos (`p.x`) e o acesso a itens e o fatiamento (`v[0]`)
+também são operadores, mas este capítulo trata dos operadores unários e infixos.
+
+A sobrecarga de operadores tem má reputação em certos círculos. É um recurso que
+pode ser abusado, resultando em programadores confusos, bugs, e gargalos de
+desempenho inesperados. Mas se bem utilizada, possibilita APIs agradáveis de
+usar e código legível. Python alcança um bom equilíbrio entre flexibilidade,
+usabilidade e segurança, pela imposição de algumas limitações:
+
+* Não é permitido modificar o significado dos operadores para os tipos embutidos.
+* Não é permitido criar novos operadores, apenas sobrecarregar os existentes.
+* Alguns poucos operadores não podem ser sobrecarregados:
+`is`, `and`, `or` e `not` (mas os operadores `==`, `&`, `|`, e `~` podem).
+
+No <>, na classe `Vector`, já apresentamos um operador infixo:
+`==`, suportado pelo método `+__eq__+`. Neste capítulo, vamos melhorar a
+implementação de `+__eq__+` para lidar melhor com operandos de outros tipos além
+de `Vector`. Entretanto, os operadores de comparação rica (`==`, `!=`, `>`, `<`,
+`>=`, `{lte}`) são casos especiais de sobrecarga de operadores, então
+começaremos sobrecarregando quatro operadores aritméticos em `Vector`: os
+operadores unários `-` e `{plus}`, seguido pelos infixos `{plus}` e `*`.
+
+Vamos começar pelo tópico mais fácil: operadores unários.
+
+
+=== Operadores unários
+
+Na((("operator overloading", "unary operators",
+id="OOunary16")))((("unary operators", id="unary16")))
+Referência da Linguagem Python, a seção
+https://fpy.li/7n[Operações aritméticas unárias e bit a bit],
+cita três operadores unários, listados abaixo com os seus métodos especiais:
+
+`-` implementado por `+__neg__+`::
+Negativo((("__neg__"))) aritmético unário. Se `x` é
+`42` então `-x == -42`.
+
+`{plus}` implementado por `+__pos__+`::
+Positivo((("__pos__"))) aritmético unário. Em geral,
+`x == +x`, mas há alguns poucos casos em que isto não ocorre. Veja:
+<> (ao final desta seção).
+
+`~` implementado por `+__invert__+`::
+Negação((("__invert__"))) binária, ou inversão
+bit a bit de um inteiro, definida como `~x == -(x+1)`. Se `x` é `2` então
+`~x == -3`, porque a representação binária de `2` é `0010` e `-3` é `1101`.
+Veja https://fpy.li/7p[Complemento para dois] na Wikipédia para entender
+esta representação de inteiros com sinal.
+
+O capítulo Modelo de Dados na Referência da Linguagem Python_ também inclui a
+função embutida `abs()` como um operador unário. O método especial associado é
+`+__abs__+`, como já vimos.
+
+É fácil suportar operadores unários. Basta implementar o método especial
+apropriado, que receberá apenas um argumento: `self`. Use a lógica que fizer
+sentido na sua classe, mas respeite a regra geral dos operadores: sempre
+devolva um novo objeto. Em outras palavras, não modifique o receptor (`self`),
+mas crie e devolva uma nova instância do tipo adequado.
+
+No caso de `-` e `{plus}`, o resultado será provavelmente uma instância da mesma
+classe de `self`. Para o `{plus}` unário, se o receptor for imutável você
+deveria devolver `self`; caso contrário, devolva uma cópia de `self`. Para
+`abs()`, o resultado deve ser um número escalar.footnote:[Em matemática, um
+"escalar" é um número que pode ser representado por um ponto em uma linha, ou
+"escala". Em Python, instâncias de `int`, `float`, `decimal.Decimal` e
+`fraction.Fraction` são escalares, mas um `complex` não é um escalar.]
+
+Já no caso de `~`, é difícil determinar o que seria um resultado razoável se
+você não estiver lidando com bits de um número inteiro. No pacote de análise de
+dados https://fpy.li/pandas[_pandas_], o til nega condições booleanas de
+filtragem; veja exemplos na documentação do _pandas_, em
+https://fpy.li/16-4[_Boolean indexing_] (indexação booleana).
+
+Como prometido acima, vamos implementar vários novos operadores na classe
+`Vector`, do  <>. O <> mostra o método
+`+__abs__+`, que já estava no  <> do <>,
+e os novos métodos `+__neg__+` e `+__pos__+` para operadores unários.
+
+[[ex_vector_v6_unary]]
+.vector_v6.py: operadores unários `-` e `{plus}` implementados.
+====
+[source, python]
+----
+include::../code/16-op-overloading/vector_v6.py[tags=VECTOR_V6_UNARY]
+----
+====
+<1> Para computar `-v`, cria um novo `Vector` com a negação de cada componente de `self`.
+<2> Para computar `+v`, cria um novo `Vector` com cada componente de `self`.
+
+Lembre-se que instâncias de `Vector` são iteráveis, e o `+Vector.__init__+`
+recebe um argumento iterável, por isso implementações de `+__neg__+` e
+`+__pos__+` são tão simples.
+
+Não vamos implementar `+__invert__+`. Se um usuário tentar escrever `~v` para
+uma instância de `Vector`, Python vai gerar um  `TypeError` com uma mensagem
+clara: “bad operand type for unary ~: `'Vector'`” (operando inválido para o ~
+unário: `'Vector'`).
+
+O quadro a seguir trata de uma curiosidade que algum dia poderá ajudar você a
+ganhar uma aposta sobre o `{plus}` unário.
+
+[[when_plus_x_sec]]
+[role="pagebreak-before less_space"]
+.Quando x e +x não são iguais
+****
+
+Todo mundo espera que `x == +x`, e isso é verdade no Python quase todo o tempo,
+mas encontrei dois casos na biblioteca padrão onde `x != +x`.
+
+O((("decimal.Decimal class"))) primeiro caso envolve a classe `decimal.Decimal`.
+Você pode obter `x != +x` se `x` é uma instância de `Decimal`, criada em um dado
+contexto aritmético e `+x` for então calculada em um contexto com definições
+diferentes. Por exemplo, `x` é calculado em um contexto com uma determinada
+precisão, mas a precisão do contexto é modificada e daí `+x` é avaliado.
+Veja o <>.
+
+[[ex_unary_plus_decimal]]
+.Uma mudança na precisão do contexto aritmético pode fazer `x` se tornar diferente de `+x`
+====
+[source, python]
+----
+include::../code/16-op-overloading/unary_plus_decimal.py[tags=UNARY_PLUS_DECIMAL]
+----
+====
+<1> Obtém uma referência ao contexto aritmético global atual.
+<2> Define a precisão do contexto aritmético em `40`.
+<3> Computa `1/3` usando a precisão atual.
+<4> Inspeciona o resultado; há 40 dígitos após o ponto decimal.
+<5> `one_third == +one_third` é `True`.
+<6> Diminui a precisão para `28`—a precisão default de `Decimal`.
+<7> Agora `one_third == +one_third` é `False`.
+<8> Inspeciona `+one_third`; aqui há 28 dígitos após o `'.'` .
+
+O fato é que cada ocorrência da expressão `+one_third` produz uma nova instância
+de `Decimal` a partir do valor de `one_third`, mas usando a precisão do contexto
+aritmético atual.
+
+Encontrei o segundo caso onde `+x != +x+` na
+https://fpy.li/34[documentação] de `collections.Counter`. A classe `Counter`
+implementa vários operadores aritméticos, incluindo o `{plus}` infixo, para
+somar a contagem de duas instâncias de `Counter`. Entretanto, por razões
+práticas, a adição em `Counter` descarta do resultado qualquer item com contagem
+negativa ou zero. E o `{plus}` unário é um atalho para somar um `Counter` vazio,
+produzindo um novo `Counter`, que preserva só as contagens maiores que
+zero. Veja o <>.
+
+[[ex_unary_plus_counter]]
+.O + unário produz um novo `Counter`sem as contagens negativas ou zero
+====
+[source, python]
+----
+>>> ct = Counter('abracadabra')
+>>> ct
+Counter({'a': 5, 'r': 2, 'b': 2, 'd': 1, 'c': 1})
+>>> ct['r'] = -3
+>>> ct['d'] = 0
+>>> ct
+Counter({'a': 5, 'b': 2, 'c': 1, 'd': 0, 'r': -3})
+>>> +ct
+Counter({'a': 5, 'b': 2, 'c': 1})
+----
+====
+
+Como visto, `+ct` devolve um contador onde todas as contagens são maiores que
+zero.
+
+Agora voltamos à nossa programação normal.((("", startref="OOunary16")))((("",
+startref="unary16")))
+
+****
+
+[[overloading_plus_sec]]
+=== Sobrecarregando + para adição de Vector
+
+A((("operator overloading", "overloading + for vector addition",
+id="OOplus16")))((("mathematical vector operations")))((("+ operator",
+id="Plusover16")))((("vectors", "overloading + for vector addition",
+id="Voverload16"))) classe `Vector` é um tipo sequência, e a seção
+https://fpy.li/6n[Emulando tipos contêineres] da documentação oficial do Python
+diz que sequências devem suportar o operadores `{plus}` para concatenação e `\*`
+para repetição. Entretanto, aqui vamos implementar `{plus}` e `*` como operações
+matemáticas de vetores, algo um pouco mais complicado porém mais útil para um
+tipo `Vector`.
+
+[TIP]
+====
+
+Usuários que desejem concatenar ou repetir instâncias de `Vector` podem
+convertê-las para tuplas ou listas, aplicar o operador e convertê-las de
+volta—graças ao fato de `Vector` ser iterável e poder ser criado a partir de um
+iterável:
+
+[source, python]
+----
+>>> v_concat = Vector(list(v1) + list(v2))
+>>> v_repeat = Vector(tuple(v1) * 5)
+----
+
+====
+
+<<<
+Somar dois vetores euclidianos resulta em um novo vetor cujos componentes
+são as somas dos componentes correspondentes dos operandos. Ilustrando:
+
+[source, python]
+----
+>>> 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
+----
+
+E o que acontece se tentarmos somar duas instâncias de `Vector` de tamanhos
+diferentes? Poderíamos gerar um erro, mas considerando as aplicações práticas
+(tal como recuperação de informação), é melhor preencher o `Vector` menor com
+zeros. Esse é o resultado que queremos:
+
+[source, python]
+----
+>>> v1 = Vector([3, 4, 5, 6])
+>>> v3 = Vector([1, 2])
+>>> v1 + v3
+Vector([4.0, 6.0, 5.0, 6.0])
+----
+
+Dados esses requisitos básicos, podemos implementar `+__add__+`:
+
+[[ex_vector_add_t1]]
+.Método `+Vector.__add__+`, versão #1
+====
+[source, python]
+----
+    # dentro da classe Vector
+
+    def __add__(self, other):
+        pairs = itertools.zip_longest(self, other, fillvalue=0.0)  # <1>
+        return Vector(a + b for a, b in pairs)  # <2>
+----
+====
+
+<1> `pairs` é um gerador que produz tuplas `(a, b)`, onde `a` vem de `self` e
+`b` de `other`. Se `self` e `other` tiverem tamanhos diferentes, `fillvalue`
+fornece os valores que faltam no o iterável mais curto.
+
+<2> Um novo `Vector` é criado a partir de uma expressão geradora, produzindo uma
+soma para cada `(a, b)` de `pairs`.
+
+Note que `+__add__+` cria um novo `Vector`, sem modificar
+`self` ou `other`.
+
+[WARNING]
+====
+
+Métodos especiais implementando operadores unários ou infixos não devem nunca
+modificar o valor dos operandos. Espera-se que expressões com tais operandos
+produzam resultados criando novos objetos. Só operadores de atribuição
+aumentada podem modificar o primeiro operando (`self`), quando ele é mutável,
+como discutido na <>.
+
+====
+
+O <> permite somar um `Vector` a um `Vector2d` ou
+uma tupla:
+
+[[ex_vector_add_demo_mixed_ok]]
+.A versão #1 de `+Vector.__add__+` aceita objetos que não são `+Vector+`
+====
+[source, python]
+----
+>>> v1 = Vector([3, 4, 5])
+>>> 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])
+----
+====
+
+Os dois usos de `{plus}` no <> funcionam porque
+`+__add__+` usa `zip_longest(…)`, capaz de consumir qualquer iterável, e a
+expressão geradora que cria um novo `Vector` simplesmente efetua a operação
+`a + b` com os pares produzidos por `zip_longest(…)`, então qualquer iterável que produza
+números compatíveis com `float` servirá.
+Entretanto, se trocarmos a ordem dos operandos, a soma de tipos diferentes falha.
+Veja o <>.
+
+[[ex_vector_add_demo_mixed_fail]]
+.O <> falha quando o operando à esquerda não é `Vector`
+====
+[source, python]
+----
+>>> v1 = Vector([3, 4, 5])
+>>> (10, 20, 30) + v1
+Traceback (most recent call last):
+  File "", line 1, in 
+TypeError: can only concatenate tuple (not "Vector") to tuple
+>>> from vector2d_v3 import Vector2d
+>>> v2d = Vector2d(1, 2)
+>>> v2d + v1
+Traceback (most recent call last):
+  File "", line 1, in 
+TypeError: unsupported operand type(s) for +: 'Vector2d' and 'Vector'
+----
+====
+
+Para suportar operações envolvendo objetos de tipos diferentes, Python
+implementa um mecanismo especial de despacho para os métodos especiais de
+operadores infixos.
+
+Dada a expressão `a + b`, o interpretador executará os
+seguintes passos (veja também a <>):
+
+. Se `a` implementa `+__add__+`, Python invoca `+a.__add__(b)+` e devolve o
+resultado, a menos que seja `NotImplemented`.
+
+. Se `a` não implementa `+__add__+`, ou a chamada `+a.__add__(b)+` devolve
+`NotImplemented`, Python verifica se `b` implementa `+__radd__+`, e então invoca
+`+b.__radd__(a)+` e devolve o resultado, a menos que seja `NotImplemented`.
+
+. Se `b` não implementa `+__radd__+`, ou a chamada `+b.__radd__(a)+`
+devolve `NotImplemented`, Python gera um `TypeError` com a mensagem
+"unsupported operand types" (tipos de operandos não suportados).
+
+[[operator_flowchart]]
+.Fluxograma para computar `a + b` com `+__add__+` e `+__radd__+`.
+image::../images/flpy_1601.png[Fluxograma de operador]
+
+<<<
+
+[TIP]
+====
+O método `+__radd__+` é chamado de variante "reversa" ou "refletida"
+de `+__add__+`. Adotei o termo geral "métodos especiais reversos".
+A documentação de Python usa os dois termos. O capítulo
+https://fpy.li/dtmodel[«Modelo de Dados«]
+usa "refletido", mas em
+https://fpy.li/16-7[«Implementando operações aritméticas»],
+a documentação menciona métodos "adiante" (_forward_)
+e "reverso" (_reverse_), uma terminologia que considero
+melhor, pois "adiante" e "reverso" descrevem sentidos opostos,
+enquanto o oposto de "refletido" não é tão evidente.
+====
+
+
+Assim, para fazer as somas de tipos diferentes no
+<> funcionarem, precisamos implementar o método
+`+Vector.__radd__+`, que Python vai invocar como alternativa, se o operando à
+esquerda não implementar `+__add__+`, ou se implementar mas devolver
+`NotImplemented`, indicando que não sabe como tratar o operando à direita.
+
+[WARNING]
+====
+
+Não confunda `NotImplemented` com `NotImplementedError`. O primeiro é um valor
+_singleton_ especial, que um método especial de operador infixo deve devolver
+para informar o interpretador que não consegue tratar um dado operando.
+
+Por sua vez, `NotImplementedError` é uma exceção que um método abstrato pode
+levantar para avisar que subclasses devem sobrescrever este método. Esta exceção
+é antiga no Python; atualmente a melhor forma de marcar um método abstrato é
+usar o decorador `@abc.abstractmethod`.
+
+====
+
+A implementação viável mais simples de `+__radd__+` aparece no <>.
+
+[[ex_vector_add_t2]]
+.Os métodos `+__add__+` e `+__radd__+` de `Vector`
+====
+[source, python]
+----
+    # dentro da classe vetor
+    def __add__(self, other):  # <1>
+        pairs = itertools.zip_longest(self, other, fillvalue=0.0)
+        return Vector(a + b for a, b in pairs)
+
+    def __radd__(self, other):  # <2>
+        return self + other
+----
+====
+
+<1> Nenhuma mudança no `+__add__+` do <>; repeti o código aqui
+porque é invocado por `+__radd__+`.
+
+<2> `+__radd__+` apenas delega para `+__add__+`.
+
+Muitas vezes, `+__radd__+` pode ser simples assim: apenas a invocação do
+operador apropriado, delegando para `+__add__+` neste caso. Isso se aplica para
+qualquer operador comutativo. O `{plus}` é comutativo quando lida com números ou
+com nossos vetores, mas não é comutativo ao concatenar sequências no Python.
+
+Se `+__radd__+` apenas invoca `+__add__+`, aqui está uma forma mais eficiente de
+obter o mesmo efeito:
+
+[source, python]
+----
+    def __add__(self, other):
+        pairs = itertools.zip_longest(self, other, fillvalue=0.0)
+        return Vector(a + b for a, b in pairs)
+
+    __radd__ = __add__
+----
+
+Os métodos no <> funcionam com objetos `Vector` ou com
+qualquer iterável com itens numéricos, tal como um `Vector2d`, uma tupla de
+inteiros ou um `array` de números de ponto flutuante. Mas se alimentado com um
+objeto não-iterável, `+__add__+` gera uma exceção com uma mensagem não muito
+útil, como no <>.
+
+[[ex_vector_error_iter]]
+.O método `+Vector.__add__+` precisa de operandos iteráveis
+====
+[source, python]
+----
+>>> v1 + 1
+Traceback (most recent call last):
+  File "", line 1, in 
+  File "vector_v6.py", line 328, in __add__
+    pairs = itertools.zip_longest(self, other, fillvalue=0.0)
+TypeError: zip_longest argument #2 must support iteration
+----
+====
+
+E pior ainda, recebemos uma mensagem enganosa se um operando for iterável,
+mas seus itens não puderem ser somados aos itens `float` no `Vector`. Veja
+o <>.
+
+[[ex_vector_error_iter_not_add]]
+.O método `+Vector.__add__+` exige um iterável com itens numéricos
+====
+[source, python]
+----
+>>> v1 + 'ABC'
+Traceback (most recent call last):
+  File "", line 1, in 
+  File "vector_v6.py", line 329, in __add__
+    return Vector(a + b for a, b in pairs)
+  File "vector_v6.py", line 243, in __init__
+    self._components = array(self.typecode, components)
+  File "vector_v6.py", line 329, in 
+    return Vector(a + b for a, b in pairs)
+TypeError: unsupported operand type(s) for +: 'float' and 'str'
+----
+====
+
+Tentei somar um `Vector` a uma `str`, mas a mensagem reclama de `float` e `str`.
+
+Na verdade, os problemas no <> e no
+<> são mais profundos que meras mensagens de erro
+obscuras: se um método especial de operando não é capaz de devolver um resultado
+válido por incompatibilidade de tipos, ele tem que devolver `NotImplemented` e
+não gerar um `TypeError`. Ao devolver `NotImplemented`, a porta fica aberta para
+o outro operando executar a operação, quando Python tentar invocar o método
+reverso em sua classe.
+
+No espírito da tipagem pato, não vamos testar o tipo do operando `other` ou o
+tipo de seus elementos. Vamos capturar as exceções e devolver `NotImplemented`.
+Se o interpretador ainda não tiver invertido os operandos, tentará isso em seguida.
+Se a invocação do método reverso devolver `NotImplemented`, então Python irá
+gerar um `TypeError` com uma mensagem de erro padrão "unsupported operand
+type(s) for +: 'Vector' and 'str'” (_tipos de operandos não suportados para +:
+`Vector` e `str`_)
+
+Veja a implementação final dos métodos especiais de adição de `Vector`.
+
+[[ex_vector_v6]]
+.vector_v6.py: métodos do operador `{plus}` adicionados a vector_v5.py (<> do <>)
+====
+[source, python]
+----
+include::../code/16-op-overloading/vector_v6.py[tags=VECTOR_V6_ADD]
+----
+====
+
+Observe que agora `+__add__+` captura um `TypeError` e devolve `NotImplemented`.
+
+[WARNING]
+====
+
+Se um método de operador infixo gera uma exceção, ele interrompe o algoritmo de
+despacho do operador. No caso específico de `TypeError`, geralmente é melhor
+capturar esta exceção e devolver `NotImplemented`. Isto permite que o
+interpretador tente chamar o método reverso do segundo operando.
+
+====
+
+Agora que já sobrecarregamos o operador `{plus}` com segurança, implementando `+__add__+` e `+__radd__+`, vamos enfrentar outro operador infixo: `*`.((("", startref="OOplus16")))((("", startref="Plusover16")))((("", startref="Voverload16")))
+
+[[overloading_mul_sec]]
+=== Sobrecarregando * para multiplicação por escalar
+
+O((("operator overloading", "overloading * for scalar multiplication",
+id="OOscalar16")))((("* (star) operator", id="starover16")))((("star (*)
+operator", id="staroverb16")))((("multiplication, scalar", id="Mscalar16"))) que
+significa `Vector([1, 2, 3]) * x`? Se `x` é um número escalar, isto é uma
+"multiplicação por escalar", e o resultado deve ser um novo `Vector` com cada
+componente multiplicado por `x`—também conhecida como multiplicação elemento a
+elemento (_elementwise multiplication_):
+
+[source, python]
+----
+>>> v1 = Vector([1, 2, 3])
+>>> v1 * 10
+Vector([10.0, 20.0, 30.0])
+>>> 11 * v1
+Vector([11.0, 22.0, 33.0])
+----
+
+[NOTE]
+====
+
+Outro tipo de multiplicação envolvendo vetores é o produto escalar
+(_dot product_). Os operandos de um produto escalar são dois vetores,
+e o resultado é um número escalar (não um vetor).
+É como uma multiplicação de matrizes, considerando um
+vetor como uma matriz de 1 × N e o outro como uma matriz de N × 1.
+Implementaremos o produto escalar em `Vector` na
+<>.
+
+====
+
+Voltando à nossa multiplicação por escalar, começamos novamente com os métodos
+`+__mul__+` e `+__rmul__+` mais simples possíveis que possam funcionar:
+
+[source, python]
+----
+    # dentro da classe Vector
+
+    def __mul__(self, scalar):
+        return Vector(n * scalar for n in self)
+
+    def __rmul__(self, scalar):
+        return self * scalar
+----
+
+Estes métodos funcionam, exceto quando recebem operandos incompatíveis. +
+O argumento `scalar` precisa ser um número que, quando multiplicado por um
+`float`, produz outro `float` (porque nossa classe `Vector` armazena
+um `array` de números de ponto flutuante). Então um `complex` não serve,
+mas pode ser um `int`, um `bool` (`bool` é subclasse  de `int`)
+ou até uma instância de `fractions.Fraction`. No <>, o método
+`+__mul__+` não faz nenhuma checagem de tipos explícita com `scalar`. Em vez
+disso, o converte para `float`, e devolve `NotImplemented` se a conversão
+falhar. É mais um exemplo prático de tipagem pato.
+
+[[ex_vector_v7]]
+.vector_v7.py: métodos do operador `*` adicionados
+====
+[source, python]
+----
+class Vector:
+    typecode = 'd'
+
+    def __init__(self, components):
+        self._components = array(self.typecode, components)
+
+    # métodos omitidos; código completo em https://fpy.li/code    
+
+    def __mul__(self, scalar):
+        try:
+            factor = float(scalar)
+        except TypeError:  # <1>
+            return NotImplemented  # <2>
+        return Vector(n * factor for n in self)
+
+    def __rmul__(self, scalar):
+        return self * scalar  # <3>
+----
+====
+<1> Se `scalar` não pode ser convertido para `float`...
+
+<2> ...não temos como lidar com ele, então devolvemos `NotImplemented`, para
+permitir ao Python tentar `+__rmul__+` no operando `scalar`.
+
+<3> Neste exemplo, `+__rmul__+` funciona bem apenas executando `self * scalar`,
+que delega a operação para o método `+__mul__+`.
+
+Com o <>, é possível multiplicar um `Vector` por valores escalares
+de tipos numéricos comuns e não tão comuns:
+
+[source, python]
+----
+>>> v1 = Vector([1.0, 2.0, 3.0])
+>>> 14 * v1
+Vector([14.0, 28.0, 42.0])
+>>> v1 * True
+Vector([1.0, 2.0, 3.0])
+>>> from fractions import Fraction
+>>> v1 * Fraction(1, 3)
+Vector([0.3333333333333333, 0.6666666666666666, 1.0])
+----
+
+Agora que podemos multiplicar `Vector` por valores escalares, vamos ver como
+implementar o produto de um `Vector` por outro `Vector`.
+
+[NOTE]
+====
+
+Na primeira edição de _Python Fluente_, usei tipagem ganso no <>:
+checava o argumento `scalar` de `+__mul__+` com `isinstance(scalar,
+numbers.Real)`. Agora evito usar as ABCs de `numbers`, por não serem
+suportadas pelas anotações de tipo introduzidas na PEP 484. Usar durante a
+execução tipos que não podem ser também checados de forma estática me parece uma
+má ideia.
+
+Outra alternativa seria checar com o protocolo `typing.SupportsFloat`, que vimos
+na <>. Escolhi usar tipagem pato naquele exemplo
+por considerar que pythonistas fluentes devem se sentir confortáveis com esse
+padrão de programação.
+
+Mas `+__matmul__+`, no <>, que é novo e foi escrito para
+essa segunda edição, é um bom exemplo de tipagem ganso.((("",
+startref="starover16")))((("", startref="staroverb16")))((("",
+startref="OOscalar16")))((("", startref="Mscalar16")))
+
+====
+
+[[matmul_operator_sec]]
+=== Usando @ como operador infixo
+
+O símbolo `@`((("operator overloading", "using @ as infix operator",
+id="OOatsign16")))((("@ sign", id="atinfix16")))((("infix operators",
+id="infixop16"))) é o prefixo de decoradores de função, mas desde 2015
+também pode ser usado como um operador infixo.
+
+Por muitos anos, o produto escalar (_dot product_) era escrito
+como `numpy.dot(a, b)` na biblioteca NumPy.
+A notação de invocação de função faz com que fórmulas mais longas sejam difíceis
+de traduzir da notação matemática para Python,footnote:[Veja o
+<> para uma discussão deste problema.] então a comunidade de
+computação numérica fez campanha pela
+https://fpy.li/pep465[_PEP 465—A dedicated infix operator for matrix multiplication_]
+(Um operador infixo dedicado para multiplicação de matrizes),
+que foi implementada no Python 3.5. Hoje podemos escrever `a @ b`
+para computar o produto de dois arrays da NumPy.
+
+O operador `@` é suportado pelos métodos especiais `+__matmul__+`,
+`+__rmatmul__+` e `+__imatmul__+`, cujos nomes derivam de "matrix
+multiplication". Até o Python 3.10, estes métodos não são usados em lugar algum
+na biblioteca padrão, mas são reconhecidos pelo interpretador desde o Python
+3.5, então nós e os desenvolvedores da NumPy podemos implementar o operador
+`@` em nossas classes. O analisador sintático de Python foi modificado para
+aceitar o novo operador, pois `a @ b` era um erro de sintaxe até o Python 3.4.
+
+Estes testes simples mostram como `@` deve funcionar com instâncias de `Vector`:
+
+[source, python]
+----
+>>> 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'
+----
+
+O resultado de `va @ vz` no exemplo acima é o mesmo que obtemos no NumPy
+fazendo o produto escalar de arrays com os mesmos valores:
+
+[source, python]
+----
+>>> import numpy as np
+>>> np.array([1, 2, 3]) @ np.array([5, 6, 7])
+38
+----
+
+
+O <> mostra o código dos métodos especiais relevantes na classe `Vector`.
+
+
+[[ex_vector_v7_matmul]]
+.vector_v7.py: métodos para o operador `@`
+====
+[source, python]
+----
+class Vector:
+    # vários métodos omitidos nesta listagem
+
+    def __matmul__(self, other):
+        if (isinstance(other, abc.Sized) and  # <1>
+            isinstance(other, abc.Iterable)):
+            if len(self) == len(other):  # <2>
+                return sum(a * b for a, b in zip(self, other))  # <3>
+            else:
+                raise ValueError('@ requires vectors of equal length.')
+        else:
+            return NotImplemented
+
+    def __rmatmul__(self, other):
+        return self @ other
+----
+====
+<1> Ambos os operandos precisam implementar `+__len__+` e `+__iter__+`...
+<2> ...e ter o mesmo tamanho, para permitir...
+<3> ...uma linda aplicação de `sum`, `zip` e uma expressão geradora.
+
+[[zip_strict_tip]]
+[TIP]
+====
+
+Desde o Python 3.10, a função `zip` aceita um argumento opcional apenas
+nomeado, `strict`. Quando `strict=True`, a função gera um `ValueError`
+se um iterável termina antes de outro. O default é `False`. Este comportamento
+se alinha à filosofia de https://fpy.li/16-8[«falhar rápido»] de Python.
+No <>, poderíamos trocar o `if` interno por um `try/except
+ValueError` e acrescentar `strict=True` à invocação de `zip`.
+Neste caso específico, como `self` e `other` suportam `+__len__+`,
+prefiro o teste explícito com `if`, por clareza.
+O `strict` é mais útil quando o `zip` vai lidar com iteradores,
+que não têm `+__len__+`.
+
+====
+
+O <> é um bom exemplo prático de tipagem ganso. Não usamos
+`isinstance(other, Vector)`, porque queremos oferecer mais flexibilidade para os
+usuários. Suportamos operandos que sejam instâncias de `abc.Sized` e
+`abc.Iterable`. Estas duas ABCs implementam o `+__subclasshook__+`, portanto
+qualquer objeto que forneça `+__len__+` e `+__iter__+` satisfaz nosso teste—não
+há necessidade de criar subclasses concretas dessas ABCs ou sequer registrar-se
+com elas, como explicado na <>. Em particular, nossa classe
+`Vector` não é subclasse nem de `abc.Sized` nem de `abc.Iterable`, mas passa os
+testes de `isinstance` contra aquelas ABCs, pois implementa os métodos
+necessários.
+
+Vamos revisar os operadores aritméticos suportados pelo Python antes de
+mergulhar na categoria especial dos operadores de comparação rica
+(<>).((("", startref="atinfix16")))((("", startref="OOatsign16")))
+
+=== Resumindo os operadores aritméticos
+
+Ao implementar `{plus}`, `*`, e `@`, vimos((("operator overloading", "infix
+operator method names"))) os padrões de programação mais comuns para operadores
+infixos. As técnicas descritas são aplicáveis a todos os operadores listados na
+<> (os operadores de atribuição aritmética serão tratados na
+<>).
+
+[[infix_operator_names_tbl]]
+.Nomes dos métodos de operadores infixos (os operadores internos são usados para atribuição aumentada; operadores de comparação estão na <>)
+[options="header"]
+[cols="18,25,27,27,50"]
+|=================================================================================================
+| op  | direto   | reverso   | interno  | descrição
+| `{plus}`       | `+__add__+`   | `+__radd__+`  | `+__iadd__+`  | Adição ou concatenação
+| `-`       | `+__sub__+`   | `+__rsub__+`  | `+__isub__+`  | Subtração
+| `*`       | `+__mul__+`   | `+__rmul__+`  | `+__imul__+`  | Multiplicação ou repetição
+| `/`       | `+__truediv__+`   | `+__rtruediv__+`  | `+__itruediv__+`  | Divisão exata
+| `//`      | `+__floordiv__+`  | `+__rfloordiv__+`     | `+__ifloordiv__+`     | Divisão inteira
+| `%`       | `+__mod__+`       | `+__rmod__+`  | `+__imod__+`  | Módulo (resto)
+| `divmod()`| `+__divmod__+`    | `+__rdivmod__+`   | `+__idivmod__+`   | Devolve uma tupla com o quociente da divisão inteira e o módulo
+| `**`, `pow()`   | `+__pow__+`   | `+__rpow__+`  | `+__ipow__+`  | Exponenciaçãofootnote:[`pow` pode receber um terceiro argumento opcional, `modulo`: `pow(a, b, modulo)`, também suportado pelos métodos especiais quando invocados diretamente (por exemplo, `+a.__pow__(b, modulo)+`).]
+| `@`       | `+__matmul__+`    | `+__rmatmul__+`   | `+__imatmul__+`   | Multiplicação de matrizes
+| `&`       | `+__and__+`   | `+__rand__+`  | `+__iand__+`  | E binário (bit a bit)
+| \|        | `+__or__+`    | `+__ror__+`   | `+__ior__+`   | OU binário (bit a bit)
+| `^`       | `+__xor__+`   | `+__rxor__+`  | `+__ixor__+`  | XOR binário (bit a bit)
+| `<<`      | `+__lshift__+`    | `+__rlshift__+`   | `+__ilshift__+`   | Deslocamento de bits para a esquerda
+| `>>`      | `+__rshift__+`    | `+__rrshift__+`   | `+__irshift__+`   | Deslocamento de bits para a direita
+|=================================================================================================
+
+
+Operadores de comparação rica usam regras diferentes.((("", startref="infixop16")))
+
+
+[[rich_comp_op_sec]]
+=== Operadores de comparação rica
+
+O((("operator overloading", "rich comparison operators",
+id="OOrich16")))((("rich comparison operators", id="richcomp16")))((("comparison operators",
+id="comop16"))) tratamento
+dos operadores de comparação rica `==`, `!=`, `>`, `<`, `>=` e `{lte}` pelo
+interpretador Python é similar ao que já vimos, com uma importante diferença:
+não existem métodos reversos com o prefixo `+__r…__+`.
+Os mesmos métodos são usados para invocações diretas ou reversas do
+operador. As regras estão resumidas na <>.
+
+[[reversed_rich_comp_op_tbl]]
+.Comparação rica: a última coluna mostra o resultado quando as tentativas devolvem `NotImplemented` ou o operando não implementa o método.
+[options="header"]
+[cols="22,13,23,23,50"]
+|=================================================================================================
+| grupo    | op | invocação direta | invocação reversa | quando não implementado
+| *igualdade* | `a == b`       | `+a.__eq__(b)+`       | `+b.__eq__(a)+`       | Devolve `id(a) == id(b)`
+|          | `a != b`       | `+a.__ne__(b)+`       | `+b.__ne__(a)+`       | Devolve `not (a == b)`
+| *ordenação* | `a > b`        | `+a.__gt__(b)+`       | `+b.__lt__(a)+`       | Levanta `TypeError`
+|          | `a < b`        | `+a.__lt__(b)+`       | `+b.__gt__(a)+`       | Levanta `TypeError`
+|          | `a >= b`       | `+a.__ge__(b)+`       | `+b.__le__(a)+`       | Levanta `TypeError`
+|          | `a {lte} b`       | `+a.__le__(b)+`       | `+b.__ge__(a)+`       | Levanta `TypeError`
+|=================================================================================================
+
+Por exemplo, no caso de `==`, tanto a chamada direta quanto a reversa invocam
+`+__eq__+`, apenas permutando os argumentos. Uma chamada direta a `+__gt__+`
+pode ser seguida de uma chamada reversa a `+__lt__+`, com os argumentos
+permutados.
+
+Nos casos de `==` e `!=`, se o método não existe no segundo operando,
+ou devolve `NotImplemented`, os métodos correspondentes `+__eq__+` e `+__ne__+`
+herdados da classe `object` comparam os IDs dos objetos, então não ocorre `TypeError`.
+
+Considerando estas regras, vamos revisar e aperfeiçoar o comportamento do método
+`+Vector.__eq__+`, escrito assim no __vector_v5.py__ (<> do <>):
+
+[source, python]
+----
+class Vector:
+    # várias linhas omitidas
+
+    def __eq__(self, other):
+        return (len(self) == len(other) and
+                all(a == b for a, b in zip(self, other)))
+----
+
+Este método produz os resultados do <>.
+
+[[eq_initial_demo]]
+.Comparando um `Vector` a um `Vector`, a um `Vector2d`, e a uma tupla
+====
+[source, python]
+----
+>>> va = Vector([1.0, 2.0, 3.0])
+>>> vb = Vector(range(1, 4))
+>>> va == vb  # <1>
+True
+>>> vc = Vector([1, 2])
+>>> from vector2d_v3 import Vector2d
+>>> v2d = Vector2d(1, 2)
+>>> vc == v2d  # <2>
+True
+>>> t3 = (1, 2, 3)
+>>> va == t3  # <3>
+True
+----
+====
+<1> Duas instâncias de `Vector` com componentes numéricos iguais são iguais.
+<2> Um `Vector` e um `Vector2d` também são iguais se seus componentes são iguais.
+<3> Um `Vector` também é considerado igual a uma tupla ou qualquer sequência
+com itens escalares de valor igual.
+
+O último resultado no <> pode ser indesejável. Queremos mesmo
+que um `Vector` seja considerado igual a uma tupla contendo os mesmos números?
+Não tenho uma regra fixa sobre isso; depende do contexto da aplicação. O "Zen of
+Python" diz:
+
+[quote]
+____
+Em face da ambiguidade, rejeite a tentação de adivinhar.
+____
+
+Liberalidade excessiva na avaliação de operandos pode levar a resultados
+surpreendentes, e programadores odeiam surpresas.
+
+Buscando inspiração no próprio Python, vemos que `[1, 2] == (1, 2)` é `False`.
+Então, seremos conservadores e faremos checagem de tipos. Se o
+segundo operando for uma instância de `Vector` (ou uma instância de uma
+subclasse de `Vector`), então usaremos a mesma lógica do `+__eq__+` atual. Caso
+contrário, devolvemos `NotImplemented` e deixamos Python cuidar do caso. Veja o
+<>.
+
+[[ex_vector_v8_eq]]
+.vector_v8.py: `+__eq__+` aperfeiçoado na classe `Vector`
+====
+[source, python]
+----
+include::../code/16-op-overloading/vector_v8.py[tags=VECTOR_V8_EQ]
+----
+====
+
+<1> Se o operando `other` é uma instância de `Vector` (ou de uma subclasse de
+`Vector`), executa a comparação como antes.
+
+<2> Caso contrário, devolve `NotImplemented`.
+
+Rodando os testes do <> com o novo `+Vector.__eq__+` do
+<>, obtemos os resultados do <>.
+
+[[eq_demo_new_eq]]
+.Mesmas comparações do <>: o último resultado mudou
+====
+[source, python]
+----
+>>> va = Vector([1.0, 2.0, 3.0])
+>>> vb = Vector(range(1, 4))
+>>> va == vb  # <1>
+True
+>>> vc = Vector([1, 2])
+>>> from vector2d_v3 import Vector2d
+>>> v2d = Vector2d(1, 2)
+>>> vc == v2d  # <2>
+True
+>>> t3 = (1, 2, 3)
+>>> va == t3  # <3>
+False
+----
+====
+<1> Mesmo resultado de antes, como esperado.
+<2> Mesmo resultado de antes, mas por quê? Explicação a seguir.
+<3> Resultado diferente; era o que queríamos. Mas por que isso funciona?
+Continue lendo...
+
+Dos três resultados no <>, o primeiro não é novidade, mas os
+dois últimos foram causados por `+__eq__+` devolver `NotImplemented` no
+<>. Eis o que acontece no exemplo com um `Vector` e um
+`Vector2d`, `vc == v2d`, passo a passo:
+
+. Para avaliar `vc == v2d`, Python invoca `Vector.__eq__(vc, v2d)`.
+
+. `+Vector.__eq__(vc, v2d)+` verifica que `v2d` não é um `Vector` e devolve
+`NotImplemented`.
+
+. Diante do resultado `NotImplemented`, Python tenta `+Vector2d.__eq__(v2d,
+vc)+`.
+
+. `+Vector2d.__eq__(v2d, vc)+` transforma os dois operandos em tuplas e os
+compara: o resultado é `True` (o código de `+Vector2d.__eq__+` está no
+<> do <>).
+
+Já para a comparação `va == t3`, entre `Vector` e `tuple` no <>,
+os passos são:
+
+. Para avaliar `va == t3`, Python invoca `+Vector.__eq__(va, t3)+`.
+
+. `+Vector.__eq__(va, t3)+` verifica que `t3` não é um `Vector` e devolve
+`NotImplemented`.
+
+. Diante do resultado `NotImplemented`, Python tenta `+tuple.__eq__(t3,
+va)+`.
+
+. `+tuple.__eq__(t3, va)+` não tem a menor ideia do que seja um `Vector`, então
+devolve `NotImplemented`.
+
+. No caso especial de `==`, se a chamada reversa devolve `NotImplemented`,
+Python compara os IDs dos objetos, como último recurso.
+
+<<<
+Não precisamos implementar `+__ne__+` para `!=`, pois o comportamento
+alternativo do `+__ne__+` herdado de `object` nos serve: quando `+__eq__+` é
+definido e não devolve `NotImplemented`, `+__ne__+` devolve a negação booleana
+do resultado de `+__eq__+`.
+
+Em outras palavras, dados os mesmos objetos que usamos no <>, os
+resultados de `!=` são consistentes:
+
+[source, python]
+----
+>>> va != vb
+False
+>>> vc != v2d
+False
+>>> va != (1, 2, 3)
+True
+----
+
+O `+__ne__+` herdado de `object` funciona como o código abaixo (mas
+o original é escrito em C):footnote:[A lógica de `+object.__eq__+` e
+`+object.__ne__+` está na função `object_richcompare` em
+https://fpy.li/16-9[_Objects/typeobject.c_], no código-fonte do CPython.]
+
+[source, python]
+----
+    def __ne__(self, other):
+        eq_result = self == other
+        if eq_result is NotImplemented:
+            return NotImplemented
+        else:
+            return not eq_result
+----
+
+Vimos o básico da sobrecarga de operadores infixos.
+Agora veremos uma categoria diferente: os operadores de atribuição
+aumentada.((("", startref="comop16")))((("", startref="richcomp16")))((("",
+startref="OOrich16")))
+
+
+[[augmented_assign_ops]]
+=== Operadores de atribuição aumentada
+
+Nossa((("operator overloading", "augmented assignment operators",
+id="OOaugmented16")))((("augmented assignment operators",
+id="augmented16")))
+classe `Vector` já suporta os operadores de atribuição aumentada `{iadd}` e `*=`.
+Isso acontece porque a atribuição aumentada trabalha com sequências imutáveis
+criando novas instâncias e re-vinculando a variável à esquerda do operador.
+
+O <> os mostra em ação.
+
+<<<
+
+[[eq_demo_augm_assign_immutable]]
+.Usando `{iadd}` e `*=` com instâncias de `Vector`
+====
+[source, python]
+----
+>>> v1 = Vector([1, 2, 3])
+>>> v1_alias = v1  # <1>
+>>> id(v1)  # <2>
+4302860128
+>>> v1 += Vector([4, 5, 6])  # <3>
+>>> v1  # <4>
+Vector([5.0, 7.0, 9.0])
+>>> id(v1)  # <5>
+4302859904
+>>> v1_alias  # <6>
+Vector([1.0, 2.0, 3.0])
+>>> v1 *= 11  # <7>
+>>> v1  # <8>
+Vector([55.0, 77.0, 99.0])
+>>> id(v1)
+4302858336
+----
+====
+
+<1> Cria um alias, para podermos inspecionar o objeto `Vector([1, 2, 3])` mais
+tarde.
+
+<2> Verifica o `id` do `Vector` inicial, vinculado a `v1`.
+
+<3> Executa a adição aumentada.
+
+<4> O resultado esperado...
+
+<5> ...mas foi criado um novo `Vector`.
+
+<6> Inspeciona `v1_alias` para confirmar que o `Vector` original não foi
+alterado.
+
+<7> Executa a multiplicação aumentada.
+
+<8> Novamente, o resultado é o esperado, mas um novo `Vector` foi criado.
+
+Se uma classe não implementa os métodos internos listados na
+<>, os operadores de atribuição aumentada funcionam
+como açúcar sintático: `+a += b+` é avaliado exatamente como `+a = a + b+`. Este
+é o comportamento esperado para tipos imutáveis, e se você fornecer `+__add__+`,
+então `{iadd}` funcionará sem qualquer código adicional.
+
+<<<
+Entretanto, se você implementar um método interno tal como `+__iadd__+`,
+aquele método será chamado para computar o resultado de `a += b`. Como indica
+seu nome, espera-se que esses operadores modifiquem internamente o operando da
+esquerdafootnote:[NT: O prefixo "i" nos nomes destes métodos se refere a
+_in-place_, traduzido como "interno" na documentação brasileira oficial de Python.],
+e não criem um novo objeto como resultado.
+
+[WARNING]
+====
+
+Nunca devemos implementar métodos internos para atribuição aumentada
+em tipos imutáveis como nossa classe `Vector`. Pode ser óbvio, mas vale a pena
+enfatizar. Por este motivo, deixaremos de lado o tema dos vetores nos próximos
+exemplos.
+
+====
+
+Para mostrar o código de um método interno de atribuição aumentada, vamos
+estender a classe `BingoCage` do <> do <> para implementar
+`+__add__+` e `+__iadd__+`.
+
+Vamos chamar a subclasse de `AddableBingoCage`. Os doctests da classe
+(<>)
+mostram o comportamento esperado do operador `{plus}`.
+
+[[demo_addable_bingo_add]]
+.O operador `{plus}` cria uma nova instância de `AddableBingoCage`
+====
+[source, python]
+----
+include::../code/16-op-overloading/bingoaddable.py[tags=ADDABLE_BINGO_ADD_DEMO]
+----
+====
+
+<1> Cria uma instância de `globe` com cinco itens (cada uma das `vowels`).
+
+<2> Extrai um dos itens, e verifica que é uma das `vowels`.
+
+<3> Confirma que `globe` tem agora quatro itens.
+
+<4> Cria uma segunda instância, com três itens.
+
+<5> Cria uma terceira instância pela soma das duas anteriores. Esta instância
+tem sete itens.
+
+<6> Tentar adicionar uma `AddableBingoCage` a uma `list` falha com um
+`TypeError`. A mensagem de erro é produzida pelo interpretador de Python quando
+nosso método `+__add__+` devolve `NotImplemented`.
+
+Como uma `AddableBingoCage` é mutável, o <> mostra como
+ela funcionará quando implementarmos `+__iadd__+`.
+
+[[demo_addable_bingo_iadd]]
+.Uma `AddableBingoCage` existente pode ser carregada com `{iadd}` (continuando do <>)
+====
+[source, python]
+----
+include::../code/16-op-overloading/bingoaddable.py[tags=ADDABLE_BINGO_IADD_DEMO]
+----
+====
+
+<1> Cria um alias para podermos checar a identidade do objeto mais tarde.
+
+<2> `globe` tem quatro itens aqui.
+
+<3> Uma instância de  `AddableBingoCage` pode receber itens de outra instância
+da mesma classe.
+
+<4> O operador à direita de `{iadd}` também pode ser qualquer iterável.
+
+<5> Durante todo esse exemplo, `globe` sempre se refere ao mesmo objeto que
+`globe_orig`.
+
+<6> Tentar adicionar um não-iterável a uma `AddableBingoCage` falha com uma
+mensagem de erro apropriada.
+
+Observe que o operador `{iadd}` é mais liberal que `{plus}` quanto ao segundo
+operando. Com `{plus}`, queremos que ambos os operandos sejam do mesmo tipo
+(neste caso, `AddableBingoCage`), pois se aceitássemos tipos diferentes, isso
+poderia causar confusão quanto ao tipo do resultado, violando a propriedade
+comutativa da adição. Com o `{iadd}`, a situação é mais clara: o objeto à
+esquerda do operador é atualizado internamente, então não há dúvida quanto ao
+tipo do resultado.
+
+[TIP]
+====
+
+Validei os comportamentos diversos de `{plus}` e `{iadd}` observando como
+funciona o tipo embutido `list`. Ao escrever `my_list + x`, você só pode
+concatenar uma `list` a outra `list`, mas se você escrever `my_list += x`, você
+pode estender a lista da esquerda com itens de qualquer iterável `x` à direita
+do operador. É assim que o método `list.extend()` funciona: ele aceita qualquer
+argumento iterável.
+
+====
+
+Agora que vimos o comportamento desejado para `AddableBingoCage`, podemos
+estudar sua implementação no <>. Lembre-se de que `BingoCage`,
+(<> do <>), é uma subclasse concreta da ABC `Tombola` do
+<> do <>.
+
+[[ex_addable_bingo]]
+.bingoaddable.py: `AddableBingoCage` é subclasse de `BingoCage` com suporte aos operadores `{plus}` e `{iadd}`
+====
+[source, python]
+----
+include::../code/16-op-overloading/bingoaddable.py[tags=ADDABLE_BINGO]
+----
+====
+
+<1> `AddableBingoCage` estende `BingoCage`.
+
+<2> Nosso `+__add__+` só vai funcionar se o segundo operando for uma instância
+de `Tombola`.
+
+<3> Em `+__iadd__+`, obtém os itens de `other`, se for uma instância de
+`Tombola`.
+
+<4> Caso contrário, tenta obter um iterador sobre `other`
+(estudaremos a função embutida `iter` no https://fpy.li/17[«Capítulo 17»] (vol.3)).
+
+<5> Se aquilo falhar, gera uma exceção explicando o que o usuário deve fazer.
+Sempre que possível, mensagens de erro devem orientar o usuário para a solução.
+
+<6> Se chegamos até aqui, podemos carregar o `other_iterable` em `self`.
+
+<7> Muito importante: os métodos especiais de atribuição aumentada de objetos
+mutáveis devem devolver `self`. É o que os usuários esperam.
+
+Podemos resumir toda a ideia dos operadores de atribuição interna comparando
+as instruções `return` que devolvem os resultados em `+__add__+` e em
+`+__iadd__+` no <>:
+
+**`+__add__+`**: O resultado é computado chamando o construtor `AddableBingoCage`
+para criar uma nova instância.
+
+**`+__iadd__+`**: O resultado é `self`, após ele ter sido modificado.
+
+Uma última observação sobre o <>: não implementei `+__radd__+`
+em `AddableBingoCage`, porque não há necessidade. O método direto `+__add__+` só
+vai lidar com operandos do mesmo tipo à direita, então se Python tentar computar
+`a + b`, onde `a` é uma `AddableBingoCage` e `b` não, devolvemos
+`NotImplemented`—talvez a classe de `b` possa fazer isso funcionar. Mas
+se a expressão for `b + a` e `b` não for uma `AddableBingoCage`, e devolver
+`NotImplemented`, então é melhor deixar Python desistir e gerar um `TypeError`,
+pois não temos como tratar `b`.
+
+[TIP]
+====
+
+Se um método de operador infixo direto (por exemplo `+__mul__+`)
+é projetado para funcionar apenas com operandos do mesmo tipo de `self`, é
+inútil implementar o método reverso correspondente (por exemplo, `+__rmul__+`)
+pois, por definição, esse método só será invocado quando estivermos lidando com
+um operando de um tipo diferente.
+
+====
+
+Assim terminamos nossa exploração de sobrecarga de operadores no Python.((("",
+startref="OOaugmented16")))((("", startref="augmented16")))((("",
+startref="addassigna16")))((("", startref="stareqa16")))((("",
+startref="adassb16")))((("", startref="stareqb16")))
+
+
+=== Resumo do capítulo
+
+Começamos((("operator overloading", "overview of"))) o capítulo revisando
+algumas restrições impostas pelo Python à sobrecarga de operadores: é impossível
+redefinir operadores nos tipos embutidos, a sobrecarga está limitada aos
+operadores existentes, e alguns operadores não podem ser sobrecarregados (`is`,
+`and`, `or`, `not`).
+
+Colocamos a mão na massa com os operadores unários, implementando `+__neg__+` e
+`+__pos__+`. A seguir vieram os operadores infixos, começando por `{plus}`,
+suportado pelo método `+__add__+`. Vimos  que operadores unários e infixos devem
+produzir resultados criando novos objetos, sem nunca modificar seus operandos.
+Para suportar operações com outros tipos, devolvemos o valor especial
+`NotImplemented` (não uma exceção) permitindo ao interpretador tentar novamente
+chamando o método especial reverso do segundo operando (por exemplo,
+`+__radd__+`). O algoritmo usado pelo Python para tratar operadores infixos está
+resumido no fluxograma da <>.
+
+Misturar operandos de mais de um tipo exige detectar os operandos que não
+podemos tratar. Neste capítulo fizemos isso de duas maneiras: ao modo da tipagem
+pato, apenas fomos em frente e tentamos a operação, capturando uma exceção de
+`TypeError` se ela acontecesse; mais tarde, em `+__mul__+` e `+__matmul__+`,
+usamos um teste `isinstance` explícito. Há prós e contras nas duas abordagens:
+tipagem pato é mais flexível, mas a checagem explícita de tipo é mais
+previsível.
+
+<<<
+De modo geral, bibliotecas devem aproveitar a tipagem pato para lidar objetos
+de diferentes tipos, desde que eles suportem as operações
+necessárias. Entretanto, o algoritmo de despacho de operadores de Python pode
+produzir mensagens de erro enganosas ou resultados inesperados quando combinado
+com a tipagem pato. Por essa razão, a disciplina da checagem de tipos com
+invocações de `isinstance` contra ABCs é muitas vezes útil quando escrevemos
+métodos especiais para sobrecarga de operadores. Esta é a técnica batizada de
+tipagem ganso (_goose typing_) por Alex Martelli—como vimos na
+<>. A tipagem ganso é um compromisso entre a flexibilidade e a
+segurança, porque os tipos definidos pelo usuário, existentes ou futuros, podem
+ser declarados como subclasses reais ou virtuais de uma ABC. Além disso, se uma
+ABC implementa o `+__subclasshook__+`, objetos podem então passar por checagens
+com `isinstance` contra aquela ABC apenas fornecendo os métodos exigidos—sem
+necessidade de ser uma subclasse ou de se registrar com a ABC.
+
+O próximo tópico tratado foram os operadores de comparação rica. Implementamos
+`==` com `+__eq__+` e descobrimos que Python oferece uma implementação
+conveniente de `!=` no `+__ne__+` herdado da classe base `object`. A forma como
+Python avalia esses operadores, bem como `>`, `<`, `>=`, e `{lte}`, é um pouco
+diferente, com uma lógica especial para a escolha do método reverso, e um
+tratamento alternativo para `==` e `!=` que nunca gera erros, pois a classe
+`object` já implementa os métodos necessários.
+
+Na última seção, nos concentramos nos operadores de atribuição aumentada. Vimos
+que Python os trata, por default, como uma combinação do operador simples
+seguido de uma atribuição: `a {iadd} b` é avaliado exatamente como +
+`a = a + b`.
+Isto sempre cria um novo objeto, então funciona para tipos mutáveis ou
+imutáveis.
+
+Para objetos mutáveis, podemos implementar métodos especiais de atualização
+interna, tal como `+__iadd__+` para `{iadd}`, e alterar o valor do operando à
+esquerda. Para demonstrar isto na prática, implementamos uma subclasse de
+`BingoCage`, suportando `{iadd}` para adicionar itens ao reservatório de itens
+para sorteio, de modo similar à forma como o tipo embutido `list` suporta
+`{iadd}` como um atalho para o método `list.extend()`. Vimos que `{plus}`
+tende a ser mais estrito que `{iadd}` em relação aos tipos aceitos. Em
+sequências, `{plus}` normalmente exige que ambos os operandos sejam do mesmo
+tipo, enquanto `{iadd}` muitas vezes aceita qualquer iterável como o operando à
+direita do operador.
+
+[[further_reading_op_sec]]
+=== Para saber mais
+
+Guido van Rossum((("operator overloading", "further reading on"))) escreveu uma
+boa apologia da sobrecarga de operadores em
+https://fpy.li/16-10[_Why operators are useful_] (Porque operadores são úteis).
+Trey Hunner postou
+https://fpy.li/16-11[_Tuple ordering and deep comparisons in Python_]
+(Ordenação de tuplas e comparações profundas em Python),
+argumentando que os operadores de comparação rica de Python são mais flexíveis e
+poderosos do que os programadores vindos de outras linguagens costumam pensar.
+
+A sobrecarga de operadores é uma área da programação em Python onde testes com
+`isinstance` são comuns. A melhor prática relacionada a tais testes é a tipagem
+ganso, tratada na <>. Se você pulou essa parte, assegure-se de
+voltar lá e ler aquela seção.
+
+A principal referência para os métodos especiais de operadores é o capítulo
+https://fpy.li/2j[Modelo de Dados] na documentação de Python. Outra
+leitura relevante é
+https://fpy.li/7r[Implementando as operações aritméticas]
+no módulo `numbers` da biblioteca padrão de Python.
+
+Um exemplo brilhante de sobrecarga de operadores apareceu no pacote
+https://fpy.li/16-13[`pathlib`], a partir do Python 3.4. Sua classe `Path`
+sobrecarrega o operador `/` para construir caminhos do sistema de arquivos a
+partir de strings, como mostra o exemplo abaixo, da documentação:
+
+[source, python]
+----
+>>> p = Path('/etc')
+>>> q = p / 'init.d' / 'reboot'
+>>> q
+PosixPath('/etc/init.d/reboot')
+----
+
+Outro exemplo não aritmético de sobrecarga de operadores está na biblioteca
+https://fpy.li/16-14[Scapy], usada para "enviar, farejar, dissecar e forjar
+pacotes de rede". Na Scapy, o operador `/` cria pacotes empilhando campos de
+diferentes camadas da rede. Veja https://fpy.li/16-15[_Stacking layers_]
+(Empilhando camadas) para mais detalhes.
+
+<<<
+Se você está prestes a implementar operadores de comparação, estude
+`functools.total_ordering`. Esse é um decorador de classes que gera
+automaticamente os métodos para todos os operadores de comparação rica em
+qualquer classe que defina ao menos alguns deles. Veja a
+https://fpy.li/7q[documentação do módulo functools].
+
+Se tiver curiosidade sobre o despacho de métodos de operadores em linguagens com
+tipagem dinâmica, duas leituras fundamentais são
+https://fpy.li/16-17[_A Simple Technique for Handling Multiple Polymorphism_]
+(Uma técnica simples para tratar polimorfismo múltiplo), de Dan Ingalls
+(membro da equipe original de Smalltalk), e
+https://fpy.li/16-18[_Arithmetic and Double Dispatching in Smalltalk-80_]
+(Aritmética e despacho duplo no Smalltalk-80), de Kurt J.
+Hebel e Ralph Johnson (Johnson ficou famoso como um dos autores do livro
+_Padrões de Projetos_ original).
+
+Os dois artigos discutem em profundidade o poder do polimorfismo em linguagens
+com tipagem dinâmica, como Smalltalk, Python e Ruby. Python não implementa
+despacho duplo exatamente como descrito naqueles artigos. O algoritmo de
+despacho duplo em Python, usando operadores diretos e reversos, é mais fácil de
+suportar em classes definidas pelo usuário que o despacho duplo clássico, mas
+exige tratamento especial pelo interpretador. Por outro lado, o despacho duplo
+clássico é uma técnica geral, que pode ser usada no Python ou em qualquer
+linguagem orientada a objetos, para além do contexto específico de operadores
+infixos. E, de fato, Ingalls, Hebel e Johnson usam exemplos muito diferentes
+para descrever essa técnica.
+
+O texto
+https://fpy.li/16-1[_The C Family of Languages: Interview with Dennis Ritchie, Bjarne Stroustrup, and James Gosling_]
+(A Família de Linguagens C: entrevista com Dennis Ritchie, Bjarne Stroustrup, e James
+Gosling), de onde tirei a epígrafe deste capítulo, apareceu na _Java Report_, 5(7),
+julho de 2000, e na _{cpp} Report_, 12(7), julho/agosto de 2000,
+juntamente com outros trechos que usei no Ponto de Vista deste capítulo (logo
+adiante). Se você se interessa pelo design de linguagens de programação, faça
+um favor a si mesmo e leia aquela entrevista.
+
+<<<
+[[operator_soapbox]]
+.Ponto de Vista
+****
+
+**Sobrecarga de operadores: prós e contras**
+
+James Gosling, citado((("operator overloading",
+"Soapbox discussion")))((("Soapbox sidebars", "operator overloading"))) no início
+deste capítulo, tomou a decisão consciente de excluir a sobrecarga de operadores
+quando projetou o Java. Na entrevista
+https://fpy.li/16-1[_The C Family of Languages_] ele diz:
+
+[quote]
+____
+Talvez uns 20 a 30% da população acha que sobrecarga de
+operadores é obra do demônio; alguém fez algo com sobrecarga de operadores
+que realmente os tirou do sério, porque usaram algo como + para inserção em
+listas, e isso torna a vida muito, muito confusa. Muito do problema vem do
+fato de existirem apenas uma meia dúzia de operadores que podem ser
+sobrecarregados de forma razoável, mas existem milhares ou milhões de operadores
+que as pessoas gostariam de definir—então é preciso escolher, e muitas vezes as
+escolhas entram em conflito com a sua intuição.
+____
+
+Guido van Rossum escolheu o caminho do meio no suporte à sobrecarga de
+operadores: ele não deixou a porta aberta para que os usuários criassem novos
+operadores arbitrários como `{lte}>` ou `:-)`, evitando uma Torre de Babel
+de operadores customizados, e que o analisador sintático de Python
+continue simples. Python também não permite a sobrecarga dos operadores dos
+tipos embutidos, outra limitação que promove a legibilidade e o desempenho
+previsível.
+
+Gosling continua:
+
+[quote]
+____
+
+E então há uma comunidade de aproximadamente 10% que havia de fato usado a
+sobrecarga de operadores de forma apropriada, e que realmente gostavam disso, e
+para quem isso era realmente importante; essas são quase exclusivamente pessoas
+que fazem trabalho numérico, onde a notação é muito importante para avivar a
+intuição [das pessoas], porque elas vêm com uma intuição sobre o que `{plus}`
+significa, e a poder dizer `a + b`, onde a e b são números complexos ou matrizes
+ou alguma outra coisa, realmente faz sentido.
+
+____
+
+<<<
+Também há benefícios em não permitir a sobrecarga de operadores em uma
+linguagem. Já ouvi o argumento de que C é melhor que {cpp}; para
+programação de sistemas, porque a sobrecarga de operadores em {cpp} pode
+fazer com que operações dispendiosas pareçam triviais. Duas linguagens modernas
+bem sucedidas, que compilam para executáveis binários, fizeram escolhas opostas:
+Go não tem sobrecarga de operadores, https://fpy.li/16-21[Rust tem].
+
+Mas operadores sobrecarregados, quando usados de forma sensata, tornam o código
+mais fácil de ler e escrever. É um ótimo recurso em uma linguagem de alto nível
+moderna.
+
+**Um exemplo de avaliação preguiçosa**
+
+Se você olhar de perto o _traceback_ no <>, vai
+encontrar evidências da avaliação https://fpy.li/16-22[preguiçosa] de
+expressões geradoras. O <> é o mesmo
+_traceback_, agora com explicações.
+
+[[ex_vector_error_iter_not_add_repeat]]
+.Mesmo que o <>
+====
+[source, python]
+----
+>>> v1 + 'ABC'
+Traceback (most recent call last):
+  File "", line 1, in 
+  File "vector_v6.py", line 329, in __add__
+    return Vector(a + b for a, b in pairs)  # <1>
+  File "vector_v6.py", line 243, in __init__
+    self._components = array(self.typecode, components)  # <2>
+  File "vector_v6.py", line 329, in 
+    return Vector(a + b for a, b in pairs)  # <3>
+TypeError: unsupported operand type(s) for +: 'float' and 'str'
+----
+====
+
+<1> A chamada a `Vector` recebe uma expressão geradora como seu argumento
+`components`. Nenhum problema nesse estágio.
+
+<2> A genexp `components` é passada para o construtor de `array`. Dentro do
+construtor de `array`, Python tenta iterar sobre a genexp, causando a avaliação
+do primeiro item `a + b`. É quando ocorre o `TypeError`.
+
+<3> A exceção se propaga para a chamada ao construtor de `Vector`, onde é
+relatada.
+
+Isso mostra como a expressão geradora é avaliada no último instante possível, e
+não onde é definida no código-fonte.
+
+Se, por outro lado, o construtor de `Vector` fosse invocado como
+`Vector([a + b for a, b in pairs])`, então a exceção ocorreria bem ali,
+porque a compreensão de lista tentou criar uma `list` para ser passada como
+argumento para a chamada a `Vector()`. O corpo de `+Vector.__init__+`
+nunca seria alcançado.
+
+O https://fpy.li/17[«Capítulo 17»] (vol.3) vai tratar das expressões geradoras em detalhes, mas eu não
+queria deixar essa demonstração acidental de sua natureza preguiçosa passar
+despercebida.
+
+****
diff --git a/vol2/vol2-cor.adoc b/vol2/vol2-cor.adoc
index 7716c81..0a7c800 100644
--- a/vol2/vol2-cor.adoc
+++ b/vol2/vol2-cor.adoc
@@ -1,4 +1,4 @@
-= Python Fluente, 2ª edição: volume 2: Classes e Protocolos
+= Python Fluente, 2ª edição: volume 2: Classes e Protocolos
 :doctype: book
 :media: prepress
 :hide-uri-scheme:
@@ -13,12 +13,12 @@
 :sectnumlevels: 4
 :sectlinks:
 :data-uri:
-:!chapter-signifier:
-include::../print/attrib-print-pt-br.adoc[]
 :toclevels: 2
 :toc: macro
+:!chapter-signifier:
+include::../print/attrib-print-pt-br.adoc[]
 :chapter-number: 8
-:revisao: 9
+:revisao: 10
 
 include::Copyright-cor.adoc[]
 
@@ -46,17 +46,17 @@ A Parte II.a está no Volume 1, e também na Web:
 Os capítulos 9 e 10 estão neste Volume 2.
 ====
 
-include::../online/cap09.adoc[]
-include::../online/cap10.adoc[]
+include::cap09.adoc[]
+include::cap10.adoc[]
 
 [[data_structures_part]]
-= Parte III: Classes e protocolos
+= Parte III: Classes e Protocolos
 :sectnums:
 
-include::../online/cap11.adoc[]
-include::../online/cap12.adoc[]
-include::../online/cap13.adoc[]
-include::../online/cap14.adoc[]
-include::../online/cap15.adoc[]
-include::../online/cap16.adoc[]
+include::cap11.adoc[]
+include::cap12.adoc[]
+include::cap13.adoc[]
+include::cap14.adoc[]
+include::cap15.adoc[]
+include::cap16.adoc[]
 
diff --git a/vol2/vol2-pb.adoc b/vol2/vol2-pb.adoc
new file mode 100644
index 0000000..b3d9c88
--- /dev/null
+++ b/vol2/vol2-pb.adoc
@@ -0,0 +1,62 @@
+= Python Fluente, 2ª edição: volume 2: Classes e Protocolos
+:doctype: book
+:media: prepress
+:hide-uri-scheme:
+:pdf-page-size: [17cm, 24cm]
+:source-highlighter: rouge
+:rouge-style: bw
+:author: Luciano Ramalho
+:lang: pt_BR
+:language: asciidoctor
+:xrefstyle: short
+:sectnums:
+:sectnumlevels: 4
+:sectlinks:
+:data-uri:
+:toclevels: 2
+:toc: macro
+:!chapter-signifier:
+include::../print/attrib-print-pt-br.adoc[]
+:chapter-number: 8
+:revisao: 10
+
+include::Copyright-pb.adoc[]
+
+[dedication%notitle%discrete,toclevels=0]
+= Dedicatória
+__Para Marta, com todo o meu amor.__
+
+toc::[]
+
+= Parte II.b: Funções como objetos
+
+[NOTE]
+====
+Esta versão impressa do _Python Fluente, 2ª Edição_
+foi publicada em três volumes, com oito capítulos por volume.
+
+A Parte II ficou dividida entre os Volumes 1 e 2.
+
+A Parte II.a está no Volume 1, e também na Web:
+
+«Capítulo 7—Funções como objetos de primeira classe» [.small]#[fpy.li/7]#
+
+«Capítulo 8—Padrões de projeto com funções» [.small]#[fpy.li/8]#
+
+Os capítulos 9 e 10 estão neste Volume 2.
+====
+
+include::cap09.adoc[]
+include::cap10.adoc[]
+
+[[data_structures_part]]
+= Parte III: Classes e Protocolos
+:sectnums:
+
+include::cap11.adoc[]
+include::cap12.adoc[]
+include::cap13.adoc[]
+include::cap14.adoc[]
+include::cap15.adoc[]
+include::cap16.adoc[]
+
diff --git a/vol3/Copyright-cor.adoc b/vol3/Copyright-cor.adoc
new file mode 100644
index 0000000..0179251
--- /dev/null
+++ b/vol3/Copyright-cor.adoc
@@ -0,0 +1,48 @@
+[colophon%discrete%notitle%nonfacing,toclevels=0]
+= Copyright
+:isbn-cor: XXXX
+:isbn-pb: XXXX
+
+Tradução autorizada em português de
+_Fluent Python, 2nd Edition_ + 
+ISBN 978-1-492-05635-5
+© 2022 Luciano Ramalho. +
+Esta tradução é publicada e vendida com a permissão da O'Reilly Media, Inc.,
+detentora dos direitos para publicação e venda desta obra.
+
+© 2025 Luciano Ramalho. +
+_Python Fluente, 2ª edição_ está publicado sob a licença
+CC BY-NC-ND 4.0 +
+_Atribuição-NãoComercial-SemDerivações 4.0 Internacional_ [.small]#[fpy.li/ccby]#. +
+O autor mantém uma versão online em https://PythonFluente.com.
+
+Autor: Luciano Ramalho +
+Título: Python Fluente, 2ª edição, volume 3: Fluxo e Metaprogramação +
+Primeira edição: 2015 +
+Edição atual: setembro/2025 +
+{revisao}ª revisão: `pyfl-vol2-rev{revisao}-cor.pdf`
+
+Tradução da 2ª edição: Paulo Candido de Oliveira Filho +
+Ilustração de capa: Thiago Castor (xilogravura "Calango") +
+Design da capa: Luciano Ramalho, Zander Catta Preta @ Z•Edições +
+Design do miolo: Luciano Ramalho, com Asciidoctor +
+Ficha catalográfica: Edison Luís dos Santos
+
+Publisher: Heinar Maracy @ Z•Edições
+
+----
+R135p   Ramalho, Luciano.
+
+        Python Fluente, 2ª edição, volume 3: Fluxo e Metaprogramação /
+        Luciano Ramalho - São Paulo, SP - Z.Edições, 2025.
+        400 f.; il.; cor; 17 cm × 24 cm
+
+        ISBN: XXX-XX-XXXXXX-X-X
+        1.Informática. 2.Linguagem de Programação. 3.Python.
+        4.Metaprogramação.
+
+        I.Título II.Fluxo e Metaprogramação III.RAMALHO, Luciano.
+
+                                                            CDU: 004.438
+                                                            CDD: 005.133
+----
diff --git a/vol3/README.md b/vol3/README.md
new file mode 100644
index 0000000..37697d5
--- /dev/null
+++ b/vol3/README.md
@@ -0,0 +1,19 @@
+# Python Fluente, 2ª ed, volume 3
+
+## Progresso
+
+Faço as primeiras tarefas nos arquivos `/online/cap??.adoc`.
+
+Depois copio cada arquivo para `/vol3/cap??.adoc`
+e faço as demais tarefas nestas cópias especiais para impressão.
+
+| 17| 18| 19| 20| 21| 22| 23| 24| local | tarefa |
+|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|-------|-------|
+|✅ |✅ |✅ |✅ |✅ |✅ |✅ |✅ |`/online`|encurtar links externos|
+|   |✅ |✅ |   |   |   |   |   |`/online`|revisar estilo|
+|   |   |   |   |   |   |   |   |`/online`|revisar ortografia e gramática|
+|   |   |   |   |   |   |   |   |`/vol3`| refazer referências entre volumes|
+|   |   |   |   |   |   |   |   |`/vol3`| encurtar links entre volumes |
+|   |   |   |   |   |   |   |   |`/vol3`| exibir capítulo alvo em xrefs para exemplos de outros capítulos |
+|   |   |   |   |   |   |   |   |`/vol3`| rever paginação   |
+
diff --git a/vol3/vol3-cor.adoc b/vol3/vol3-cor.adoc
new file mode 100644
index 0000000..e4ef2df
--- /dev/null
+++ b/vol3/vol3-cor.adoc
@@ -0,0 +1,41 @@
+= Python Fluente, 2ª edição: volume 3: Controle e Metaprogramação
+:doctype: book
+:media: prepress
+:hide-uri-scheme:
+:pdf-page-size: [17cm, 24cm]
+:source-highlighter: rouge
+:rouge-style: github
+:author: Luciano Ramalho
+:lang: pt_BR
+:language: asciidoctor
+:xrefstyle: short
+:sectnums:
+:sectnumlevels: 4
+:sectlinks:
+:data-uri:
+:toclevels: 2
+:toc: macro
+:!chapter-signifier:
+include::../print/attrib-print-pt-br.adoc[]
+:chapter-number: 16
+:revisao: 10
+
+include::Copyright-cor.adoc[]
+
+[dedication%notitle%discrete,toclevels=0]
+= Dedicatória
+__Para Marta, com todo o meu amor.__
+
+toc::[]
+
+= Parte IV: Classes e Protocolos
+:sectnums:
+
+include::../online/cap17.adoc[]
+include::../online/cap18.adoc[]
+include::../online/cap19.adoc[]
+include::../online/cap20.adoc[]
+include::../online/cap21.adoc[]
+include::../online/cap22.adoc[]
+include::../online/cap23.adoc[]
+include::../online/cap24.adoc[]